Given a simple class template with multiple implicit conversion functions (non-explicit constructor and conversion operator), as in the following example:
template<class T>
class Foo
{
private:
T m_value;
public:
Foo();
Foo(const T& value):
m_value(value)
{
}
operator T() const {
return m_value;
}
bool operator==(const Foo<T>& other) const {
return m_value == other.m_value;
}
};
struct Bar
{
bool m;
bool operator==(const Bar& other) const {
return false;
}
};
int main(int argc, char *argv[])
{
Foo<bool> a (true);
bool b = false;
if(a == b) {
// This is ambiguous
}
Foo<int> c (1);
int d = 2;
if(c == d) {
// This is ambiguous
}
Foo<Bar> e (Bar{true});
Bar f = {false};
if(e == f) {
// This is not ambiguous. Why?
}
}
The comparison operators involving primitive types (bool
, int
) are ambiguous, as expected - the compiler does not know whether it should use the conversion operator to convert the left-hand template class instance to a primitive type or use the conversion constructor to convert the right-hand primitive type to the expected class template instance.
However, the last comparison, involving a simple struct
, is not ambiguous. Why? Which conversion function will be used?
Tested with compiler msvc 15.9.7.
According to [over.binary]/1
Thus, for any binary operator
@
,x@y
can be interpreted as eitherx.operator@(y)
oroperator@(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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With