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&);
};
- The most canonical duplicate What are the basic rules and idioms for operator overloading? says "overload binary operators as non-member" as rule of a thumb.
- Operator overloading : member function vs. non-member function? gives example mentioned above - if you were to use this operator with instance of another class/primitive type...
- CppCoreGuidelines has a vague explanation "If you use member functions, you need two", which I assume applies to comparing with object of different type.
- Why should operator< be non-member function? mentions that "non-member functions play better with implicit conversion", but it seems again the case of left-hand operand not being the instance of the class.
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?
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.
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.