C++ Operator Overloading

Make your own types work with +, -, ==, <, << and [] by overloading operators, as member or non-member functions, following the rules that keep them predictable.

Lesson 19 of 44Advanced18 min read

You can add two numbers with +, compare two strings with == and print almost anything with cout <<. But if you write your own class, say a Money type or a 2D Vec2 for a game, none of that works yet. You'd have to write a.add(b) and a.isEqualTo(b), and call a print() function every time you want to see a value.

Operator overloading lets your own types use the same operators as the built-in ones. After defining operator+ for Vec2, position + velocity just works. The standard library is full of overloaded operators: string uses + to join text, vector uses [] for indexing, and streams use << and >>.

In this lesson you'll overload arithmetic, comparison, printing and indexing operators, decide whether each one should be a member function or a non-member function, and learn the rules that keep overloaded operators easy to understand instead of surprising.

Overloading + and +=

An overloaded operator is just a function with a special name, operator followed by the symbol:

C++
struct Vec2 {
    double x, y;

    Vec2 operator+(const Vec2& other) const {
        return {x + other.x, y + other.y};
    }
    Vec2& operator+=(const Vec2& other) {
        x += other.x;
        y += other.y;
        return *this;
    }
};
  • a + b calls a.operator+(b). It returns a new object and doesn't change either side, so it's marked const.
  • a += b changes a itself and returns a reference to it (*this), so you can chain calls.
  • A common pattern is to write += first and then define + using it.

Comparison operators

operator== says when two objects count as equal; operator!= is usually its opposite. operator< gives your type a natural order, which sort, set and map use automatically.

In C++20, you can ask the compiler to write all of them for you:

C++
struct Version {
    int major, minor, patch;
    auto operator<=>(const Version&) const = default;   // also gives ==
};

The "spaceship" operator <=> compares the members in order, just like comparing words letter by letter, and C++20 generates <, >, <=, >=, == and != from it. With older compilers, write operator== and operator< by hand.

Printing with operator<<

To print your type with cout, overload << as a non-member function (outside the class), because the left side is the stream, not your object:

C++
ostream& operator<<(ostream& out, const Vec2& v) {
    out << "(" << v.x << ", " << v.y << ")";
    return out;
}

Return the stream so that cout << a << " and " << b keeps working. If the operator needs private members, declare it a friend inside the class, or use public getters.

Indexing and the rules

operator[] lets an object be indexed like an array. Provide two versions: one returning a reference so values can be changed, and a const one for reading.

Some guidelines that keep overloads predictable:

  • Make the operator do what people expect: + adds or combines, == compares. Never make + subtract.
  • If you provide ==, provide !=; if you provide +, consider +=.
  • Operators that don't change their operands should take const& parameters and be const.
  • You can't create new operators, and you can't change how operators work on built-in types like int.

C++ Operator Overloading Syntax

Operator signatures

T operator+(const T& other) const;          // member
T& operator+=(const T& other);              // member, returns *this
bool operator==(const T& a, const T& b);    // non-member function
bool operator<(const T& a, const T& b);
auto operator<=>(const T&) const = default; // C++20
ostream& operator<<(ostream& out, const T& t);
Elem& operator[](size_t i);
const Elem& operator[](size_t i) const;

C++ Operator Overloading Summary

  • An overloaded operator is a function named operator followed by the symbol, such as operator+.
  • Arithmetic operators return new objects; compound ones like += change the object and return *this.
  • operator== and operator< enable comparisons, sorting, sets and maps; C++20's <=> can generate them.
  • operator<< is a non-member function that takes and returns an ostream&.
  • Keep overloads unsurprising: match the usual meaning of each symbol.

C++ Operator Overloading Examples

Each example shows the code, the output it produces and what each part does.

Example 1

A 2D vector for a game

C++
#include <iostream>
using namespace std;

struct Vec2 {
    double x, y;

