函数返回的局部变量是否在C++20中自动移动?
请考虑一个 C++ 程序,其中函数使用两个构造函数之一从类型的局部变量foo构建其返回U对象int:
struct U {
U(int) {}
U(int&&) {}
};
U foo(int a = 0) { return a; }
int main() { foo(); }
在 C++17 模式下,程序被所有编译器接受,演示:https : //gcc.godbolt.org/z/b8hhEh948
但是在 C++20 模式下,GCC 拒绝它并显示错误:
In function 'U foo(int)':
<source>:6:27: error: conversion from 'int' to 'U' is ambiguous
6 | U foo(int a = 0) { return a; }
| ^
<source>:3:5: note: candidate: 'U::U(int&&)'
3 | U(int&&) {}
| ^
<source>:2:5: note: candidate: 'U::U(int)'
2 | U(int) {}
| ^
演示:https : //gcc.godbolt.org/z/fMvEPMGhq
我认为这是因为 C++20 特性P1825R0:P0527R1 和 P1155R3 的合并措辞(更多隐式动作)
并且foo根据此特性的函数必须等效于
U foo(int a = 0) { return std::move(a); }
由于所有编译器的构造函数选择歧义而被拒绝,演示:https : //gcc.godbolt.org/z/TjWeP965q
GCC 是 C++20 模式下上述示例中唯一正确的编译器吗?
回答
这里发生的是 gcc 实现 P1825 的方式。
在这个例子中:
U foo(int a) {
return a;
}
C++17 和 C++20 语言规则(这里没有变化)是我们首先将其a视为右值,如果重载解析失败(在 C++17 中,也需要绑定到int&&),然后我们将a作为左值。有了这条规则,这段代码就可以工作了——a作为 xvalue的重载解析由于歧义而失败(因为U是一种愚蠢的类型),所以我们回退到将其a视为左值,然后成功了。
但是 gcc 的实现并没有这样做。相反,它将axvalue视为也可以绑定到非常量左值引用的 xvalue,并执行单轮重载决议(这样做是为了避免破坏某些代码,请参见此处)。那一轮重载决议是模棱两可的,因为简单地将其a视为 xvalue 是模棱两可的,并且这里没有相关的左值 ref 构造函数,因此 gcc 拒绝了该示例。
但在这种情况下,我会说这是U的错而不是 gcc 的错。如果U(a)像往常一样有两个构造函数采用int const&and int&&,或者 (b) 有一个采用 的构造函数int,则该示例可以正常编译。请注意,在 (b) 情况下,C++17 规则将执行复制,但 C++20 规则将执行移动(因为我们不再要求构造函数专门采用右值引用)。
- [P2266](http://open-std.org/JTC1/SC22/WG21/docs/papers/2021/p2266r1.html) 应该让 `a` 只是一个 xvalue,所以它是模棱两可的,但不支持一个 `int&` 构造函数。