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:
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
?
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.
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.