    Vec2 operator+(const Vec2& o) const { return {x + o.x, y + o.y}; }
    Vec2 operator*(double k) const { return {x * k, y * k}; }
    Vec2& operator+=(const Vec2& o) {
        x += o.x;
        y += o.y;
        return *this;
    }
    bool operator==(const Vec2& o) const { return x == o.x && y == o.y; }
};

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

int main() {
    Vec2 position{0, 0};
    Vec2 velocity{1.5, -2};

    for (int frame = 1; frame <= 3; frame++) {
        position += velocity;
        cout << "Frame " << frame << ": " << position << endl;
    }
    cout << "Double speed: " << velocity * 2 << endl;
    cout << "Back at start? " << (position == Vec2{0, 0}) << endl;
    return 0;
}
Output
Frame 1: (1.5, -2)
Frame 2: (3, -4)
Frame 3: (4.5, -6)
Double speed: (3, -4)
Back at start? 0
Line-by-line explanation
  1. Line 7+ returns a new Vec2 and leaves both operands unchanged.
  2. Line 8Operators can take other types too: here a Vec2 times a number.
  3. Lines 9 to 13+= changes this object and returns a reference to it.
  4. Lines 17 to 19<< is a non-member function that returns the stream so outputs can be chained.
  5. Line 26Moving the position reads naturally, like built-in maths.
Example 2

Sorting with operator<

C++
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;

struct Version {
    int major, minor, patch;
};

bool operator<(const Version& a, const Version& b) {
    if (a.major != b.major) return a.major < b.major;
    if (a.minor != b.minor) return a.minor < b.minor;
    return a.patch < b.patch;
}

ostream& operator<<(ostream& out, const Version& v) {
    return out << v.major << "." << v.minor << "." << v.patch;
}

int main() {
    vector<Version> releases = {{1, 10, 0}, {1, 2, 3}, {2, 0, 0}, {1, 2, 0}};
    sort(releases.begin(), releases.end());
    for (const auto& v : releases) cout << v << "  ";
    cout << endl;

    set<Version> versions(releases.begin(), releases.end());
    cout << "Newest: " << *versions.rbegin() << endl;
    return 0;
}
Output
1.2.0  1.2.3  1.10.0  2.0.0  
Newest: 2.0.0
Line-by-line explanation
  1. Lines 12 to 16Compare major first, then minor, then patch, so 1.10.0 correctly comes after 1.2.3.
  2. Line 24sort uses operator< automatically; no lambda needed.
  3. Lines 28 to 29set also relies on operator<, and rbegin() points at the largest item.
Example 3

Indexing with operator[]

C++
#include <iostream>
#include <stdexcept>
using namespace std;

class Week {
    double hours[7] = {};
public:
    double& operator[](int day) {
        if (day < 0 || day > 6) throw out_of_range("day must be 0 to 6");
        return hours[day];
    }
    double operator[](int day) const {
        if (day < 0 || day > 6) throw out_of_range("day must be 0 to 6");
        return hours[day];
    }
    double total() const {
        double sum = 0;
        for (double h : hours) sum += h;
        return sum;
    }
};

int main() {
    Week w;
    w[0] = 7.5;
    w[1] = 8;
    w[4] = 6.25;
    cout << "Monday: " << w[0] << " h" << endl;
    cout << "Week total: " << w.total() << " h" << endl;
    try {
        w[9] = 3;
    } catch (const out_of_range& e) {
        cout << "Error: " << e.what() << endl;
    }
    return 0;
}
Output
Monday: 7.5 h
Week total: 21.75 h
Error: day must be 0 to 6
Line-by-line explanation
  1. Lines 8 to 11Returning a reference lets w[0] = 7.5 store a value inside the object.
  2. Lines 12 to 15A const version for reading from const objects.
  3. Line 9Unlike a plain array, this operator checks the index and throws a clear error.
  4. Line 31Day 9 doesn't exist, so the exception is thrown and caught.

Common C++ Operator Overloading Mistakes

Forgetting to return the stream

