5

I've been combing through the internet to find an answer, but I couldn't find any. The only reasons given seems to be relevant for comparing with objects of different type (e.g. MyClass == int). But the most common use case is comparing a class instance to another instance of the same class, not to any unrelated type.

In other words, I do understand the problems with:

struct A {
    bool operator==(int b);
};

But I cannot find any good reason to not use member function in the most obvious use-case:

struct A {
    bool operator==(const A&);
};

On the other hand, member overload seems to have a couple positive sides:

  • No need to befriend the function or to provide getters for members
  • It is always available to class users (although this might be the downside also)
  • No problems with lookup (which seems to be common in our GoogleTests for some reason)

Is overloading operator== as non-member function just a convention to keep it the same with possible overloads in other classes? Or are there any other reasons to make it non-member?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
  • Unrelated, but "problems with lookup" sounds like you are losing a battle against ADL. Are you defining non-member functions that are part of the class interface in the same namespace as the class (as one should)? – Max Langhof Sep 13 '19 at 16:16
  • [See cpp guideline C4](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c4-make-a-function-a-member-only-if-it-needs-direct-access-to-the-representation-of-a-class). – Marek R Sep 13 '19 at 16:20
  • A downside with "_Make a function a member only if it needs direct access to the representation of a class_" is that as a user of a class, I would need to search for loose helper functions as well as class member functions. Code completion, typing `classname.` and waiting for my options if I've forgotten the interface goes out the window etc. – Ted Lyngmo Sep 13 '19 at 16:25
  • @TedLyngmo - The API of a class also belongs in the same header file under the same namespace, or in a well associated header. No damage to code completion. Also, let's not forget the open-closed principle. A class that can be extended easily by free functions is surely inline with that one. – StoryTeller - Unslander Monica Sep 13 '19 at 16:27
  • @StoryTeller "_No damage to code completion_" - I must upgrade my `vim`:-) – Ted Lyngmo Sep 13 '19 at 16:30

2 Answers2

6

The arguments for using non-member operator overload for symmetric operations are based on style and consistency. They are not very strong arguments 1. Non-member overloads are typically preferred because a weak argument is still a little bit better than no argument at all.

Your arguments for member operator overload don't seem to be any stronger. Consider following:

No need to befriend the function

On the other hand, if you use a non-member overload, then you don't have need to declare a member function. Is befriending the non-member somehow worse?

or to provide getters for members

There is no need for that if you befriend the overload.

It is always available to class users (although this might be the downside also)

It is unclear how this differs from the non-member overloads. Are they also not always available to the class users?

No problems with lookup (which seems to be common in our GoogleTests for some reason)

Are there lookup problems with non-member overloads? Could you demonstrate the problem with an example, and show how the problem is solved by using a member overload instead?

If it does solve the problem, then you can of course use that. Just because some guidelines recommend that you prefer one alternative as a rule of thumb, doesn't mean that is the only alternative to be used in all use cases.


1 Although, see answer https://stackoverflow.com/a/57927564/2079303 which is arguably stronger than just stylistic.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Problem with lookup might happen with misplaced functions (not in same namespace than the class). Member cannot be outside the class :) – Jarod42 Sep 13 '19 at 17:45
6

Well, in your question, you did forget to const qualify the member function, and it would be harder to write bool operator==(A&, const A&); by accident.

If you had an implicit constructor, a class with implicit conversion to A or base class with an operator== with higher priority, the member function wouldn't work if it was on the left, but would if it was on the right. Although most of the time implicit conversions are a bad idea, inheritance could reasonably lead to a problem.

struct A {
    A(int);  // Implicit constructor
    A();

    bool operator==(const A&) const;
};

struct B : A {
    bool operator==(const B&) const;
};

void test() {
    A a;
    B b;
    // 1 == a;  // Doesn't work
    a == 1;
    // b == a;  // Doesn't work; Picks `B::operator==(const B&) const;`
    a == b;  // Picks `A::operator==(const A&) const`, converting `b` to an `A&`.
    // Equality is no longer symmetric as expected
}

In the future, with the C++20 operator<=>, you will most likely always implement this as a member function (namely as auto operator<=>(const T&) const = default;), so we know that this guideline may change.

Artyer
  • 31,034
  • 3
  • 47
  • 75