C++ Operator Overloading, Friend Functions & Stream Operators Masterclass

โšก Modern C++ (C++17 / C++20 / C++23) ๐ŸŸข Lesson 12 ๐Ÿ“‚ Phase 12: Operator Overloading ๐Ÿ“… 2026 Master Edition
๐Ÿ“Œ Covered in this in-depth guide: Overloading +, -, *, == ยท Assignment & Compound Assignment ยท operator<< stream ยท Friend Functions ยท Prefix & Postfix ++ ยท operator[] ยท C++20 Spaceship <=>

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.

1What is Operator Overloading?

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.

OperatorImplementationNotes
+, -, *Member or non-memberReturn by value (new object)
==, !=, <Non-member preferredReturn bool
=Member onlyReturn T& (self)
[]Member onlyReturn reference (r/w) and const reference (read)
<<, >>Non-member friendReturn std::ostream&
++ prefixMember: T& operator++()Increment then return self
++ postfixMember: T operator++(int)Copy, increment, return old copy
2Complete Vector2D Example with Full Operator Suite
C++ โ€” Operator Overloading (Vector2D)โ–ถ Run in Compiler
#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;
}
3Friend Functions & Rules / Limitations

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 (<, <=, >, >=, ==, !=).

4Technical FAQs

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.