Don't
void operator<<(ostream& out, const Vec2& v) { out << v.x; }
cout << a << endl;              // error: can't chain
Do
ostream& operator<<(ostream& out, const Vec2& v) { return out << v.x; }

+ that changes the left side

Don't
Vec2 operator+(const Vec2& o) { x += o.x; y += o.y; return *this; }
// a + b changes a!
Do
Vec2 operator+(const Vec2& o) const { return {x + o.x, y + o.y}; }

Surprising meanings

Don't
Money operator+(const Money& a, const Money& b) { return a.pence - b.pence; }
Do
Money operator+(const Money& a, const Money& b) { return Money{a.pence + b.pence}; }

An operator should always do what its symbol suggests.

C++ Operator Overloading in Real Projects

Overloaded operators in practice

Game engines and graphics libraries such as GLM overload arithmetic for vectors and matrices, so physics code reads like the maths on paper. Financial code defines Money types with + and comparisons that can't accidentally mix currencies. The standard library itself relies on overloading: string + string, map[key], iterator ++ and *, and every << you've sent to cout.

C++ Operator Overloading FAQ

How do I overload the prefix and postfix ++ operators?

Prefix ++x is T& operator++(): it changes the object and returns it by reference. Postfix x++ has a dummy int parameter, T operator++(int), and returns a copy of the old value: save *this in a local, increment, then return the saved copy. Write prefix first and have postfix call it.

Which operators can't be overloaded in C++?

You can't overload :: (scope), . (member access), .*, the conditional operator ?:, sizeof, alignof or typeid. You also can't invent new operator symbols or change an operator's precedence or how many operands it takes. Almost everything else, including [], (), -> and <<, can be overloaded.

How do I overload operator>> to read my own type?

Write a non-member function that takes the input stream and a non-const reference to your object, reads the members, and returns the stream: istream& operator>>(istream& in, Vec2& v) { return in >> v.x >> v.y; }. Returning the stream lets callers chain reads and test the result, as in while (cin >> v).

What is operator() and what is a function object?

Overloading operator() lets an object be called like a function. struct Multiplier { int k; int operator()(int x) const { return x * k; } }; then Multiplier triple{3}; triple(5) gives 15. Such function objects can carry their own data, which is exactly how lambdas work behind the scenes, and they can be passed to algorithms like sort and transform.

Why should operator= return a reference to *this?

Returning T& with return *this; lets assignments chain the way they do for built-in types: a = b = c; assigns c to b and then b to a. It also avoids making an extra copy of the object. In most classes you don't write operator= at all; the compiler-generated one does the right thing.

C++ Operator Overloading Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the function name that overloads the + operator.

Vec2 (const Vec2& o) const {
    return {x + o.x, y + o.y};
}
Multiple choiceeasy

Why must operator<< for printing be a non-member function?

Predict the outputmedium

Using the lesson's Vec2, what does cout << Vec2{1, 2} + Vec2{3, 4}; print?

Multiple choicemedium

What should operator+= return?

Multiple choicehard

Which operator do std::sort and std::set use by default?

C++ Operator Overloading Quiz

5 questions to check what you learned.

  1. What is operator overloading?

    • Using too many operators
    • Creating new symbols
    • Defining how operators like + and == work for your own types
    • Changing how + works for int
  2. What does C++20's auto operator<=>(const T&) const = default; give you?

    • All the comparison operators, comparing members in order
    • Only <
    • Printing support
    • Addition
  3. Why does the non-const operator[] return a reference?

    • To save memory
    • So code like w[0] = 7.5 can change the stored value
    • References are faster to print
    • It must return void
  4. Why mark operator+ as const?

    • It's required by the compiler
    • It makes the result const
    • It makes it a non-member function
    • It promises not to change the object, and lets it work on const objects
  5. Which is a poor use of operator overloading?

    • + to add two Money values
    • == to compare two Points
    • + to remove an item from a list
    • << to print a Vec2