Given the following code snippet:
struct Foo {
};
struct Bar {
operator const Foo() {
return Foo();
}
};
int main() {
Bar bar;
Foo foo(bar);
return 0;
}
See here on godbolt
It compiles fine with gcc 11.2 but fails to compile with clang 12.0 with the following error:
<source>:12:13: error: no viable conversion from 'Bar' to 'Foo'
Foo foo(bar);
^~~
<source>:1:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'Bar' to 'const Foo &' for 1st argument
struct Foo {
^
<source>:1:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'Bar' to 'Foo &&' for 1st argument
struct Foo {
^
<source>:5:5: note: candidate function
operator const Foo() {
^
<source>:1:8: note: passing argument to parameter here
struct Foo {
^
- Which implementation is correct?
- Is this actually valid C ?
PS: I know it can be fixed by removal of the const or return of a const reference.
CodePudding user response:
I think this is the open CWG issue 2077.
Basically,
Foo foo(bar);
is direct-initialization, meaning that it will consider the constructors of Foo for overload resolution and choose the best viable one. The candidates are the implicit copy and move constructor with signatures
Foo(const Foo&);
Foo(Foo&&);
If you look at the linked CWG issue and for more details the related Clang bug, then we see that this is exactly the situation described there, only that our function call is a constructor call, while theirs is a normal function call.
If we simply believe the argument made in the CWG issue, then overload resolution should apparently by current rules pick the Foo&& overload, while not actually being allowed to bind the reference, resulting in an ill-formed program.
I will try to reproduce the reasoning from the linked sources here:
When considering conversions to the constructor's parameter from bar, the reference cannot directly bind bar because of the type mismatch.
According to [over.ics.ref]/2 in such a case the conversion sequence is determined as if by copy-initialization of a temporary of the referenced type.
In this case the conversion is Bar -> const Foo -> Foo via the user-defined constructor for the move constructor.
For the copy constructor it is Bar -> const Foo.
However in the copy-initialization of the temporary, const-qualifier conversions are "subsumed" in the initialization. [over.ics.ref]/2 Therefore the move constructor sequence is not worse than that of the copy constructor.
In such a case the rvalue reference is a tie-breaker and so the move constructor is chosen in overload resolution.
However, a const Foo cannot be bound to a non-const rvalue reference. Therefore the program is ill-formed. ([dcl.init.ref]/5.4.3, DR 1604)
Also note that the rest of [over.ics.ref] gives requirements on the binding of references to influence the viability of an overload, but do not include binding of const rvalues to non-const rvalue references.
This is not a problem in e.g. copy-initialization (Foo foo = bar;), because that can use the conversion operator directly without going through the constructor.
It is also not a problem before C 11, because the move constructor doesn't exist there.
For the same reason it is not a problem if the copy constructor of Foo is explicitly declared, because that would inhibit the declaration of the implicit move constructor.
It is also not a problem in C 17 and later because mandatory copy elision makes it so the move constructor will never be called.
I think this explains all of Clang's behaviors.
GCC and MSVC seem to choose the copy constructor instead of the move constructor in overload resolution, which while apparently not correct by the current wording of the standard, is probably what is intended to happen and what the linked CWG issue aims for, by removing the function overload with the ill-formed reference binding from the set of viable functions.
CodePudding user response:
I'm making a 'stab' at this, but I may get shot to pieces …
As others have mentioned in the comments, clang compiles it without error or warning if the standard is set to C 17 (or higher); and this is, according to cppreference, correct:
Temporary materialization
A prvalue of any complete type T can be converted to an xvalue of the same type T. This conversion initializes a temporary object of type T from the prvalue by evaluating the prvalue with the temporary object as its result object, and produces an xvalue denoting the temporary object.
…
Temporary materialization occurs in the following situations: [Since C 17]
- when binding a reference to a prvalue;
So, the question then becomes: Is the bar argument in the call to the Foo constructor a prvalue? I would say yes; again, following cppreference:
The following expressions are prvalue expressions:
…
- a function call or an overloaded operator expression, whose return type is non-reference, …
You have (effectively) an 'overloaded' conversion operator whose return type is non-reference.
