Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the local variable returned by a function automatically moved in C++20?

Please consider a C++ program, where the function foo builds its returning U object from a local variable of type int using one of two constructors:

struct U {
    U(int) {}
    U(int&&) {}
};

U foo(int a = 0) { return a; }

int main() { foo(); }

In C++17 mode the program is accepted by all compilers, demo: https://gcc.godbolt.org/z/b8hhEh948

However in C++20 mode, GCC rejects it with the error:

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) {}
      |     ^

demo: https://gcc.godbolt.org/z/fMvEPMGhq

I think this is because of C++20 feature P1825R0: Merged wording for P0527R1 and P1155R3 (more implicit moves) And the function foo according to this feature must be equivalent to

U foo(int a = 0) { return std::move(a); }

which is rejected due to constructor selection ambiguity by all compilers, demo: https://gcc.godbolt.org/z/TjWeP965q

Is GCC the only right compiler in above example in C++20 mode?

like image 617
Fedor Avatar asked Aug 11 '21 18:08

Fedor


1 Answers

What happens here is that the way that gcc went about implementing P1825.

In this example:

U foo(int a) {
    return a;
}

The C++17 and C++20 language rules (no change here) are that we first treat a as an rvalue and if that overload resolution fails (in C++17, it was also required to bind to int&&), then we treat a as an lvalue. With that rule, this code works - the overload resolution with a as an xvalue fails due to ambiguity (because U is a silly type), so we fallback to treating a as an lvalue, and that succeeds.

But gcc's implementation doesn't do that. Instead it treats a as an xvalue that can also bind to non-const lvalue references, and performs a single round of overload resolution (it does so in an effort to avoid breaking some code, see here). That single round of overload resolution is ambiguous, because simply treating a as an xvalue is ambiguous and there's no lvalue ref constructor that is relevant here, so gcc rejects the example.

But in this case, I'd say this is U's fault rather than gcc's. If U either (a) had two constructors that took int const& and int&& as is usual or (b) had a single constructor that took an int, the example would compile fine. Note that in the (b) case, the C++17 rule would perform a copy but the C++20 rule would perform a move (since we no longer require that the constructor specifically takes an rvalue reference).

like image 160
Barry Avatar answered Sep 28 '22 02:09

Barry