c++enumsoperator-overloadingc++20spaceship-operator

overwrite c++20 spaceship operator for enum class


I'm struggling with providing the new spaceship-operator for a enum class. Lets take the following example:

#include <cstdio>
#include <iostream>
#include <compare>
#include <cstdint>

enum class Animals : uint8_t
{
  Bird = 27, //those values are just for making a point
  Tiger = 5,
  Ant = 100,
  Snake = 45,
  Wale = 17
};

//auto operator<=(const Animals& lhs, const Animals& rhs) = delete;
//auto operator>=(const Animals& lhs, const Animals& rhs) = delete;
//auto operator<(const Animals& lhs, const Animals& rhs) = delete;
//auto operator>(const Animals& lhs, const Animals& rhs) = delete;

auto operator<=>(const Animals& lhs, const Animals& rhs)
{
    std::cout << "comparing via overloaded <=> operator\n";
  //order the animals by their size in real life
  // Ant < Bird < Snake < Tiger < Wale
  //for this MVCE only Ant < Tiger is in here:
  if(lhs == Animals::Ant && rhs == Animals::Tiger)
    return -1;
  return 0; //all unimplemented ones are treated as equal
}

int main(void)
{
  if(Animals::Ant < Animals::Tiger)
    std::puts("success (do I see the prompt of the overloaded operator?)");
  else
    std::puts("seems to use uint8_t comparison instead");

  return 0;
  
}

But obviously I'm getting something wrong in here, as my main() still tells me, that Ants are bigger than Tigers. As you can see I tried to explicitly delete the default comparison-operators, to force the compiler to use my custom-spaceship-one, but without success.

When I explicit call auto result = Animals::Ant <=> Animals::Tiger I get an ambiguous overload for 'operator<=>' (operand types are 'Animals' and 'Animals'). But that seems to be related to my operators signature (using const Animals instead).

Is it possible to overwrite the operator for my Enum (without interfering with the operators for its basic type "uint8_t"?


Solution

  • There are two things wrong with your operator<=>:

    1. it needs to take the enums by value, not by reference-to-const, in order to the suppress the built-in candidate
    2. it needs to return one of the comparison categories, not int. In this case probably std::weak_ordering is right.

    That is:

    constexpr auto operator<=>(Animals lhs, Animals rhs) -> std::weak_ordering
    {
      //order the animals by their size in real life
      // Ant < Bird < Snake < Tiger < Wale
      //for this MVCE only Ant < Tiger is in here:
      if(lhs == Animals::Ant && rhs == Animals::Tiger)
        return std::weak_ordering::less;
      return std::weak_ordering::equivalent;
    }
    

    That said, there is implementation divergence for how to handle rewrite candidates with <=>. clang and msvc implement what the rules probably should be, which is that our user-declared operator<=> suppresses all the built-in relational and three-way comaprison operators, so that Animals::Ant < Animals::Tiger invokes our operator<=>. But gcc implements what the rules technically actually literally say, which is that Animals::Ant <=> Animals::Tiger evaluates our operator but using < does not. There is a gcc bug report open for this (#105200), where one of the gcc developers points out the wording issue. This strikes me as a wording issue, rather than an actual design intent issue, so I'm opening a Core issue about this (#205).

    In order for this to work on gcc, you have to also go through and add these yourself (note: always by value):

    constexpr auto operator<(Animals lhs, Animals rhs) -> bool {
        return (lhs <=> rhs) < 0;
    }
    
    constexpr auto operator<=(Animals lhs, Animals rhs) -> bool {
        return (lhs <=> rhs) <= 0;
    }
    
    constexpr auto operator>(Animals lhs, Animals rhs) -> bool {
        return (lhs <=> rhs) > 0;
    }
    
    constexpr auto operator>=(Animals lhs, Animals rhs) -> bool {
        return (lhs <=> rhs) >= 0;
    }