Operator overloading

C++ · Practical · 12 min

Well-chosen operators make value types like money, vectors or dates read like built-ins, and the standard library relies on them.

You can define what operators mean for your own types by writing functions named operator+, operator==, operator<< and so on. The rule is to keep the usual meaning: + should add, == should compare, and nothing should surprise the reader.

Some conventions:

  • Implement compound assignment (+=) as a member returning *this by reference, then write + in terms of it.
  • Make symmetric binary operators non-members, so conversions apply to both operands. A popular way is a hidden friend: a friend function defined inside the class body, found only by argument-dependent lookup.
  • Stream output is always a non-member: std::ostream& operator<<(std::ostream& os, const T& v), returning os so calls chain.
  • Other common ones: operator[] for indexing (C++23 allows several arguments), operator() to make function objects, and operator bool (declare it explicit).

C++20 transformed comparisons. Declare auto operator<=>(const T&) const = default; (the three-way comparison or "spaceship" operator) and the compiler compares members in declaration order and gives you <, <=, > and >=. A defaulted <=> also implicitly declares a defaulted operator==, so you get == and != too. If you write <=> by hand, also define == yourself, since equality is often cheaper to compute than ordering. The result is a std::strong_ordering (or weak_ordering/partial_ordering for doubles), from <compare>.

The compiler also rewrites expressions: a != b uses operator==, and b < a can be served by a <=> b. That removes the old boilerplate of writing six comparison operators.

Don't overload &&, || or ,: overloaded versions lose short-circuiting and ordering guarantees.

Example

#include <compare>
#include <iostream>

struct Money {
    long cents = 0;

    Money& operator+=(Money other) { cents += other.cents; return *this; }
    friend Money operator+(Money a, Money b) { return a += b; }    // built on +=

    // C++20: compares members in order, gives < <= > >= and also == !=
    friend auto operator<=>(const Money&, const Money&) = default;

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

int main() {
    Money a{250}, b{1999};
    Money total = a + b;
    total += Money{1};
    std::cout << total << '\n';                                    // $22.50
    std::cout << (a < b) << ' ' << (a == Money{250}) << ' ' << (a != b) << '\n';  // 1 1 1
}

Remember

  • Keep the conventional meaning of each operator
  • Write += as a member, + (and <<) as non-member or hidden friend
  • C++20: a defaulted <=> gives all six comparisons

Watch out for

  • Writing a custom operator<=> and expecting == to appear — only a defaulted <=> declares == for you.

Check yourself

  1. A struct declares only `auto operator<=>(const P&) const = default;`. Which expressions compile?
  2. Why is `operator<<` for printing written as a non-member?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: en.cppreference.com/w/cpp/language/operators

Builds on

Unlocks

Nothing builds on this yet.

Operator overloading · C++ · Topolearn