That particular aspect is a simple form of rewriting, reversing the operands. The primary operators ==
and <=>
can be reversed, the secondaries !=
, <
, >
, <=
, and >=
, can be rewritten in terms of the primaries.
The reversing aspect can be illustrated with a relatively simple example.
If you don't have a specific B::operator==(A)
to handle b == a
, you can use the reverse to do it instead: A::operator==(B)
. This makes sense because equality is a bi-directional relationship: (a == b) => (b == a)
.
Rewriting for secondary operators, on the other hand, involves using different operators. Consider a > b
. If you cannot locate a function to do that directly, such as A::operator>(B)
, the language will go looking for things like A::operator<=>(B)
then simply calculating the result from that.
That's a simplistic view of the process but it's one that most of my students seem to understand. If you want more details, it's covered in the [over.match.oper]
section of C++20, part of overload resolution (@
is a placeholder for the operator):
For the relational and equality operators, the rewritten candidates include all member, non-member, and built-in candidates for the operator <=>
for which the rewritten expression (x <=> y) @ 0
is well-formed using that operator<=>
.
For the relational, equality, and three-way comparison operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each member, non-member, and built-in candidate for the
operator <=>
for which the rewritten expression 0 @ (y <=> x)
is well-formed using that operator<=>
.
Hence gone are the days of having to provide a real operator==
and operator<
, then boiler-plating:
operator!= as ! operator==
operator> as ! (operator== || operator<)
operator<= as operator== || operator<
operator>= as ! operator<
Don't complain if I've gotten one or more of those wrong, that just illustrates my point on how much better C++20 is, since you now only have to provide a minimal set (most likely just operator<=>
plus whatever else you want for efficiency) and let the compiler look after it :-)
The question as to why one is being selected over the other can be discerned with this code:
#include <iostream>
struct B {};
struct A {
bool operator==(B const&) { std::cout << "1
"; return true; }
};
bool operator==(B const&, A const&) { std::cout << "2
"; return true; }
int main() {
auto b = B{}; auto a = A{};
b == a; // outputs: 1
(const B)b == a; // 1
b == (const A)a; // 2
(const B)b == (const A)a; // 2
}
The output of that indicates that it's the const
-ness of a
deciding which is the better candidate.
As an aside, you may want to have a look at this article, which offers a more in-depth look.