c++language-lawyerc++20comparison-operatorsconst-correctness

(x != x) for default equality operator==


This program

struct A {
    int i = 0;
    constexpr operator int() const { return i; }
    constexpr operator int&() { return ++i; }
};

struct B {
    A a;
    bool operator==(const B &) const = default;
};

constexpr bool f() {
    B x;
    return x == x;
}

static_assert( f() ); // fails in GCC

fails static assertion in GCC compiler, meaning that x != x is true during constant evaluation, where operator== is generated by the compiler. Online demo: https://gcc.godbolt.org/z/4xjTnM54M

If one modifies struct B such that it inherits from A instead of having it as a field:

struct B : A {
    bool operator==(const B &) const = default;
};

constexpr bool f() {
    B x;
    return x == x;
}

static_assert( f() ); // fails in Clang

then the program succeeds in GCC but fails in Clang. Online demo: https://gcc.godbolt.org/z/5hYob3K66

Are both programs well formed and f() must be evaluated to true?


Solution

  • This is just a bug in GCC/Clang.

    A defaulted equality operator is specified to perform a memberwise equality comparison of its operands' base class subobjects, followed by non-static data members, in declaration order ([class.eq]/3).

    The comparison is performed as-if by a == b, where a and b are lvalues denoting the corresponding subobjects of the operands. [class.compare.default]/5 explicitly says that such lvalues are

    formed by a sequence of derived-to-base conversions, class member access expressions, and array subscript expressions applied to x

    where x is the respective parameter of the defaulted equality operator, which is necessarily const-qualified.

    So, following the normal rules of the language, a and b should also inherit this const-qualification (except when referring to mutable data members), but for some reason this does not happen in GCC for non-static data members, whereas Clang coincidentally exhibits the same buggy behavior with base class subobjects.

    In the OP example, this bug leads to A::operator int&() being selected over A::operator int() const to perform the A->int conversion for the built-in operator==(int, int).