Why can I invoke == with a defaulted <=> but not a user-provided one?

Viewed 168
#include <compare>

struct A
{
    int n;
    auto operator <=>(const A&) const noexcept = default;
};

struct B
{
    int n;
    auto operator <=>(const B& rhs) const noexcept
    {
        return n <=> rhs.n;
    }
};

int main()
{
    A{} == A{}; // ok
    B{} == B{}; // error: invalid operands to binary expression
}

compiled with clang-10 as clang -std=c++20 -stdlib=libc++ main.cpp

Why does A{} == A{} work but not B{} == B{}?

1 Answers

In the original design of the spaceship operator, == is allowed to call <=>, but this is later disallowed due to efficiency concerns (<=> is generally an inefficient way to implement ==). operator<=>() = default is still defined to implicitly define operator==, which correctly calls == instead of <=> on members, for convenience. So what you want is this:

struct A {
    int n;
    auto operator<=>(const A& rhs) const noexcept = default;
};

// ^^^ basically expands to vvv

struct B {
    int n;
    bool operator==(const B& rhs) const noexcept
    {
        return n == rhs.n;
    }
    auto operator<=>(const B& rhs) const noexcept
    {
        return n <=> rhs.n;
    }
};

Note that you can independently default operator== while still providing a user-defined operator<=>:

struct B {
    int n;
    // note: return type for defaulted equality comparison operator
    //       must be 'bool', not 'auto'
    bool operator==(const B& rhs) const noexcept = default;
    auto operator<=>(const B& rhs) const noexcept
    {
        return n <=> rhs.n;
    }
};
Related