C++ Operator Overloading, Friend Functions & Stream Operators Masterclass
Welcome to Phase 12: Operator Overloading! C++ allows user-defined types to define the behavior of built-in operators (+, -, ==, <<, [], etc.) making classes feel as natural as built-in types. This is the basis for std::string, std::vector, and smart pointer syntax.
Operator overloading is defining a special function named operator@ (where @ is the operator symbol) that C++ calls when the operator is used on objects of your class. It does NOT change operator precedence, associativity, or arity.
| Operator | Implementation | Notes |
|---|---|---|
+, -, * | Member or non-member | Return by value (new object) |
==, !=, < | Non-member preferred | Return bool |
= | Member only | Return T& (self) |
[] | Member only | Return reference (r/w) and const reference (read) |
<<, >> | Non-member friend | Return std::ostream& |
++ prefix | Member: T& operator++() | Increment then return self |
++ postfix | Member: T operator++(int) | Copy, increment, return old copy |
#include <iostream>
#include <cmath>
#include <stdexcept>
class Vector2D {
double x_, y_;
public:
Vector2D(double x = 0, double y = 0) : x_{x}, y_{y} {}
// Arithmetic operators (member)
Vector2D operator+(const Vector2D& other) const {
return {x_ + other.x_, y_ + other.y_};
}
Vector2D operator-(const Vector2D& other) const {
return {x_ - other.x_, y_ - other.y_};
}
Vector2D operator*(double scalar) const {
return {x_ * scalar, y_ * scalar};
}
// Compound assignment
Vector2D& operator+=(const Vector2D& other) {
x_ += other.x_; y_ += other.y_;
return *this;
}
// Comparison
bool operator==(const Vector2D& other) const {
return x_ == other.x_ && y_ == other.y_;
}
bool operator!=(const Vector2D& other) const {
return !(*this == other);
}
// Prefix increment
Vector2D& operator++() {
++x_; ++y_;
return *this;
}
// Postfix increment (dummy int parameter)
Vector2D operator++(int) {
Vector2D old = *this;
++(*this);
return old;
}
// Index operator
double& operator[](int idx) {
if (idx == 0) return x_;
if (idx == 1) return y_;
throw std::out_of_range("Vector2D: index must be 0 or 1");
}
const double& operator[](int idx) const {
if (idx == 0) return x_;
if (idx == 1) return y_;
throw std::out_of_range("Vector2D: index must be 0 or 1");
}
double magnitude() const { return std::sqrt(x_*x_ + y_*y_); }
// Friend: Stream insertion (non-member accessing private data)
friend std::ostream& operator<<(std::ostream& os, const Vector2D& v) {
return os << "(" << v.x_ << ", " << v.y_ << ")";
}
// Friend: scalar * vector (reversed operand order)
friend Vector2D operator*(double scalar, const Vector2D& v) {
return v * scalar;
}
};
int main() {
Vector2D a{3.0, 4.0}, b{1.0, 2.0};
std::cout << "a = " << a << "
";
std::cout << "b = " << b << "
";
std::cout << "a + b = " << (a + b) << "
";
std::cout << "a - b = " << (a - b) << "
";
std::cout << "a * 2 = " << (a * 2.0) << "
";
std::cout << "3 * a = " << (3.0 * a) << "
";
std::cout << "|a| = " << a.magnitude() << "
";
std::cout << "a == b = " << std::boolalpha << (a == b) << "
";
std::cout << "a[0] = " << a[0] << "
";
Vector2D c = a;
std::cout << "c++ (post) = " << c++ << " then c = " << c << "
";
std::cout << "++c (pre) = " << ++c << "
";
return 0;
}
When to use friend non-member operators:
โข Use friend for symmetric operators (+, ==) so both sides get equal treatment.
โข Use friend for operator<< and operator>> since ostream is on the left side.
Operators that CANNOT be overloaded:
:: (scope resolution) . (member access) .* (pointer-to-member) ?: (ternary) sizeof alignof
C++20 โ Spaceship operator <=>:
Defining auto operator<=>(const T&) const = default; automatically generates all 6 comparison operators (<, <=, >, >=, ==, !=).
Q1: Should operator+ be a member or non-member?
Prefer non-member (or friend) for symmetric binary operators like +. This allows implicit conversions on both operands.
Q2: What is the return type of operator=?
Always return T& (reference to *this). This enables chaining: a = b = c;.
Q3: Difference between prefix and postfix ++?
Prefix operator++() has no parameters and returns reference to modified object. Postfix operator++(int) has a dummy int parameter, saves a copy, increments, returns old copy.
Q4: Can I overload operator&& or operator||?
Technically yes, but avoid it! Overloaded && and || lose their short-circuit evaluation property โ both operands are always evaluated.
Q5: What is the conversion operator?
operator bool() const defines implicit conversion to bool, used for if (myObj) checks. Mark it explicit to prevent accidental implicit conversions.