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.
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.
What You'll Learn
💡 Real-World Analogy
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.
📊 Operators You'll Overload
Operator
Does
Returns
Member?
+ - *
Make a new value
by value (new object)
prefer non-member
== <
Compare two values
bool
+=
Change this object
*this by reference
member
[ ]
Index into the object
element by reference
must be member
<<
Print to a stream
the stream by reference
must be non-member
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 .
🔎 Deep Dive: why friend for operator<<
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.
🔎 Deep Dive: operator= and the Rule of Three / Five
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.
Pro Tips
- 💡 Implement + in terms of += : write operator+= first, then a + b as { T r = a; r += b; return r; } . Less code, no drift.
- 💡 Define != as !(a == b) and keep < consistent with == — never write them independently.
- 💡 Mark read-only operators const and take big parameters by const& . It enables use on const objects and avoids copies.
- 💡 C++20 spaceship: auto operator<=>(const T&) const = default; generates all six comparisons from one line.
Common Errors (and the fix)
- Returning by reference instead of value (or vice-versa): operator+ builds a new object, so return it by value — returning a reference to a local is a dangling reference and undefined behaviour. operator+= changes the existing object, so return *this by reference .
- Forgetting const -correctness: if operator+ or operator== isn't marked const , you can't use it on a const object or a const& parameter — "passing 'const T' as 'this' argument discards qualifiers".
- Asymmetric == : defining == but not keeping != consistent (or comparing only some fields) breaks std::find , std::set , and sorting. Define != as !(a == b) .
- Making operator<< a member: " operator<< must take exactly two arguments" — its left operand is the stream, so it has to be a non-member (a friend if it reads privates).
- Forgetting to return the stream from << : if operator<< returns void , then cout << a << b won't compile — return ostream& so it can chain.
📋 Quick Reference
Typical signature
+
T operator+(const T&) const
new T by value
T& operator+=(const T&)
==
bool operator==(const T&) const
<
bool operator<(const T&) const
E& operator[](int)
ostream& operator<<(ostream&, const T&)
the stream (non-member)
Frequently Asked Questions
Mini-Challenge: a Temperature class
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.
🎉 Lesson Complete
- ✅ Arithmetic ( + - * ) returns a new object by value
- ✅ Compound assignment ( += ) returns *this by reference so it chains
- ✅ Comparison ( == , < ) returns bool and is const ; keep != consistent with ==
- ✅ operator[] returns the element by reference so you can assign into it
- ✅ operator<< is a non-member friend that returns the stream
- ✅ Member for = / [] / () / += ; non-member for symmetric + / == / <<
- ✅ Mind the Rule of Three/Five for operator= — or follow the Rule of Zero
- ✅ Next lesson: Concurrency in C++ — threads, futures, promises, and async
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.
Continue this course
- Previous: C++17 & C++20 Modern Features
- Next: Concurrency in C++