#include <compare> struct A { int n; auto operator<=>(A const& other) const { if (n < other.n) { return std::strong_ordering::less; } else if (n > other.n) { return std::strong_ordering::greater; } else { return std::strong_ordering::equal; } } // compile error if the following code is commented out. // bool operator==(A const& other) const // { return n == other.n; } }; int main() { A{} == A{}; }
See online demo
Why must I provide operator ==
when operator <=>
is enough?
Operator overloading is a crucial concept in C++ that lets you achieve the functionality of the built-in operators while working with user-defined data types. Comparison operators in C++ are the ones that are there to compare two values with each other such as “==”, “!=
The strict equality operator ( === ) checks whether its two operands are equal, returning a Boolean result. Unlike the equality operator, the strict equality operator always considers operands of different types to be different.
Operator Overloading in Binary Operators Here, + is a binary operator that works on the operands num and 9 . When we overload the binary operator for user-defined types by using the code: obj3 = obj1 + obj2; The operator function is called using the obj1 object and obj2 is passed as an argument to the function.
Overloaded operators are just functions (but of a special type) with a special keyword operator followed by the symbol of the operator to be overloaded.
Rules for operator overloading. In C++, following are the general rules for operator overloading. 1) Only built-in operators can be overloaded. New operators can not be created. 2) Arity of the operators cannot be changed. 3) Precedence and associativity of the operators cannot be changed. 4) Overloaded operators cannot have default arguments ...
For example operator + is used to add two integers as well as join two strings and merge two lists. It is achievable because ‘+’ operator is overloaded by int class and str class.
It is achievable because ‘+’ operator is overloaded by int class and str class. You might have noticed that the same built-in operator or function shows different behavior for objects of different classes, this is called Operator Overloading . Attention geek!
2) Arity of the operators cannot be changed. 3) Precedence and associativity of the operators cannot be changed. 4) Overloaded operators cannot have default arguments except the function call operator () which can have default arguments.
Why must I provide
operator==
whenoperator<=>
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:
a == b
can be either: a.operator==(b)
if available; orb.operator==(a)
if not.a != b
can be: ! a.operator==(b)
if availableThat 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)
.Why must I provide 'operator ==' when 'operator <=>' is enough?
Because it won't be used.
It will be enough if you were to use the defaulted one:
struct A { int n; auto operator<=>(A const& other) const = default; };
Basically, n == n
is potentially more efficient than (a <=> a) == std::strong_ordering::equal
and there are many cases where that is an option. When you provide a user defined <=>
, the language implementation cannot know whether latter could be substituted with the former, nor can it know whether latter is unnecessarily inefficient.
So, if you need a custom three way comparison, then you need a custom equality comparison. The example class doesn't need a custom three way comparison, so you should use the default one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With