我遇到了这个example观看有关 C++ 的视频时如下:
#include <iostream>
#include <type_traits>
struct S {
S() { std::cout << 'a'; }
S(const S&) { std::cout << 'b'; }
S(S&&) { std::cout << 'c'; }
};
template <typename T>
S f(T&& t) { return t; }
int main() {
S s{};
f(s);
f(std::move(s));
}
起初我认为很明显 std::move(s) 将 s 转换为右值引用,并且模板被实例化为 S f(S&& t) {return t;}
。该参数是一个右值,因此在传递参数时 S(S&&)
被调用,输出为“abc”。使用编译器 x86-64 clang 15.0.0 编译时,输出为“abc”。但是当使用x86-64 gcc 12.2编译时,它输出'abb'。
为什么用 gcc 编译时输出'b'而不是'c'?这是关于 UB 还是依赖于实现的问题?还是还有什么我不知道的?
编辑:当模板修改为这样时:
template <typename T>
S f(T&& t) { return std::forward<T>(t); }
gcc 开始像 clang 一样输出 'abc'(与上述版本相同)。 我不知道它与 std::forward 有何关系。 这是video我当时正在看。该示例大约出现在 54 分钟处。
最佳答案
最初在 C++17(及之前)中,正确答案是 abb
。
最后一次调用中使用的 f
的特化如下所示:
S f<S>(S&& t) { return t; }
通常,变量的名称,无论其类型如何,都是左值,因此重载决策应选择此处的复制构造函数来构造返回值。
但是,操作数为(可能带括号的)id 表达式的 return 语句具有特殊规则,在某些情况下,首先执行重载解析,就像操作数是右值一样,即使它不是右值,并且仅当此重载决议失败,将尝试正常的重载决议。如果这适用于您的情况,则 return t;
仍将使用移动构造函数。
在 C++17 中,这种特殊行为的要求之一是 id-expression 命名一个局部变量,该变量是一个对象,而不是引用。因此它不适用于此处。
对于 C++20,这已更改为 P0527包含右值引用类型的局部变量。
GCC 似乎实现了此更改,仅适用于 C++20 及更高版本(您可以通过 -std=c++20
获得 abc
),而Clang 似乎也将其作为缺陷报告应用于较旧的 C++ 模式。
commit message实现更改的 GCC 11 提到,这应该也是针对早期 C++ 版本的缺陷报告,但他们希望收集一些有关此更改影响的经验。他们似乎尚未实现 -std=c++20
以下的模式。
对于 C++23,规则进一步简化,因此不再有两个重载解析阶段。相反,在这些特殊情况下,id 表达式现在只是 xvalue 表达式,而不是 lvalue 表达式。此更改是由 P2266 进行的.
关于c++ - 为什么运行使用 gcc 和 clang 编译的程序时输出不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75606715/