Operator overloading

An operator is a function with a special name. Defining operator+ for a vector type lets the code say a + b instead of add(a, b), and defining operator<< lets a value print itself. Used with restraint, that makes a value type read like a built-in one.

The rules are few: an operator must have at least one operand of class type, its meaning should match what the symbol already means, and the operators that change the left operand are members while the ones that do not are usually free functions. This lesson writes each kind on a small 2D vector.

operator+ as a free function

The addition of two vectors does not touch either operand, so it is a free function that returns a new value. Two accessors give it what it needs, and the class does not have to be a friend.

The program adds two vectors and prints the result through the coordinates. The temporary returned by operator+ is a normal object, and it is destroyed at the end of the full expression like any other.

A free function is also what allows 1.0 + v to work later, because the left operand is not the class. A member operator+ would only accept the class on the left.

#include <iostream> class vec2 { public: vec2(double x, double y) : x_(x), y_(y) {} double x() const { return x_; } double y() const { return y_; } private: double x_; double y_; }; vec2 operator+(const vec2& a, const vec2& b) { return vec2{a.x() + b.x(), a.y() + b.y()}; } int main() { vec2 a{1.0, 2.0}; vec2 b{0.5, 0.25}; vec2 c = a + b; std::cout << c.x() << ' ' << c.y() << '\n'; }

g++ -std=c++23 -Wall -Wextra plus.cpp -o plus
./plus

1.5 2.25

operator==

Equality compares the values of the two operands and returns a bool. It is a free function for the same reason as +, and it should not change either side.

The program compares two equal vectors and two different ones. C++20 can generate the comparison from a defaulted member, but the explicit version shows what the compiler writes: compare every member and combine the results with &&.

operator!= is generated from operator== in C++20, so only one of the two needs to be written. In older code both are written by hand, which is one reason the defaulted form is worth knowing.

#include <iostream> class point { public: point(int x, int y) : x_(x), y_(y) {} int x() const { return x_; } int y() const { return y_; } private: int x_; int y_; }; bool operator==(const point& a, const point& b) { return a.x() == b.x() && a.y() == b.y(); } int main() { point a{1, 2}; point b{1, 2}; point c{2, 1}; std::cout << (a == b) << ' ' << (a == c) << ' ' << (a != c) << '\n'; }

g++ -std=c++23 -Wall -Wextra equals.cpp -o equals
./equals

1 0 1

operator<<

Printing a value is a free function whose left operand is an ostream. It returns the stream so that std::cout << a << '\n' keeps working, and it takes the value by const reference.

The program prints a point with one call. The operator is found by argument-dependent lookup, so the header that declares point is also the header that makes it printable; no using directive is needed.

The same shape works with any stream: an output file, a stringstream, std::cerr. That is why operator<< takes std::ostream& and not std::cout&.

#include <iostream> class point { public: point(int x, int y) : x_(x), y_(y) {} int x() const { return x_; } int y() const { return y_; } private: int x_; int y_; }; std::ostream& operator<<(std::ostream& out, const point& p) { return out << '(' << p.x() << ", " << p.y() << ')'; } int main() { point p{3, 4}; std::cout << p << ' ' << p << '\n'; }

g++ -std=c++23 -Wall -Wextra stream.cpp -o stream
./stream

(3, 4) (3, 4)

operator+= and operator[]

An operator that changes its left operand is a member function. operator+= returns a reference to *this so that chained calls and the usual a += b; statement both work, and it is the member form because the left operand must be the object.

operator[] is a member for the same reason. The program's buffer returns a reference to the element, so b[0] = 7 writes through it; the const overload from lesson 029 is what a read-only caller would use.

The program appends with += and writes with [], and the printed line shows both effects. No operator here allocates, so the class stays a plain value.

