Home > Net >  operator==() code compiles w/C 17 but not C 20
operator==() code compiles w/C 17 but not C 20

Time:11-16

This code snippet

#include <stdlib.h>

struct Base { };
template<typename T>
inline bool operator==(const T&, const Base&)
{
    return true;
}
template<typename T>
inline bool operator!=(const T& lhs, const Base& rhs)
{
    return !(lhs == rhs);
}

struct A : public Base
{
    bool operator==(const A&) const { return true; }
};

struct A_1 final : public A { };

int main()
{
    const A a;
    const A_1 a_1;
    if (a_1 != a) {}

    return EXIT_SUCCESS;
}

Compiles without errors in C 17 (Visual Studio 2022). (See C 17 operator==() and operator!=() code fails with C 20 for a more detailed example; note in that case the code compiles.)

Trying to build the same code with C 20 generates three compiler errors:

error C2666: 'operator !=': 3 overloads have similar conversions
message : could be 'bool A::operator ==(const A &) const' [rewritten expression '!(x == y)']
message : or 'bool A::operator ==(const A &) const' [synthesized expression '!(y == x)']
message : or       'bool operator !=<A_1>(const T &,const Base &)'

Yes, I understand that C 20 will synthesize operator!= and the like ... but shouldn't existing C 17 code still compile with C 20?

How can I fix things so that the same code compiles with both C 17 and C 20 and generates identical results?

Making changes to derived class could be difficult as that code could be elsewhere (this is actually library code); it would be much preferred to make changes to Base.

CodePudding user response:

This is because all of the functions are now candidate, and before they weren't.

This means that suddently, the operator== in A is a candidate for a_1 != a. The compiler is now allowed to invert the arguments, change a == b to !(a != b) and vice versa, and even change the order to b == a.

The code result in an ambiguous call since bool operator==(const A&); in A supports a_1 != a, by inverting the operation to !(a_1 == a) and changing parameter order to finally have !(a == a_1) which is a candidate.

There are multiple solution to this.

One is to simply make one candidate better by inheriting the function:

struct A_1 final : public A { using A::operator==; };

The other would be to restraint the operator to work only with A:

struct A : public Base
{
    friend bool operator==(std::same_as<A> auto const&, std::same_as<A> auto const&) { return true; }
};

Another solution not requirering changing A or A_1 would be to add an overload that is always an ideal candidate inside Base as a friend function.

The whole code becomes this in C 20:

struct Base {
    friend bool operator==(std::derived_from<Base> auto const&, std::derived_from<Base> auto const&) { return true; }
};

struct A : public Base {};
struct A_1 final : public A {};

You can remove the template function in the global namespace, and you can also remove the function in the derived classes too.

You are not required to remove the function in A, but it won't be call and the resulting code will be quite surprising still.


However, note that your code was broken before.

This is the compiler output for code using the operator== in C 17:

int main()
{
    const A a;
    const A_1 a_1;
    if (!(a_1 == a)) {}

    return EXIT_SUCCESS;
}

Compiler output:

<source>: In function 'int main()':
<source>:26:15: error: ambiguous overload for 'operator==' (operand types are 'const A_1' and 'const A')
   26 |     if (!(a_1 == a)) {}
      |           ~~~ ^~ ~
      |           |      |
      |           |      const A
      |           const A_1
<source>:17:10: note: candidate: 'bool A::operator==(const A&) const'
   17 |     bool operator==(const A&) const { return true; }
      |          ^~~~~~~~
<source>:5:13: note: candidate: 'bool operator==(const T&, const Base&) [with T = A_1]'
    5 | inline bool operator==(const T&, const Base&)
      |             ^~~~~~~~
ASM generation compiler returned: 1

C 20 will just accept less code that has a surprising behaviour.

CodePudding user response:

C extends overload resolution for relational operator to include swapped arguments, which is in the diagnostic message:

  • GCC marks the candidate "(reversed)" and
  • Clang mentions (with reverser paramater order)

So you need fewer operators. In fact, you could consider overloading / defaulting the three-way comparison operator (<=>) instead.

CodePudding user response:

It's fine if you remove A::operator==.

There's really no point to A::operator==, as the template is saying that everything is equal to a Base.

but shouldn't existing C 17 code still compile with C 20?

The committee works hard to minimise breaking changes, but they don't promise there will be none. In this case it was felt that having == be an equivalence relation was more important than maintaining existing behaviour. As the linked question notes, polymorphic equality testing is often a source of bugs.

  • Related