Consider this example
#include <iostream>
struct T{
T() = default;
T(T const&) =default;
};
T global;
struct S{
operator T(){
std::cout<<"#1\n";
return T{};
}
operator T&(){
std::cout<<"#2\n";
return global;
}
};
int main(){
S s;
T obj(s);
}
GCC selects #1
while Clang selects #2
, instead, I consider them ambiguous. According to dcl.init#17.6.3
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversions that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in [over.match.copy], and the best one is chosen through overload resolution ([over.match]). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call is a prvalue of the cv-unqualified version of the destination type whose result object is initialized by the constructor. The call is used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
According to over.match.copy#1.2
When the type of the initializer expression is a class type “cv S”, the non-explicit conversion functions of S and its base classes are considered. When initializing a temporary object ([class.mem]) to be bound to the first parameter of a constructor where the parameter is of type “reference to cv2 T” and the constructor is called with a single argument in the context of direct-initialization of an object of type “cv3 T”, explicit conversion functions are also considered. Those that are not hidden within S and yield a type whose cv-unqualified version is the same type as T or is a derived class thereof are candidate functions. A call to a conversion function returning “reference to X” is a glvalue of type X, and such a conversion function is therefore considered to yield X for this process of selecting candidate functions.
Hence, regardless of #1
or #2
, they are both considered to yield type T
. They both have the implicit parameter object with T&
, hence [over.match.best#2.1] cannot determine which is the best
for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
The only hope that can determine which overload is the best falls on [over.match.best#2.2]
the context is an initialization by user-defined conversion (see [dcl.init], [over.match.conv], and [over.match.ref]) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type
Their standard conversion sequences are both identity conversions. Hence, they are should be ambiguous. However, GCC and Clang have their respective interpretations and are not consistent. I wonder which interpretation is correct?
T
is if essence here? Particularly if we consider a two-step approach, were the destination type of the first step is the type of the copy ctor ofT
, and then going via [dcl.init]/17.2 into [dcl.init.ref]. This would argue for Clang being correct. – CovalenceT
would affect the overload resolution in [dcl.init#17.6.3], since it says The call is used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization., In this step, it will involve the constructor call. overload resolution takes place prior to that direct-initialization. – MisstepT
is prohibited, but if one enables it then GCC starts complaining about ambiguity, while Clang does not: godbolt.org/z/ncYd9qTrs This is another point of interest. – Areola