Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::tuple call operator <=> twice?

The following code calls operator <=> twice, with arguments reversed. But why?

GCC 10.2 and clang 12 both seem to be using libstdc++-10, whose <tuple> does provide operator <=>, so it doesn’t appear to be a case of missing standard library support and my code has to be incorrect. How to fix it?

#include <tuple>
#include <compare>
#include <iostream>

struct X {
    int i;
    auto operator <=>(X const& other) const {
        std::cout << this << " <=> " << &other << std::endl;
        return i <=> other.i;
    }
};

int main() {
    std::tuple{X{42}} <=> std::tuple{X{42}};
}
like image 742
Roman Odaisky Avatar asked Feb 12 '21 16:02

Roman Odaisky


1 Answers

Short answer: You need to define operator== for X.

std::tuple compares elements via a synthesized three-way comparison that uses <=> only if the type satisfies std::three_way_comparable_with<T,U>. Ultimately, this requires std::three_way_comparable<X>, which requires an expositionary weakly-equality-comparable-with concept. As you might guess, this requires == to be valid.

The fix is a one-liner:

bool operator==(X const& other) const = default;

Now why is == required when <=> seems to do the job on its own here? I can only speculate, but it might be due to concepts being more "complete" than we're used to with only needing operator< for example. If a type is comparable with <=>, it should really also support equality.

As for why <=> does not cover == on its own unless defaulted, this is because of the performance pitfall for classes whose equality can short-circuit (such as vectors and strings) as well as any classes that contain such types. No indication would be given that equality compares every element instead of short-circuiting, so <=> does not handle equality unless it can guarantee that you'll avoid that pitfall (via defaulting <=>).

like image 187
chris Avatar answered Nov 19 '22 23:11

chris