Why is implicit conversion not ambiguous for non-primitive types?

According to [over.binary]/1

Thus, for any binary operator @, x@y can be interpreted as either x.operator@(y) or operator@(x,y).

According to this rule, in the case of e == f, the compiler can only interpret it as e.operator==(f), not as f.operator==(e). So there is no ambiguity; the operator== you defined as a member of Bar is simply not a candidate for overload resolution.

In the case of a == b and c == d, the built-in candidate operator==(int, int) (see [over.built]/13) competes with the operator== defined as a member of Foo<T>.


Operator overloads implemented as member functions don't allow for implicit conversion of their left-hand operand, which is the object on which they are called.

It always helps to write out the explicit call of an operator overload to better understand exactly what it does:

Foo<Bar> e (Bar{true});
Bar f = {false};

// Pretty explicit: call the member function Foo<Bar>::operator==
if(e.operator ==(f)) { /* ... */ }

This can't be confused with the comparison operator in Bar, because it would require an implicit conversion of the left-hand side, which is impossible.

You can trigger an ambiguity similar to the ones you see with the built-in types when you define Bar and its comparison operator like this:

struct Bar { bool m; };

// A free function allows conversion, this will be ambiguous:
bool operator==(const Bar&, const Bar&)
{
   return false;
}

This is nicely demonstrated and explained in Scott Meyers's Effective C++, Item 24.