c++comparisonstd-variantspaceship-operator

Letting compiler write operator==, <, > etc. via spaceship-operator


I'm trying my luck with the spaceship operator. Say I have a variant-like object (that I conveninently derive from std::variant with fixed template parameters) and I want to define operators ==, <, >, !=, etc... for easy comparison, I thought I could just spaceship-it according to the contained type and have the compiler define all operators by itself:

Demo

#include <compare>
#include <iostream>
#include <variant>
#include <string>

class JSON : public std::variant<std::monostate, double, std::string> {
public:
    using variant::variant;

    auto operator<=>(std::string str) const {
        return std::get<std::string>(*this) <=> str;
    }
    auto operator<=>(double d) const {
        return std::get<double>(*this) <=> d;
    }
};
  
int main()
{
    JSON myjson = 2.3;

    if (myjson == 2.3) {
        std::cout << "JSON was 2.3" << std::endl;
    }
}

Yet this apporach yields:

<source>: In function 'int main()':
<source>:22:16: error: no match for 'operator==' (operand types are 'JSON' and 'double')
   22 |     if (myjson == 2.3) {
      |         ~~~~~~ ^~ ~~~
      |         |         |
      |         JSON      double

I would have thought that the compiler now knows how to write operator== for my variant object in case rhs is a double or rhs is a string. In case the variant contains a different alternative, I'm fine with std::get throwing at me.


Solution

  • From Default comparisons (since C++20):

    Per the rules for any operator<=> overload, a defaulted <=> overload > will also allow the type to be compared with <, <=, >, and >=.

    If operator<=> is defaulted and operator== is not declared at all, then operator== is implicitly defaulted.

    As you can see in the link above, this is the only mention of an implicit default operator== based on operator<=>.

    In your case operator<=> is not defaulted, and so the above does not apply.
    Therefore you need to supply operator== (or in the general case have it itself defaulted if possible based on the operand types).

    Note:

    As @康桓瑋 commented, in your specific case you could actually defaulted operator<=>:

    auto operator<=>(const JSON& d) const = default;
    

    As explained above, it works because double is converted to JSON, so comparing two JSONs will invoke the variant's comparison method.