c++c++20spaceship-operatordefaulted-functions

Defaulting three way comparison operator in recursive data structures


I'd like to use a defaulted three-way comparison operator in a recursive data structure, like this:

struct MyStruct {
  int i;
  std::vector<MyStruct> v;
  std::partial_ordering operator<=>(const MyStruct &a) const = default;
};

// Fails: can't infer comparison result type
static_assert(std::same_as<std::compare_three_way_result_t<MyStruct>,
          std::partial_ordering>);

Unfortunately, there seems to be a circular dependency, because the defaulted <=> operator depends on the comparison categories of integer comparison and std::vector, while the vector comparison category depends on the comparison category if its value_type, namely MyStruct. The fact that I've explicitly declared the comparison type to be std::partial_ordering doesn't seem to help, even though it should. In particular, it does work if I implement the function by hand:

struct MyStruct {
  int i;
  std::vector<MyStruct> v;
  std::partial_ordering operator<=>(const MyStruct &other) const {
    if (auto r = i <=> other.i; r != 0)
      return r;
    return v <=> other.v;
  }
};

// OK
static_assert(std::same_as<std::compare_three_way_result_t<MyStruct>,
          std::partial_ordering>);

My questions:

  1. Is there any way to short-circuit this problem? The spec prohibits overloading std::compare_three_way_result, but is there some other way to affect vector or std::lexicographical_compare_three_way?

  2. Is there any way, using concepts or requires clauses, to detect an incomplete type? As an alternative, I could design my own vector-like container class that just defaults to std::partial_ordering when the type is incomplete.


Solution

  • If you indeed specify std::partial_ordering yourself (i.e. without auto), then you can define the comparison operator outside the class as an inline function. At that point, the class is complete.

    struct MyStruct {
      int i;
      std::vector<MyStruct> v;
      std::partial_ordering operator<=>(const MyStruct &) const;
    };
    
    inline std::partial_ordering MyStruct::operator<=>(const MyStruct &) const = default;
    

    I'm not sure your original code really is ill-formed either come to think of it (meaning = default; in the member specification is a complete class context already), unless there's a standard defect I missed.