6
votes

Consider the following two overload operator<=> for S:

#include <compare>

struct S {};

int operator<=>(S, int) { return 0;  } #1
S   operator<=>(S,   S) { return {}; } #2

If I compare an object S with an int, the #1 will generate the right operators for me, so expression like S{} <= 0, 0 < S{} or 0 <=> S{} would be just fine.

But if I compare an object S with other object S:

S{} < S{};

Then this will be rewritten as (S{} <=> S{}) < 0. Since (S{} <=> S{}) will return an other S, we back to the origin problem: S compare with a int. At this time, we don't have operator<(S, int), so #1 would generate the right operator for me.

But surprisingly, none of the three compilers do this to me. GCC, Clang, and MSVC all reject S{} < S{} with the same error message:

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

This makes me frustrated. Since the #1 actually exists. Why the nested generation of the operator are not occurring here? What does the standard say? Is there a static constraint violation?

1
Your code is not legal in either case. <=> is required to return auto, std::strong_ordering, std::weak_ordering, std::partial_ordering, or bool. en.cppreference.com/w/cpp/language/default_comparisonsNathanOliver
Just change the return type of <=>(S,S) to auto, which conforms to the standard (as far as I can tell), and you still get the same behavior.cigien
@NathanOliver That's only for defaulted comparisons (and the bool there doesn't apply to <=>).Barry
See further down the linked page (from @NathanOliver) for the return type for custom comparators "...There are three available return types:..."Richard Critten
@RichardCritten I'm not sure that means those are the only possible return types. At least, I can't find the relevant wording constraining that in the standard.cigien

1 Answers

8
votes

This is ill-formed, although admittedly the error message is quite confusing.

The rule, from [over.match.oper]/8 is (emphasis mine):

If a rewritten operator<=> candidate is selected by overload resolution for an operator @, x @ y is interpreted as 0 @ (y <=> x) if the selected candidate is a synthesized candidate with reversed order of parameters, or (x <=> y) @ 0 otherwise, using the selected rewritten operator<=> candidate. Rewritten candidates for the operator @ are not considered in the context of the resulting expression.

The expression S{} < S{} is going to resolve to the rewritten candidate (S{} <=> S{}) < 0. The resulting expression will not consider rewritten candidates in its lookup. So when we do S{} < 0, that is going to look for just an operator<, and not also operator<=>. It can't find such a thing, so the expression is ill-formed.

<source>:8:14: error: no match for 'operator<' (operand types are 'S' and 'int')
    8 | auto x = S{} < S{};
      |          ~~~~^~~~~

In that sense, the error is literally true: there is no match for, specifically operator< with those operands. Although it would help if the error message had quite a bit more context explaining why it's looking for that (and I submitted a request to that effect in 99629).