函数返回的局部变量是否在 C++20 中自动移动?
Is the local variable returned by a function automatically moved in C++20?
请考虑一个 C++ 程序,其中函数 foo
使用两个构造函数之一从 int
类型的局部变量构建其返回的 U
对象:
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: Merged wording for P0527R1 and P1155R3 (more implicit moves)
而根据这个特性的函数foo
一定等价于
U foo(int a = 0) { return std::move(a); }
由于所有编译器的构造函数选择不明确而被拒绝,演示:https://gcc.godbolt.org/z/TjWeP965q
在 C++20 模式下,GCC 是上面示例中唯一正确的编译器吗?
这里发生的是 gcc 实现 P1825 的方式。
在这个例子中:
U foo(int a) {
return a;
}
C++17 和 C++20 语言规则(这里没有变化)是我们首先将 a
视为右值,如果重载解析失败(在 C++17 中,它是还需要绑定到 int&&
),那么我们将 a
视为左值。使用该规则,此代码有效 - 由于歧义(因为 U
是一个愚蠢的类型),将 a
作为 xvalue 的重载决策失败,因此我们回退到将 a
视为左值, 就成功了。
但是 gcc 的实现并没有这样做。相反,它将 a
视为一个 xvalue,它也可以绑定到非常量左值引用,并执行单轮重载决议(这样做是为了避免破坏某些代码,请参阅 here) .单轮重载决议是不明确的,因为简单地将 a
视为 xvalue 是不明确的,并且没有与此处相关的左值引用构造函数,因此 gcc 拒绝该示例。
但在这种情况下,我会说这是 U
的错误,而不是 gcc 的错误。如果 U
(a) 有两个像往常一样采用 int const&
和 int&&
的构造函数,或者 (b) 有一个采用 int
的构造函数,该示例将编译美好的。请注意,在 (b) 情况下,C++17 规则将执行复制,但 C++20 规则将执行移动(因为我们不再要求构造函数专门采用右值引用)。
请考虑一个 C++ 程序,其中函数 foo
使用两个构造函数之一从 int
类型的局部变量构建其返回的 U
对象:
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: Merged wording for P0527R1 and P1155R3 (more implicit moves)
而根据这个特性的函数foo
一定等价于
U foo(int a = 0) { return std::move(a); }
由于所有编译器的构造函数选择不明确而被拒绝,演示:https://gcc.godbolt.org/z/TjWeP965q
在 C++20 模式下,GCC 是上面示例中唯一正确的编译器吗?
这里发生的是 gcc 实现 P1825 的方式。
在这个例子中:
U foo(int a) {
return a;
}
C++17 和 C++20 语言规则(这里没有变化)是我们首先将 a
视为右值,如果重载解析失败(在 C++17 中,它是还需要绑定到 int&&
),那么我们将 a
视为左值。使用该规则,此代码有效 - 由于歧义(因为 U
是一个愚蠢的类型),将 a
作为 xvalue 的重载决策失败,因此我们回退到将 a
视为左值, 就成功了。
但是 gcc 的实现并没有这样做。相反,它将 a
视为一个 xvalue,它也可以绑定到非常量左值引用,并执行单轮重载决议(这样做是为了避免破坏某些代码,请参阅 here) .单轮重载决议是不明确的,因为简单地将 a
视为 xvalue 是不明确的,并且没有与此处相关的左值引用构造函数,因此 gcc 拒绝该示例。
但在这种情况下,我会说这是 U
的错误,而不是 gcc 的错误。如果 U
(a) 有两个像往常一样采用 int const&
和 int&&
的构造函数,或者 (b) 有一个采用 int
的构造函数,该示例将编译美好的。请注意,在 (b) 情况下,C++17 规则将执行复制,但 C++20 规则将执行移动(因为我们不再要求构造函数专门采用右值引用)。