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?
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With