#include <iostream> class buffer { public: buffer(int a, int b) : a_(a), b_(b) {} buffer& operator+=(int amount) { a_ += amount; b_ += amount; return *this; } int& operator[](int index) { return index == 0 ? a_ : b_; } int operator[](int index) const { return index == 0 ? a_ : b_; } private: int a_; int b_; }; int main() { buffer b{1, 2}; b += 10; b[1] = 0; std::cout << b[0] << ' ' << b[1] << '\n'; }

g++ -std=c++23 -Wall -Wextra compound.cpp -o compound
./compound

11 0

An operator needs a class operand

The language does not allow redefining an operator for two built-in types. int operator+(int, int) would change the meaning of every addition in the program, and the compiler rejects it.

The message says the operator must have an argument of class or enumerated type, and points at the declaration. The rule is the reason a library can define + for its own vector type without touching int.

The same restriction applies to operators that cannot be overloaded at all: ::, ., .* and ?:, plus sizeof and alignof. A type that needs to customise those needs a different design.

int operator+(int a, int b) { return a - b; } int main() { return 1 + 2; }

g++ -std=c++23 -Wall -Wextra builtin.cpp -o builtin

builtin.cpp:1:5: error: 'int operator+(int, int)' must have an argument of class or enumerated type
1 | int operator+(int a, int b)
| ^~~~~~~~

Exercises

  1. Add operator- to vec2 and use it in a program that subtracts two vectors.
  2. Add operator== to buffer and compare two buffers with the same values. Does operator!= work without writing it?
  3. Write operator* for a point and a double on both sides, one as a free function and one as a member, and compare which calls compile.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. When may an operator be overloaded?

    Answer

    When at least one operand has a class or enum type

    The rule stops a program from redefining the arithmetic of the built-in types.

  2. Which operator is usually written as a free function?

    Answer

    operator<< for streams

    The left operand is the stream, so the function cannot be a member of the class it prints.

  3. What should operator+= return?

    Answer

    A reference to the left operand

    Returning the object allows chaining and matches the behaviour of the built-in operator.

  4. Why must an overloaded operator keep its usual meaning?

    Answer

    Because a surprising operator makes the code harder to read

    The symbol is a convention, and a + that subtracts costs every reader of the code.

  5. What does this program print? #include <iostream> struct pair { double x; double y; }; pair operator+(const pair& a, const pair& b) { return {a.x + b.x, a.y + b.y}; } int main() { pair a{1.0, 0.5}; pair b{0.5, 1.75}; pair c = a + b; std::cout << c.x << ' ' << c.y; }

    Answer

    1.5 2.25

    The free operator adds the components, and the program prints the two sums.

  6. What does this program print? #include <iostream> struct point { int x; int y; bool operator==(const point& other) const { return x == other.x && y == other.y; } }; int main() { point a{1, 2}, b{1, 2}, c{3, 4}; std::cout << (a == b) << ' ' << (a == c) << ' ' << (b == b); }

    Answer

    1 0 1

    The operator compares both members, and the three comparisons are true, false and true.

  7. What does this program print? #include <iostream> struct point { int x; int y; }; std::ostream& operator<<(std::ostream& out, const point& p) { return out << '(' << p.x << ", " << p.y << ')'; } int main() { point p{3, 4}; std::cout << p << ' ' << p; }

    Answer

    (3, 4) (3, 4)

    The free operator writes the two members, and both print calls use the same overload.

  8. What does this program print? #include <iostream> struct numbers { int data[2] = {5, 0}; numbers& operator+=(int value) { data[0] += value; return *this; } int& operator[](int index) { return data[index]; } }; int main() { numbers n; n += 6; std::cout << n[0] << ' ' << n[1]; }

    Answer

    11 0

    The += adds six to the stored five, and the second element has never been set.

  9. What does defining int operator+(int, int) produce?

    Answer

    must have an argument of class or enumerated type

    Both operands are built-in, and the language reserves their operators.

  10. Why is operator<< not a member of the class it prints?

    Answer

    The left operand must be the stream

    The expression is std::cout << value, so the stream is on the left and the free function supplies the right side.

The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.

Article author: Arthur Isaev