8
votes
#include <iostream>
using namespace std;

struct CL2
{
    CL2(){}
    CL2(const CL2&){}
};

CL2 cl2;

struct CL1
{
    CL1(){}
    operator CL2&(){cout<<"operator CL2&"; return cl2;}
    operator const CL2&(){cout<<"operator const CL2&"; return cl2;}
};

CL1 cl1;

int main()
{
    CL1 cl1;
    CL2 cl2 (cl1);
}

Both clang and gcc give ambiguous conversion operator, but Visual Studio compiles ok and prints "operator const CL2&". How must be right according to Standard?
As I undestand, conversion of CL1 to const CL2& is in copy-initialization context (as a part of a direct-initialization of cl2 object). I seen n4296 draft, [over.match.copy]:

Assuming that “cv1 T” is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:
— The converting constructors (12.3.1) of T are candidate functions.
— 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 to be bound to the first parameter of a constructor where the parameter is of type “reference to possibly cv-qualified T” and the constructor is called with a single argument in the context of direct-initialization of an object of type “cv2 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. Conversion functions that return “reference to X” return lvalues or xvalues, depending on the type of reference, of type X and are therefore considered to yield X for this process of selecting candidate functions.

I.e. both of conversion operators are considered as return CL2 and const CL2 (not just CL2 without const) and it remains to solve, which conversion is better: CL2 -> const CL2& or const CL2 -> const CL2&. The second case seems more appropriate. Should a better qualification conversion considered in that context? Or both cases are Identity conversion? I couldn't find it in Standard

1
Weirdly enough Visual Studio does compile your code. But IntelliSense complains on declaring the second object. As far as I understand, your problem is not what you think it is. The compiler doesn't know which conversion operator function to call, since you can't overload based on return type, but you can overload based on the function's constness. Try operator const CL2&() const { cout << "operator const CL2&"; return cl2; }DeiDei

1 Answers

4
votes

Since both conversion operators have identical signatures, the only way in which one could be preferred over the other is by application of [over.match.best]/(1.4)…

— the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) 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.

…or (1.5):

— the context is an initialization by conversion function for direct reference binding (13.3.1.6) of a reference to function type, […]

Clearly, neither applies, hence the ambiguity. A possible way to disambiguate:

operator CL2&();
operator const CL2&() const; 

Demo; Here, the former overload's initial standard conversion sequence of the implicit object argument is better as per [over.ics.rank]/(3.2.6), which is decisive by [over.match.best]/(1.3).