Why must I provide operator==
when operator<=>
is enough?
Well, mainly because it's not enough :-)
Equality and ordering are different buckets when it comes time for C++ to rewrite your statements:
|
Equality |
Ordering |
---|
Primary |
== |
<=> |
Secondary |
!= |
<, >, <=, >= |
Primary operators have the ability to be reversed, and secondary operators have the ability to be rewritten in terms of their corresponding primary operator:
- reversing means that
a == b
can be either:
a.operator==(b)
if available; or
b.operator==(a)
if not.
- rewriting means that
a != b
can be:
! a.operator==(b)
if available
That last one could also be ! b.operator==(a)
if you have to rewrite and reverse it (I'm not entirely certain of that since my experience has mostly been with the same types being compared).
But the requirement that rewriting not take place by default across the equality/ordering boundary means that <=>
is not a rewrite candidate for ==
.
The reason why equality and ordering are separated like that can be found in this P1185 paper, from one of the many standards meetings that discussed this.
There are many scenarios where automatically implementing ==
in terms of <=>
could be quite inefficient. String, vector, array, or any other collections come to mind. You probably don't want to use <=>
to check the equality of the two strings:
"xxxxx(a billion other x's)"
; and
"xxxxx(a billion other x's)_and_a_bit_more"
.
That's because <=>
would have to process the entire strings to work out ordering and then check if the ordering was strong-equal.
But a simple length check upfront would tell you very quickly that they were unequal. This is the difference between O(n) time complexity, a billion or so comparisons, and O(1), a near-immediate result.
You can always default equality if you know it will be okay (or you're happy to live with any performance hit it may come with). But it was thought best not to have the compiler make that decision for you.
In more detail, consider the following complete program:
#include <iostream>
#include <compare>
class xyzzy {
public:
xyzzy(int data) : n(data) { }
auto operator<=>(xyzzy const &other) const {
// Could probably just use: 'return n <=> other.n;'
// but this is from the OPs actual code, so I didn't
// want to change it too much (formatting only).
if (n < other.n) return std::strong_ordering::less;
if (n > other.n) return std::strong_ordering::greater;
return std::strong_ordering::equal;
}
//auto operator==(xyzzy const &other) const {
// return n == other.n;
//}
//bool operator==(xyzzy const &) const = default;
private:
int n;
};
int main() {
xyzzy twisty(3);
xyzzy passages(3);
if (twisty < passages) std::cout << "less\n";
if (twisty == passages) std::cout << "equal\n";
}
It won't compile as-is since it needs an operator==
for the final statement. But you don't have to provide a real one (the first commented-out chunk), you can just tell it to use the default (the second). And, in this case, that's probably the correct decision as there's no real performance impact from using the default.
Keep in mind that you only need to provide an equality operator if you explicitly provide a three-way comparison operator (and you use ==
or !=
, of course). If you provide neither, C++ will give you both defaults.
And, even though you have to provide two functions (with one possibly being a defaulted one), it's still better than previously, where you had to explicitly provide them all, something like:
a == b
.
a < b
.
a != b
, defined as ! (a == b)
.
a > b
, defined as ! (a < b || a == b)
.
a <= b
, defined as a < b || a == b
.
a >= b
, defined as ! (a < b)
.
<=>
not include==
? I mean, if==
is provided, use it; if not, use<=>
instead? Why does the C++ standard not be designed in this way? – xmllmx