Operator overloading — common interview cases
Scenario
You overload `operator+` as a member function of your `Vector2D` class. While `vec + 5` works perfectly, writing `5 + vec` throws a compiler error.
Why does addition suddenly care about left-to-right order?
Mental model
When an operator is a member function, the left side of the operator IS the object making the call (`vec.operator+(5)`). If the left side is an integer (`5.operator+(vec)`), it fails because integers don't have your class's methods.
Operator overloading allows custom classes to use standard operators like `+`, `==`, or `<<`. The primary pitfall is choosing whether to overload them as member functions or free (non-member) functions. For symmetric operators (like `+` or `*`), you should use non-member `friend` functions so that both the left and right operands are treated equally and can undergo implicit conversions.
Explanation
C++ allows you to overload operators to make custom classes behave like primitives. However, don't overload them to be 'cute' (e.g., using `+` to add an item to a database)—operators should stick to their conventional mathematical or logical meanings.
**Member vs. Non-Member Overloads:**
If you define `operator+` inside a class, the object on the left side of the `+` is the `this` object. This creates an asymmetry: `myObject + 5` is evaluated as `myObject.operator+(5)`, which works. But `5 + myObject` fails because `5` is a primitive and cannot invoke `operator+` on your class.
To achieve mathematical symmetry, binary operators (`+`, `-`, `*`, `==`) must be defined as free (non-member) functions. If they need to access private data, you declare them as `friend` functions inside the class. They take both operands as parameters (usually by `const&`), meaning `5 + myObject` perfectly matches `operator+(5, myObject)`.
**Return Types:**
Operators like `+` create a entirely new object without modifying the originals, so they must return by value. Operators like `+=` modify the existing object and return `*this` by reference (`Type&`).
Code examples
The bug: Asymmetric member operator
#include <iostream>
class Vector2D {
double x, y;
public:
Vector2D(double x, double y) : x(x), y(y) {}
// Member operator+
Vector2D operator+(double scalar) const {
return Vector2D(x + scalar, y + scalar);
}
};
int main() {
Vector2D v(1.0, 2.0);
Vector2D v1 = v + 5.0; // OK: v.operator+(5.0)
// Vector2D v2 = 5.0 + v; // ERROR: 5.0 is not a Vector2D!
return 0;
}
The member operator ties the addition strictly to the `Vector2D` object being on the left side. Commutative math breaks down.
The fix: Symmetric friend function
#include <iostream>
class Vector2D {
double x, y;
public:
Vector2D(double x, double y) : x(x), y(y) {}
// Friend free function: defined inside class for convenience, but acts as a global function
friend Vector2D operator+(const Vector2D& lhs, double scalar) {
return Vector2D(lhs.x + scalar, lhs.y + scalar);
}
// Reverse order overload ensures perfect symmetry
friend Vector2D operator+(double scalar, const Vector2D& rhs) {
return Vector2D(rhs.x + scalar, rhs.y + scalar);
}
};
int main() {
Vector2D v(1.0, 2.0);
Vector2D v1 = v + 5.0; // OK: operator+(v, 5.0)
Vector2D v2 = 5.0 + v; // OK: operator+(5.0, v)
return 0;
}
By using `friend` free functions, we decouple the operator from the `this` pointer. We provide both orderings so that primitive types can safely sit on either side of the `+` operator.
Key points
- Symmetry needs free functions: If an operator treats both operands equally (like `+` or `==`), use a non-member `friend` function.
- Return types matter: `+` returns by value. `+=` returns by reference.
Common mistakes
- Returning a reference from operator+: Writing `Vector2D& operator+(...)` means you are returning a reference to a local temporary object created during addition. This causes a dangling reference and immediate Undefined Behaviour. Always return by value for `operator+`.
Recall questions
- Why should binary operators like operator+ be implemented as non-member (free) functions?
- What is the purpose of the friend keyword in operator overloading?
- Why must operator+ return by value instead of by reference?
Questions & answers
A candidate implements `operator==` as a member function of their `String` class. When they write `if ("hello" == myString)`, the code fails to compile. Why?
The left side is a `const char*`. Since `operator==` is a member function, the compiler tries to call `("hello").operator==(myString)`, which doesn't exist. It should be a free function `bool operator==(const char* lhs, const String& rhs)`.
Approach: Identify the asymmetry of member operators.
How does `operator+=` differ from `operator+` in terms of its return type?
`operator+=` modifies the existing object in place and should return `*this` by reference (e.g., `Class&`). `operator+` creates a new object and must return it by value.
Approach: Understand the difference between mutating and non-mutating operators.