Search code examples
c++operator-overloadingc++20equality

operator== ambiguity in C++20


Why is this code ambiguous starting with C++20?

template <typename T>
struct base_widget {
    bool operator==(T const&) const;
};

struct gadget : base_widget<gadget> {};

bool foo(gadget const& a, gadget const& b) {
    return a == b;
}

for MSVC that is an error:

error C2666: 'base_widget<gadget>::operator ==': overloaded functions have similar conversions  
    could be 'bool base_widget<gadget>::operator ==(const T &) const'
          with
          [
              T=gadget
          ]
     or 'bool base_widget<gadget>::operator ==(const T &) const' [synthesized expression 'y == x']
         with
         [
             T=gadget
         ]
    while trying to match the argument list '(const gadget, const gadget)'

for GCC it is a warning:

In function 'bool foo(const gadget&, const gadget&)':
warning: C++20 says that these are ambiguous, even though the second is reversed:
      |     return a == b;
      |                 ^
note: candidate 1: 'bool base_widget<T>::operator==(const T&) const [with T = gadget]'
      |     bool operator==(T const&) const;
      |          ^~~~~~~~
note: candidate 2: 'bool base_widget<T>::operator==(const T&) const [with T = gadget]' (reversed)

And also a warning for Clang:

warning: ISO C++20 considers use of overloaded operator '==' (with operand types 'const gadget' and 'const gadget') to be ambiguous despite there being a unique best viable function [-Wambiguous-reversed-operator]
      |     return a == b;
      |            ~ ^  ~
note: ambiguity is between a regular call to this operator and a call with the argument order reversed
      |     bool operator==(T const&) const;
      |  

I know that C++20 allows this:

Overload resolution/operators in expressions

[over.match.oper]

For the equality operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each non-rewritten candidate for the expression y == x.

But why is the synthesized reversed operator problematic in this case?

Making the operator== a friend fixes the issue:

template <typename T>
struct base_widget {
    friend bool operator==(T const&, T const&);
};

Why there is no issue with reversing in this case?


Solution

  • To demonstrate the problem more clearly, I created a simpler example: without templates and with non-member operator==:

    struct B{};
    struct D : B{};
    
    constexpr bool operator==(const B&, const D&)
    {
        return true;
    }
    static_assert( D{}==D{});
    

    clang gives the following warning:

    <source>:8:19: warning: ISO C++20 considers use of overloaded operator '==' (with operand types 'D' and 'D') to be ambiguous despite there being a unique best viable function [-Wambiguous-reversed-operator]
        8 | static_assert( D{}==D{});
          |                ~~~^ ~~~
    <source>:4:16: note: ambiguity is between a regular call to this operator and a call with the argument order reversed
        4 | constexpr bool operator==(const B&, const D&)
      |              
    

    In other words, the compiler cannot decide whether to convert the 1st or 2nd D{} argument to const B&.