Three-way operator <=> return struct with implicit conversion function

Viewed 174

Consider the following useless code:

struct S{
  constexpr operator int() const { return 0; }
  constexpr auto operator<=>(S) const { return *this; }
};

static_assert(S{} <= S{});

Clang and MSVC accept this code but GCC rejects it with an error message:

error: no match for 'operator<=' (operand types are 'S' and 'int')

Which compiler is right? How operator<= is synthesized from operator<=>?

2 Answers

From [over.match.oper] (3.4.1 and 8):

For the relational ([expr.rel]) operators, the rewritten candidates include all non-rewritten candidates for the expression x <=> y.

and

If a rewritten operator<=> candidate is selected by overload resolution for an operator @, x @ y is interpreted as [...] (x <=> y) @ 0 [...], using the selected rewritten operator<=> candidate. Rewritten candidates for the operator @ are not considered in the context of the resulting expression.

So for the expression S{} <= S{} the selected operator will be S::operator<=>(S) const and the expression will be rewritten as (S{} <=> S{}) <= 0. In the rewritten expression the types of the operands are S and int, for which the built-in operator<=(int, int) will be selected. So ultimately the expression (after converting S to an int) will result in 0 <= 0, which is true.

In conclusion Clang and MSVC are right in this case, and GCC seems to fail in interpreting (S{} <=> S{}) <= 0 as a call to the built-in operator (notice the error message reading operand types are 'S' and 'int'). If you change the condition in the static_assert to be the rewritten expression (S{} <=> S{}) <= 0, then all three compilers accept it.

C++20 support in GCC is still experimental, so while it does support the three-way operator, your static_assert is failing because the other compilers are auto inferring the <= operator from the <=> operator, while GCC seems to be more pedantic in its interpretation of the standard, and since you do not have the <= operator directly, the compiler is then emitting a compile time error because it can't find the <= operator.

If you add the <= operator, the code works, example:

struct S{
  constexpr operator int() const { return 0; }
  constexpr auto operator<=>(S) const { return *this; }
  constexpr bool operator<=(S) { return true; }
};

static_assert(S{} <= S{});

Additionally, if you change your assert to be the three way operator, the test fails on all compilers, example:

struct S{
  constexpr operator int() const { return 0; }
  constexpr auto operator<=>(S) const { return *this; }
};

static_assert(S{} <=> S{});

Furthermore, since the three-way operator is expected to essentially return a negative, zero, or positive value (really returning an ordering), returning *this is likely converting the value to something that Clang and MSVC interpret as a true value for the assert, while GCC could be converting it to a false value, and thus the assert fails.

If you change the return type to any negative value (even -0) or a zero value, the assert passes on all compilers, additionally if you change the value to any positive value above 0 the assert fails on all compilers.

You could change the three-way operator to cast *this to an int which would call operator int and return 0, which would then cause the assert to pass, example:

constexpr auto operator<=>(S) const { return static_cast<int>(*this); }

So to answer directly:

Which compiler is right?

In my experience with GCC, it tends to interpret the language very pedantically and err on the side of caution when it comes to a language specification that might be ambiguous in the face of an odd code snippet (like yours).

To that end, the other compilers might be too loose in their interpretation of the language or GCC might be too strict in this particular case.

Either way, even though this code is "useless," anyone who is running into something like this who is targeting all 3 compilers should probably try to be as pedantic as possible with this type of code, though that might necessarily defeat the purpose of the code in this case, unfortunately.

Related