C++ by Example
C++ by Example

Operator Overloading

2 min read

Operator Overloading

C++ lets you define the behavior of operators (+, ==, <<, [], etc.) for your own types. Done well, operator overloading makes user-defined types as natural to use as built-in ones.

Arithmetic operators

#include <iostream>

struct Vec2 {
    double x, y;

    Vec2 operator+(const Vec2& rhs) const {
        return {x + rhs.x, y + rhs.y};
    }

    Vec2 operator*(double scalar) const {
        return {x * scalar, y * scalar};
    }

    Vec2& operator+=(const Vec2& rhs) {
        x += rhs.x;
        y += rhs.y;
        return *this;
    }
};

int main() {
    Vec2 a = {1.0, 2.0};
    Vec2 b = {3.0, 4.0};

    Vec2 c = a + b;
    std::cout << c.x << " " << c.y << "\n";   // 4 6

    Vec2 d = a * 3.0;
    std::cout << d.x << " " << d.y << "\n";   // 3 6
}

Comparison operators

#include <string>

struct Employee {
    std::string name;
    int id;

    bool operator==(const Employee& rhs) const { return id == rhs.id; }
    bool operator!=(const Employee& rhs) const { return id != rhs.id; }
    bool operator< (const Employee& rhs) const { return id <  rhs.id; }
};

C++20 introduces the spaceship operator <=> which generates all six comparisons from one definition:

#include <compare>

struct Employee {
    std::string name;
    int id;

    auto operator<=>(const Employee& rhs) const = default;
    bool operator==(const Employee& rhs) const = default;
};

Stream insertion operator

Overload << as a free function to integrate with std::cout:

#include <iostream>

struct Vec2 { double x, y; };

std::ostream& operator<<(std::ostream& os, const Vec2& v) {
    return os << "(" << v.x << ", " << v.y << ")";
}

int main() {
    Vec2 v = {1.5, 2.5};
    std::cout << v << "\n";   // (1.5, 2.5)
}

Return std::ostream& so that chained << calls work: std::cout << a << b.

Subscript operator

#include <stdexcept>
#include <vector>

class Matrix {
    std::vector<double> data;
    int rows_, cols_;
public:
    Matrix(int r, int c) : data(r * c, 0.0), rows_(r), cols_(c) {}

    double& operator()(int r, int c) {
        return data[r * cols_ + c];
    }

    double operator()(int r, int c) const {
        return data[r * cols_ + c];
    }
};

int main() {
    Matrix m(3, 4);
    m(1, 2) = 9.5;
    std::cout << m(1, 2) << "\n";   // 9.5
}

Note: for 2D access [][] does not work directly in C++23 and earlier. Use () for multi-dimensional indexing, or a proxy object. C++23 adds multi-dimensional [].

Increment and decrement

Prefix and postfix versions have different signatures:

struct Counter {
    int value = 0;

    Counter& operator++() {      // prefix ++c
        value++;
        return *this;
    }

    Counter operator++(int) {    // postfix c++ (the int is a dummy)
        Counter tmp = *this;
        value++;
        return tmp;
    }
};

Rules of thumb

  • Overload only when it makes the type feel natural to use (+ for numeric types, << for printable types).
  • Do not overload operators with unintuitive meanings (* for a string type, + for a database connection).
  • Arithmetic operators that produce a new value (+, -) should be const member functions or free functions.
  • Compound assignment operators (+=, -=) should modify *this and return *this by reference.
C++ by Example
C++ by Example

Learn modern C++ through working code. Each chapter introduces one concept — variables, functions, classes, templates, smart pointers, concurrency — with a clear example, a line-by-line explanation, and notes on how it applies in real programs.

View book →