Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why must I provide 'operator ==' when 'operator <=>' is enough?

#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?

like image 305
xmllmx Avatar asked Jul 02 '21 07:07

xmllmx


People also ask

Why would you need to overload a comparison operator?

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 “==”, “!=

What is === operator in Javascript?

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.

How do you overload operator in C++?

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.

How do you overload an operator?

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.

What are the rules for operator overloading in C++?

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 ...

What is the use of + operator in C++?

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.

Why ‘+’ operator is overloaded?

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!

What are the limitations of operators in C++?

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.


Video Answer


2 Answers

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).
like image 135
paxdiablo Avatar answered Sep 30 '22 18:09

paxdiablo


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.

like image 32
eerorika Avatar answered Sep 30 '22 18:09

eerorika