Grzegorz Szpetkowski Grzegorz Szpetkowski - 1 month ago 12
C++ Question

Calling function with explicit user defined cast operator is ambigous

I encounter an issue, that is hard to explain for me. Here is the minimal, reproducible code, that is failing on both GCC 6.2 and Clang 3.9:

class T2;
class T1
{
int field1;
public:
T1(int pf) : field1(pf) {}
operator T2();
operator int() { return field1; }
};

class T2
{
int field2;
public:
T2(int pf) : field2(pf) {}
};

T1::operator T2() { return T2(field1); }

void foo(T2 pt) {}

int main()
{
T1 obj1(1);
T2 obj2(2);

foo((T2) obj1); // ambiguous conversion for C-style cast from 'T1' to 'T2'
foo(T2(obj1)); // ambiguous conversion for functional-style cast from 'T1' to 'T2'
foo(static_cast<T2>(obj1)); // ambiguous conversion for static_cast from 'T1' to 'T2'

}


Note that I didn't write the specific constructor to convert from T1 to T2, so I guess it should be all clear to the compiler, that the only way is to use user defined cast operator.

The curious fact is that when I comment out a seemingly unrelated cast operator:

// operator int() { return field1; }


then the code compiles hassle-free. What is the reason for that?

Answer

(T2) obj1 means exactly the same thing as T2(obj1) (or static_cast<T2>(obj1) in this case), but maybe it'll be easier to reason about this constructor-like syntax.

With the code as-is, there are two options:

  • construct T2 with an int, obtained by the user-defined conversion operator to int
  • construct T2 from T2 obtained by the user-defined conversion operator to T2

As per N4140:

If there is exactly one viable function that is a better function than all other viable functions, then it is the one selected by overload resolution; otherwise the call is ill-formed

Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:

  • User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 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 of U1 is better than the second standard conversion sequence of U2.

Since this doesn't apply, neither conversion is better than the other.