Jack Brown
Jack Brown

Reputation: 73

C++17 operator==() and operator!=() code fails with C++20

I've got the following sample code:

#include <assert.h>

struct Base
{
    bool operator==(const Base& rhs) const
    {
        return this->equalTo(rhs);
    }
    virtual bool equalTo(const Base& rhs) const = 0;
};
inline bool operator!=(const Base& lhs, const Base& rhs)
{
    return !(lhs == rhs);
}

struct A : public Base
{
    int value = 0;
    bool operator==(const A& rhs) const
    {
        return (value == rhs.value);
    }
    virtual bool equalTo(const Base& rhs) const
    {
        auto a = dynamic_cast<const A*>(&rhs);
        return (a != nullptr) ? *this == *a : false;
    }
};

class A_1 : public A
{
    virtual bool equalTo(const Base& rhs) const
    {
        auto a_1 = dynamic_cast<const A_1*>(&rhs);
        return (a_1 != nullptr) ? *this == *a_1 : false;
    }
};

int main()
{
    A_1 a_1;
    a_1.value = 1;

    // Make sure different types aren't equal
    A a;
    a.value = 1;
    assert(a_1 != a);
}

When I compile with C++17, everything is fine (no assert, as desired). Building the same code with C++20 causes the assert to fire.

How can I get this existing code to work when compiling with C++20? If I crank up the warnings, with C++20, I get 'operator !=': unreferenced inline function has been removed; I suspect this is all somehow related to operator<=>().

Is this really a known/desired breaking change from C++17 to C++20?

Upvotes: 2

Views: 576

Answers (1)

Barry
Barry

Reputation: 302852

In C++17, the line

assert(a_1 != a);

Invokes operator!=(const Base&, const Base&), because of course, it's the only candidate. That then invokes a_1->equalTo(a), which tries to downcast a to an A_1, which in your logic gives you false. So a_1 != a evaluates as true.


In C++20, we now additionally consider rewritten candidates in terms of ==. So now we have three candidates:

  1. Base::operator==(const Base&) const (rewritten)
  2. A::operator==(const A&) const (rewritten)
  3. bool operator!=(const Base&, const Base&)

Of these, (2) is the best candidate, since it's a better match for the two parameters. So a_1 != a evaluates as !((const A&)a_1 == a), which gives you a different answer.


However, your operators are fundamentally broken. Even in C++17, we had the scenario that a_1 != a evaluates as true while a != a_1 evaluates as false. This is basically the inherent problem of trying to do dynamic equality like this: this->equalTo(rhs) and rhs.equalTo(*this) might actually do different things. So this issue is less of a C++20 comparisons issue and more of a fundamental design issue.

Upvotes: 10

Related Questions