Operator Overloading

By the end of this lesson you'll be able to make your own classes behave like built-in types — adding them with + , comparing them with == and < , indexing them with [] , and printing them with cout << obj — while getting return types and const -correctness right.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Think of an operator like + as a universal plug socket . The built-in types ( int , double ) already fit it. Operator overloading is wiring an adapter so your own type fits the same socket — once Money + Money is wired up, the rest of the language (printing, sorting, totalling) "just works" with your type the way it does with numbers. You're not inventing new syntax; you're teaching a familiar symbol what it means for your class . The skill is wiring the adapter correctly: returning the right thing, and not changing what shouldn't change.

The hardest part isn't the syntax — it's the return type . Operators that build a new value return by value ; operators that change the object return *this by reference . Keep that distinction and most bugs disappear.

1. A Fully-Wired Class: Money

Here's a complete, correct example that overloads every operator this lesson covers on a Money type (stored as whole pence so there's no rounding drift). Read every comment, run it, and check the output. Notice the four shapes you'll reuse forever: arithmetic returns a new object by value , compound assignment returns *this by reference , comparison returns bool and is const , and operator<< is a non-member friend that returns the stream .

2. Member vs Non-Member — How to Choose

When you write an operator as a member , the left operand is always the object itself ( *this ), so it takes one fewer parameter. That's perfect for operators that belong to the object: = , [] , () , -> , and the compound assignments like += . A non-member function is symmetric: both operands are ordinary parameters, so it works even when the left side isn't your class. That's why operator<< must be a non-member — its left operand is cout , not your type — and why + and == are usually non-members too, so 2.0 * vec works as well as vec * 2.0 .

A member operator's left operand is fixed as your object. But cout << m has cout on the left, so operator<< can't be a member — it's a free function taking (ostream&, const Money&) . You mark it friend inside the class only so it can read private fields; it returns ostream& so chained << works.

3. Your Turn: Arithmetic & <<

Time to wire up your own type. The Vector2 below is almost finished — fill in the blanks marked ___ using the hints, then run it. Remember: operator+ and operator* build a new vector, so they return one by value .

4. Subscript [] , Compound += , and ==

Three more shapes to lock in. operator[] returns the element by reference so callers can assign into it ( s[2] = 30 ). operator+= changes the object, so it returns *this by reference — that's what lets you chain (s += 10) += 20 . And operator== compares field-by-field and returns bool . Fill in the blanks below.

operator= (copy assignment) is the one operator with serious hidden danger. If your class manages a raw resource — heap memory, a file handle — and you write a custom destructor, you almost certainly also need a custom copy constructor and copy assignment operator . That's the Rule of Three : those three go together. The compiler's default versions copy pointers shallowly, so two objects end up owning the same memory and both try to free it — a double-free crash.

C++11 adds the move constructor and move assignment , extending it to the Rule of Five . The best advice for beginners is the Rule of Zero : store your data in types that already manage themselves — std::string , std::vector , std::unique_ptr — and the compiler-generated operator= is automatically correct, so you write none of the five.

No blanks this time — just a brief and an outline. Build a Temp class that supports + , < , and printing with << . Run it and check your output against the example in the comments. This is exactly the shape of a real value type.

Practice quiz

How should operator+ return its result?

  • By reference to *this
  • By value — it builds a brand-new object
  • By pointer
  • By const reference to a local

Answer: By value — it builds a brand-new object. Arithmetic operators like + create a new value (a fresh object), so they return by value.

What should operator+= return, and how?

  • A new object by value
  • void
  • *this by reference, so calls can chain
  • A copy of the right-hand side

Answer: *this by reference, so calls can chain. Compound assignment modifies the existing object and returns *this by reference, enabling chains like (a += b) += c.

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

  • Members can't return references
  • Its left operand is the stream (cout), not your class
  • It is too large for a class
  • Streams forbid member operators

Answer: Its left operand is the stream (cout), not your class. cout << m has cout on the left, so operator<< can't be a member (a member's left operand is always *this); it's a free function.

Why is operator<< often marked friend inside the class?

  • To make it run faster
  • So it can read the class's private members
  • Because friends are required for all operators
  • To allow it to modify the stream

Answer: So it can read the class's private members. friend grants the free operator<< access to private fields; if it only prints public data it doesn't even need to be a friend.

What must operator<< return so that cout << a << b chains?

  • void
  • bool
  • ostream& (the stream by reference)
  • a copy of the object

Answer: ostream& (the stream by reference). Returning ostream& lets the next << operate on the same stream, so chaining works.

Why does operator[] return the element by reference (E&)?

  • To avoid copying the whole container

Returning a reference lets s[2] = 30 write into the element; returning by value would only give a temporary copy.

For a read-only comparison like operator==, what qualifier should it carry?

  • static
  • const
  • virtual
  • friend

Answer: const. Comparison operators don't modify the object, so mark them const so they work on const objects and const& parameters.

Given a Money type storing whole pence, what does Money(350) + Money(425) represent?

  • £3.50
  • £4.25
  • £7.75
  • £8.25

Answer: £7.75. 350p + 425p = 775p = £7.75; operator+ adds the pence and returns a new Money.

What is the Rule of Zero?

  • Never write any operators
  • Store data in self-managing types (string, vector, smart pointers) so the compiler-generated special members are correct
  • Always write all five special members by hand
  • Use only static member functions

Answer: Store data in self-managing types (string, vector, smart pointers) so the compiler-generated special members are correct. The Rule of Zero: rely on members that manage themselves so you write none of the five special members and operator= is automatically correct.

In C++20, what single declaration can generate the comparison operators automatically?

  • auto operator<=>(const T&) const = default;
  • bool operator==(const T&) = 0;
  • operator compare() default;
  • using std::compare;

Answer: auto operator<=>(const T&) const = default;. The C++20 spaceship operator <=> defaulted generates the comparisons consistently from one line.