函数返回的局部变量是否在 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 规则将执行移动(因为我们不再要求构造函数专门采用右值引用)。