I considered a type conversion from one type to the other, which are defined by two way, i.e., type conversion constructor and type conversion function.
struct to_type;
struct from_type{
operator to_type()const;
};
struct to_type{
to_type() = default;
to_type(const from_type&){}
};
from_type::operator to_type()const{return to_type();}
int main(){
from_type From;
to_type To;
To = From;
return 0;
}
gcc (v13.0.0, but seems to be same even in v4.9.4) don't throw any error and just call type conversion constructor in the above code.
On the other hand, clang (v16.0.0, but seems to be same even in v7.6.0) throw a "ambiguous" compile error like the following.
prog.cc:14:10: error: reference initialization of type 'to_type &&' with initializer of type 'from_type' is ambiguous
To = From;
^~~~
prog.cc:3:4: note: candidate function
operator to_type()const;
^
prog.cc:7:4: note: candidate constructor
to_type(const from_type&){}
^
prog.cc:5:8: note: passing argument to parameter here
struct to_type{
^
1 error generated.
It seems to be so curious that two major compiler show different result for this simple code. Is either compiler don't match with the standard for C++? I guess this overload resolution related to [over.ics.rank], but I could not concluded which compiler's behavior match to the standard.
Or do my source code contains undefined behavior?
[ADD 2022-12-21T00:20Z]
Following the comment by Artyer, I tried -pedantic
compile option for gcc, and now gcc also output error message.
prog.cc: In function 'int main()':
prog.cc:14:10: error: conversion from 'from_type' to 'to_type' is ambiguous
14 | To = From;
| ^~~~
prog.cc:9:1: note: candidate: 'from_type::operator to_type() const'
9 | from_type::operator to_type()const{return to_type();}
| ^~~~~~~~~
prog.cc:7:4: note: candidate: 'to_type::to_type(const from_type&)'
7 | to_type(const from_type&){}
| ^~~~~~~
prog.cc:5:8: note: initializing argument 1 of 'constexpr to_type& to_type::operator=(to_type&&)'
5 | struct to_type{
| ^~~~~~~
This suggests that at least the default behavior of gcc without -pedantic
don't match with the requirements of C++ standard for this source code.
In this case clang is right, this behaviour is defined in [over.best.ics.general], and standard even mentions that such a conversion is ambiguous explicitly under the sample code to [over.best.ics.general]/10 (the scenario under the link is in fact considers another kind of ambiguity, but resolution to user-defined pair of conversion constructor and conversion operator is one of the candidates, so I removed the part of the code with another candidate):
class B;
class A { A (B&);};
class B { operator A (); };
...
void f(A) { }
...
B b;
f(b); // ... an (ambiguous) conversion b → A (via constructor or conversion function)
In order to break the name resolution down, I'd like to represent the conversion sequence (To = From;
) as an assignment operator function:
to_type& operator=(to_type&& param) { } // defined implicitly by the compiler
The actual conversion happens when param
needs to get into existence out of From
argument of type from_type
. The compiler then needs to decide step by step:
Which type the conversion sequence from from_type
to to_type&&
is of? (standard/user defined/ellipsis):
from_type
is user defined, but to_type&&
is of reference type, and reference binding could be considered identity (i.e. standard) conversion. However it's not the case, since from_type
and to_type
are not the same and cannot be bound directly ([over.ics.ref]/2):When a parameter of reference type is not bound directly to an argument expression, the conversion sequence is the one required to convert the argument expression to the referenced type according to [over.best.ics].
A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user-defined conversion ([class.conv]) followed by a second standard conversion sequence. If the user-defined conversion is specified by a constructor ([class.conv.ctor]), the initial standard conversion sequence converts the source type to the type of the first parameter of that constructor. If the user-defined conversion is specified by a conversion function, the initial standard conversion sequence converts the source type to the type of the implicit object parameter of that conversion function.
This sounds about right to me: we need to convert from_type
argument to to_type
temporary in order for to_type&&
to bind to the argument, thus the sequence is either
from_type
-> const from_type&
for the converting constructor argument to_type(const from_type&)
-> to_type&&
for the move-assignment operator of to_type& operator=(to_type&&)
OR
from_type
-> implicit-object-parameter of type const from_type&
for conversion function operator to_type() const
-> to_type&&
for the move-assignment operator of to_type& operator=(to_type&&)
.Now we have two possible conversion sequences of the same kind (user-defined). For this scenario [over.best.ics.general]/10 says the following:
If there are multiple well-formed implicit conversion sequences converting the argument to the parameter type, the implicit conversion sequence associated with the parameter is defined to be the unique conversion sequence designated the ambiguous conversion sequence. For the purpose of ranking implicit conversion sequences as described in [over.ics.rank].
The Ranking implicit conversion sequences documentation then gives the following clues about deciding on which conversion (of the same sequence type) should take precedence for user-defined sequences ([over.ics.rank]/3.3, emphasis mine):
User-defined conversion sequence
U1
is a better conversion sequence than another user-defined conversion sequenceU2
if they contain the same user-defined conversion function or constructor or they initialize the same class in an aggregate initialization and in either case the second standard conversion sequence ofU1
is better than the second standard conversion sequence ofU2
Here we go, for both scenarios (with the converting constructor and the conversion function) the second standard conversion is of the same type (a temporary of type to_type
to to_type&&
), thus the operations are indistinguishable.