c++ - 为什么运行使用 gcc 和 clang 编译的程序时输出不同

标签 c++ templates gcc clang value-categories

我遇到了这个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/

相关文章:

c++ - 在 Windows/Linux 中检查对文件的写入权限

jquery - 在 ASP.NET MVC 3 下的 "Page-less"设计中使用 jQuery JSON 请求的 API 驱动设计有哪些缺点?

c++ - boost::lexical_cast 如何只采用一种模板类型?

c++ - 程序给出错误的分配错误

c++ - 如何将项目 win32 控制台应用程序转换为 C++ Windows 窗体?

ios - 有没有办法在团队中共享 Xcode 模板?

c++ - GCC/进行构建时间优化

c - gcc 中如何实现可变参数?

python - mingw32-gcc 编译 Cython 输出 : unknown multiarch location for pyconfig. h (和其他警告)

c++ - 混合 std::move() 和 std::thread 不会编译