Operator Overloading: Member Function Vs. Non-Member Function

Operator overloading : member function vs. non-member function?

If you define your operator overloaded function as member function, then the compiler translates expressions like s1 + s2 into s1.operator+(s2). That means, the operator overloaded member function gets invoked on the first operand. That is how member functions work!

But what if the first operand is not a class? There's a major problem if we want to overload an operator where the first operand is not a class type, rather say double. So you cannot write like this 10.0 + s2. However, you can write operator overloaded member function for expressions like s1 + 10.0.

To solve this ordering problem, we define operator overloaded function as friend IF it needs to access private members. Make it friend ONLY when it needs to access private members. Otherwise simply make it non-friend non-member function to improve encapsulation!

class Sample
{
public:
Sample operator + (const Sample& op2); //works with s1 + s2
Sample operator + (double op2); //works with s1 + 10.0

//Make it `friend` only when it needs to access private members.
//Otherwise simply make it **non-friend non-member** function.
friend Sample operator + (double op1, const Sample& op2); //works with 10.0 + s2
}

Read these :

A slight problem of ordering in operands

How Non-Member Functions Improve Encapsulation

Operator overload: Member vs. non-member when only same type objects can be involved

Because operator== has symmetric semantics for its LHS and RHS argument, the recommended approach is to always implement it as a non-member in terms of the public interface of its operands (or if private data is required, to declare it as a friend inside the class).

So

class Bla
{
public:
// complete interface to data required for comparison
auto first();
auto second();
// ... more
private:
// data goes here
};

bool operator==(Bla const& L, Bla const& R)
{
return
std::forward_as_tuple(L.first(), L.second() /*, ... */) ==
std::forward_as_tuple(R.first(), R.second() /*, ... */)
;
}

This way, implicit conversion to Bla are considered for both the L and R arguments (I'm not saying implicit conversions are a good idea, but if you have those, it's better to avoid surprises where they are only considered for the RHS argument).

friend vs member functions in Operator Overloading C++

The choice isn't "member or friend" but "member or non-member".

(Friendship is frequently overused, and usually taught much too early in schools.)

This is because you can always add a public member function that a free function can call.

For instance:

class A
{
public:
explicit A(int y) : x(y) {}
A plus(const A& y) const { return A{x + y.x}; }
private:
int x;
};

A operator+(const A& lhs, const A& rhs) { return lhs.plus(rhs); }

As for how to choose: if the operator doesn't take an instance of the class as its left-hand operand, it must be a free function, otherwise it's pretty much a matter of personal taste (or coding standards if you're not alone).

Example:

// Can't be a member because the int is on the left.
A operator+ (int x, const A& a) { return A{x} + a; }

For operators that have a corresponding mutating operator (like + and +=), it's common to do the mutating operator as a member and the other as a non-member:

class B
{
public:
explicit B(int y) : x(y) {}
B& operator+= (const B& y) { x += y.x; return *this; }
private:
int x;
};

B operator+(B lhs, const B& rhs) { return lhs += rhs; }

but you can spell this out too, of course:

class C
{
public:
explicit C(int y) : x(y) {}
C& add(const C& y) { x += y.x; return *this; }
private:
int x;
};

C& operator+=(C& lhs, const C& rhs) { return lhs.add(rhs); }
C operator+(C lhs, const C& rhs) { return lhs += rhs; }

Why are relational operators overloaded as non-member function in STL string? [duplicate]

Overloading them as non-member functions allows the LHS of the operator to be converted to type std::string. For example, the following does not work should operator== be a member:

std::string name = "foo";
if ("foo" == name)
// ...

That's because "foo".operator==(name) is not a valid expression.

Why is function overloading between member functions and non-member functions not allowed?

You're mostly asking why so rather than saying "because the language says so", lets show an example where your suggestion causes everything to fail.

Suppose I have a handy class in my github repo:

struct HandyClass {
void display(short c) {
std::cout << c;
}
void doStuff() {
display(3);
}
};

And then some other developer in a different github repro makes a handy set of display functions:

void display(double v) {
showWindowsPopup("Your score was %f", v);
}
void display(int v) {
showWindowsPopup("Your score was %d", v);
}

You download both repros, and suddenly, HandyClass doesn't work right anymore:

#include "displays.h"
#include "handyclass.h"

int main() {
HandyClass a;
a.doStuff(); //Why does this show a windows popup!?!?
}

Because you included the display headers first, then display(3) matched to ::display(int) instead of ::HandyClass::display(short), because 3 is an int. And much sadness occurs.

But with the official C++ lookup rules, this doesn't happen. Since my class has display functions, it will ignore functions outside the class, to prevent mistakes, so that HandyClass always does the same thing for everyone.

Overload operators as member function or non-member (friend) function?

Each operator has its own considerations. For example, the << operator (when used for stream output, not bit shifting) gets an ostream as its first parameter, so it can't be a member of your class. If you're implementing the addition operator, you'll probably want to benefit from automatic type conversions on both sides, therefore you'll go with a non-member as well, etc...

As for allowing specialization through inheritance, a common pattern is to implement a non-member operator in terms of a virtual member function (e.g. operator<< calls a virtual function print() on the object being passed).

Is there any reason to not overload operator== as member, when only comparing to other object of the class?

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.

operator overloading and non-member functions c++

Here is the addition operator outside of the class:

Complex operator+(const Complex& lhs, const Complex& rhs) {
//implement the math to add the two
return Complex(lhs.aGetValue() + rhs.aGetValue(),
lhs.bGetValue() + rhs.bGetValue());
}

Of course you will need to declare aGetValue() and bGetValue() as const:

double aGetValue() const {return a;}
double bGetValue() const {return b;}

Member function vs Operator overloading

If an operator maps directly to the meaning of the function, then it's recommended to use the operator, e.g.

  • == instead of equals because it compares equality,
  • + instead of add if it's adding e.g. mathematical vectors,
  • but not + instead of add if it's e.g. appending to a list.


Related Topics



Leave a reply



Submit