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*thisby 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
friendfunction 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), returningosso calls chain. - Other common ones:
operator[]for indexing (C++23 allows several arguments),operator()to make function objects, andoperator bool(declare itexplicit).
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.