Operator Overloading

You can give your own types the natural operators: + for adding vectors, == and < for comparing dates, << for printing. Done well, operator overloading makes user-defined types as convenient as built-in ones.

Arithmetic operators

C++
#include <iostream>

struct Vec2 {
    double x = 0, y = 0;

    Vec2& operator+=(const Vec2& other) {       // compound assignment: a member
        x += other.x;
        y += other.y;
        return *this;
    }
};

Vec2 operator+(Vec2 a, const Vec2& b) { return a += b; }          // built on +=
Vec2 operator*(const Vec2& v, double k) { return {v.x * k, v.y * k}; }
Vec2 operator-(const Vec2& v) { return {-v.x, -v.y}; }            // unary minus

int main() {
    Vec2 position{1, 2}, velocity{0.5, -0.5};
    position += velocity * 2;
    Vec2 back = -position;
    std::cout << position.x << ',' << position.y << "  " << back.x << ',' << back.y << '\n';
    Vec2 sum = Vec2{1, 1} + Vec2{2, 3};
    std::cout << sum.x << ',' << sum.y << '\n';
}
Output
2,1  -2,-1
3,4

The usual pattern: implement += as a member, then write + as a free function that uses it. A free operator+ treats both sides equally, so conversions work on either operand.

Printing with <<

To print with std::cout, overload operator<< for std::ostream. It must be a free function (the stream is on the left) and should return the stream so output can be chained:

C++
#include <iostream>

struct Money {
    long cents;
};

std::ostream& operator<<(std::ostream& out, const Money& m) {
    return out << '$' << m.cents / 100 << '.' << (m.cents % 100 < 10 ? "0" : "") << m.cents % 100;
}

int main() {
    Money price{1999}, tip{305};
    std::cout << "price " << price << ", tip " << tip << '\n';
}
Output
price $19.99, tip $3.05

Comparisons with <=>

C++20 makes comparisons easy. Defaulting operator<=> compares members in order and gives you <, <=, > and >=; defaulting it also provides == and !=:

C++
#include <algorithm>
#include <compare>
#include <iostream>
#include <vector>

struct Date {
    int year, month, day;
    auto operator<=>(const Date&) const = default;
};

std::ostream& operator<<(std::ostream& out, const Date& d) {
    return out << d.year << '-' << d.month << '-' << d.day;
}

int main() {
    std::vector<Date> dates{{2026, 3, 14}, {2025, 12, 1}, {2026, 1, 30}};
    std::sort(dates.begin(), dates.end());
    for (const auto& d : dates) std::cout << d << "  ";
    std::cout << '\n' << std::boolalpha << (Date{2026, 1, 1} < Date{2026, 1, 2}) << ' '
              << (dates[0] == Date{2025, 12, 1}) << '\n';
}
Output
2025-12-1  2026-1-30  2026-3-14
true true

When the default order isn't right, write operator<=> yourself and return a comparison of the relevant parts.

Subscript and call operators

operator[] gives indexing; operator() makes an object callable — a function object, which can carry state:

C++
#include <algorithm>
#include <iostream>
#include <vector>

class Matrix {
public:
    Matrix(int rows, int cols) : cols_(cols), data_(rows * cols) {}
    double& operator()(int r, int c) { return data_[r * cols_ + c]; }
    double operator()(int r, int c) const { return data_[r * cols_ + c]; }
private:
    int cols_;
    std::vector<double> data_;
};

struct IsMultipleOf {
    int n;
    bool operator()(int x) const { return x % n == 0; }
};

int main() {
    Matrix m(2, 3);
    m(1, 2) = 7.5;
    std::cout << m(1, 2) << ' ' << m(0, 0) << '\n';

    std::vector<int> v{3, 6, 7, 9, 10, 12};
    std::cout << std::count_if(v.begin(), v.end(), IsMultipleOf{3}) << '\n';
}
Output
7.5 0
4

Lambdas (lesson 24) are just a short way of writing function objects like IsMultipleOf. (C++23 also allows operator[] with several indexes.)

Guidelines

  • Keep the usual meaning. + should add, == should compare for equality. Surprising operators make code unreadable.
  • Overload related operators together — if you have +, also provide +=; prefer defaulted <=> for comparisons.
  • Return the right things: compound assignment returns *this by reference; arithmetic returns a new value; << returns the stream.
  • Some operators should almost never be overloaded: &&, || and , lose their short-circuit and ordering rules.

Try it yourself

Give a Fraction type +, *, == and <<.

Try it yourself · main.cpp
Edit the code, copy it into main.cpp, then compile and run it with c++ -std=c++20 -Wall main.cpp -o main && ./main. How to set up a C++ compiler
Output
5/6 1/6 true