Overloading comparison operators for result types of three-way comparison operators

Viewed 158

In C++20 we got a new three-way comparison operator <=> , which typically returns std::strong_ordering or std::partial_ordering types. And if class A has operator <=>, then the comparison of its objects a1 < a2 is interpreted as (a1 <=> a2) < 0.

But can the user overload comparison operators, taking the first argument of type std::strong_ordering and the second argument accepting 0 literal? For example:

#include <compare>
#include <iostream>

struct A { 
    std::strong_ordering operator <=>(const A &) const = default; 
};

void operator < (std::strong_ordering, std::nullptr_t) {
    std::cout << "overloaded< ";
}

int main() { 
    A{} < A{}; // #1
    (A{} <=> A{}) < 0; //#2
}

Here both GCC and Clang call overloaded operator <, even without any warning about the presence of another operator in the standard library. Is it ok? Demo: https://gcc.godbolt.org/z/zEEP45Ezj

The behavior of MSVC is more interesting. In #1 it prints a weird error:

error C2088: '<': illegal for struct

In #2:

error C2593: 'operator <' is ambiguous
<source>(8): note: could be 'void operator <(std::strong_ordering,std::nullptr_t)'
C:/data/msvc/14.30.30423-Pre/include\compare(189): note: or       'bool std::operator <(const std::strong_ordering,std::_Literal_zero) noexcept' [found using argument-dependent lookup]
<source>(14): note: while trying to match the argument list '(std::strong_ordering, int)'

Personally I would prefer this behavior, but what is the right one here?

1 Answers

The standard doesn't say anything about the unspecified type in strong_ordering's comparisons, other than that it accepts the literal 0 exactly (and using anything else is undefined).

In particular, it doesn't specify the kind of implicit conversion sequence involved in converting the literal 0 to the parameter's type. If the implementation uses a class type for the parameter, then you get a user-defined conversion sequence that loses to your operator<'s standard conversion from 0 to nullptr_t; if it uses nullptr_t too, then it's ambiguous; if it uses int, then your overload loses.

Just...don't do it.

Related