57

Say I am working with a class:

class Foo{
public:
  std:string name;
  /*...*/
}/*end Foo*/

and I provide an overload for operator==

bool operator==(const Foo& fooObj, const std::string& strObj) {
    return (fooObj.name == strObj);
}

Do I also need to re-implement the same logic in reverse?

bool operator==(const std::string& strObj, const Foo& fooObj) {
    return (strObj == fooObj.name);
}
hehe3301
  • 746
  • 7
  • 17
  • 24
    By the way, in [tag:c++20] you will be able to define just one new operator and the compiler will do the juggling. It's `operator <=>`. – StoryTeller - Unslander Monica Nov 14 '18 at 12:29
  • 4
    related [All you always dreamed to know about operator overloading but never cared to ask](https://stackoverflow.com/q/4421706/5470596). – YSC Nov 14 '18 at 12:33
  • 3
    The bigger question, IMO, is whether the concept of equality between a `Foo` and a string is a valid concept or an inherent category error. A thing is not equivalent to its name, and the idea that it is is arguably part of what confuses people so much about pointers and references. – cHao Nov 14 '18 at 15:03
  • @cHao in my actual case there is an equivalence between name and "the thing" – hehe3301 Nov 14 '18 at 15:43
  • Implement the first comparison operator as a member of `Foo`, **not** as a global freestanding function. The second operator must simply be implemented as `return objB == objA` – Happy Green Kid Naps Nov 14 '18 at 16:17
  • 1
    @StoryTeller - I must have missed the bit where `operator<=>` will reorder arguments of different types (your first comment) - can you point to the section that specifies that? – Toby Speight Nov 14 '18 at 17:04
  • 3
    @TobySpeight - http://eel.is/c++draft/over.match.oper#3.4 – StoryTeller - Unslander Monica Nov 14 '18 at 17:13
  • Thanks @StoryT - I was looking in the `<=>` proposal instead. – Toby Speight Nov 14 '18 at 17:40

2 Answers2

77

(C++20 onward)

With the acceptance of p1185 into C++20, you don't need to provide more than one overload. The paper made these changes (among others) to the standard:

[over.match.oper]

3.4 - [...] For the != operator ([expr.eq]), the rewritten candidates include all member, non-member, and built-in candidates for the operator == for which the rewritten expression (x == y) is well-formed when contextually converted to bool using that operator ==. For the equality 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 (y == x) is well-formed when contextually converted to bool using that operator ==. [ Note: A candidate synthesized from a member candidate has its implicit object parameter as the second parameter, thus implicit conversions are considered for the first, but not for the second, parameter. — end note ] [...]

8 [...] If a rewritten candidate is selected by overload resolution for a != operator, x != y is interpreted as (y == x) ? false : true if the selected candidate is a synthesized candidate with reversed order of parameters, or (x == y) ? false : true otherwise, using the selected rewritten operator== candidate. If a rewritten candidate is selected by overload resolution for an == operator, x == y is interpreted as (y == x) ? true : false using the selected rewritten operator== candidate.

The above means that not only do you not need to provide the operator with the order of the operands reversed, you also get != for free! Furthermore, the operator== function can be a member if it makes sense. Though as the note in the first paragraph above says, it being a member or free function will affect implicit conversions so you still need to bear that in mind.


(Upto C++17)

You do if you want to support comparisons where the string is on the left and the Foo is on the right. An implementation won't reorder the arguments to an overloaded operator== to make it work.

But you can avoid repeating the implementation's logic, though. Assuming your operator should behave as expected:

inline bool operator==(const std::string& objA, const Foo& objB) {
    return objB == objA; // Reuse previously defined operator
}
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 8
    So theoretically one could implement different behaviour for `Foo==String` and `String==Foo` – hehe3301 Nov 14 '18 at 12:25
  • 55
    Yes, you could. But you definitely shouldn't. – Matthieu Brucher Nov 14 '18 at 12:25
  • @StoryTeller Do we get op!= for free with this as !(op==) or should that be implemented both directions as well? (expecting the answer no) – hehe3301 Nov 14 '18 at 13:46
  • 22
    @hehe3301 - I'm afraid all the overloads you want will need to be implemented explicitly. Although again, the body can be `!(lhs == rhs)`. That's why `operator <=>` generates so much excitement. Because it can cut down on a lot of boilerplate. – StoryTeller - Unslander Monica Nov 14 '18 at 13:50
  • @hehe3301: You can use [boost/operators.hpp](https://www.boost.org/doc/libs/1_68_0/libs/utility/operators.htm), which provides classes to inherit from which fill in operators based on your provided ones. For instance, if you inherit from `equality_comparable` and provide `operator==(T, U)`, it will supply `bool operator==(const U&, const T&)`, `bool operator!=(const U&, const T&)`, and `bool operator!=(const T&, const U&)` for you. – Nick Matteo Nov 15 '18 at 09:12
  • @StoryTeller does the same logic apply to `Foo::operator==(const std::string&)`? – Dev Null Mar 27 '19 at 06:28
  • @DevNull - Interesting that you should ask. There was a proposal merged into C++20 recently that may require a complete update to this answer (and will also address) your comment. I'll ping you once I edited it in. – StoryTeller - Unslander Monica Mar 27 '19 at 08:03
  • @DevNull - As for C++17, the same logic applies. You can't add your own overload to `std::string` so if you want the order reversed, you need to do it yourself. Either directly or indirectly via something like Boost. – StoryTeller - Unslander Monica Mar 27 '19 at 08:33
6

Yes, you do. Just like in lots of other languages, C++ takes sides and comparisons between two objects of different types will lead to calls to two different comparison operators depending on the order.

Of course, you want them to be consistent and not surprising, so the second should be defined in terms of the first.

Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
  • 12
    Re: "Just like in other languages": Overloading works sufficiently differently from one language to the next that I don't think this sort of sweeping statement is helpful. – ruakh Nov 14 '18 at 21:02