It's a lot like how
strcmp works. You implement the one operator, the spaceship operator, which is effectively 3-valued:
- a < b : (a <=> b) < 0
- a == b : (a <=> b) == 0
- a > b : (a <=> b) > 0
Then, using that single value, from a single operator call, you can determine (compiler generated):
- a == b : (a <=> b) == 0
- a != b : (a <=> b) != 0
- a < b : (a <=> b) < 0
- a > b : (a <=> b) > 0
- a <= b : (a <=> b) <= 0
- a >= b : (a <=> b) >= 0
So effectively you get all 6 comparison operators, but only have to implement one.
Previously they had
std::rel_ops. Given two operators
operator==, and
operator<, it would implement the other 4 for you. The problem with
std::rel_ops is it didn't play nicely with
ADT (Argument-Dependent Lookup), so sometimes those operators weren't found. Additionally, it added extra template operators to the global namespace, so all structs would end up getting the extra comparison operators, whether you wanted them or not.
The implementation of
std::rel_ops might use the following equivalences:
- a != b : !(a == b)
- a > b : (b < a)
- a <= b : !(a > b)
- a >= b : !(a < b)
Note: The above also only require one operator call.
A naive implementation might call two possibly expensive operators:
- a <= b : (a < b) || (a == b)
You want to avoid that.
Another benefit of the
<=> operator, is that it can distinguish between the various relational strengths, and only provide overloads that are appropriate. That's a whole other topic, but here are the orderings:
- strong ordering
- weak ordering
- partial ordering
- strong equality
- weak equality
Edit: In regards to a "point" (2D, 3D, etc.), there may be no natural way to do inequality comparisons. Hence the different orderings above, which may exclude auto generation of some comparison operators. In particular, if the return type of the spaceship operator is of the last 2 types, the compiler knows not to generate inequality comparisons.