Operator Overloading
Here's a question that sounds like a trick but isn't: why is 1 + 2 valid C++, but p1 + p2 for two Point objects a compile error, unless you do something about it? + isn't magic. It's just a function with unusually short, symbolic syntax. int::operator+ (conceptually) is built into the language for the built-in types. For your own types, nothing calls it unless you write it.
Operator overloading is defining a function that runs when someone writes an operator symbol against your type. That's the whole idea. a + b on a class you wrote is sugar for a function call - either a.operator+(b) or operator+(a, b), depending on how you defined it. The compiler sees + between two Points, looks for an operator+ that accepts those types, and calls it, exactly like it would resolve any other overloaded function name (phase 4). You're not changing what + means to the language. You're teaching it what + means for your type.
This phase assumes you're comfortable with classes (phase 6) and constructors/destructors (phase 7). We won't re-cover operator= here - assignment is part of the Rule of Five from phase 8, and it deserves that phase's full attention to copies, moves, and self-assignment. This phase is about the other operators: arithmetic, comparison, indexing, and the one you've been using since phase 1 without knowing it was overloaded - << on std::cout.
Member vs. free function: who's on the left?
An operator function can be written two ways, and the choice isn't stylistic - it's forced by which side of the operator your type is on.
As a member function, the left operand is the implicit this:
;
Point a, b;
Point c = a + b; // calls a.operator+(b)
This works because a, the left operand, is a Point - it has the member to call.
As a free (non-member) function, neither operand needs to be this:
Point
Both versions produce identical behavior for a + b. So why would you ever pick the free function? Because sometimes the left operand isn't your type, and a member function can't help you there.
The stream operator: why << has to be free
You've written std::cout << x in every example in this guide. << is an operator on std::ostream, overloaded to mean "print" instead of its original meaning "bit-shift left" (that's the C legacy showing - << was borrowed, not invented, for streams). When you want std::cout << myPoint to work, think about what a member function would require: operator<< would need to be a member of std::ostream, since the left operand is the stream. You can't add members to a class you don't own. So the stream operator for a custom type is always a free function:
std::ostream&
std::cout << a << "\n"; // (1, 2)
Two details matter here. First, it takes std::ostream& and returns std::ostream& - that's what makes chaining (os << a << " and " << b) work, the same reason std::cout itself returns a reference to itself from every <<. Second, if operator<< needs to read Point's private members, it can't - unless you either expose the data (as we did, with public x/y) or declare it friend inside the class:
;
std::ostream&
friend is a narrow, deliberate exception: this one specific free function, and only this one, gets access to Point's private members. It's not a general escape hatch - use it for exactly this pattern (operators and closely-coupled helper functions), not as a way to avoid writing accessors.
Comparison operators
operator== follows the same member-or-free choice as +. The natural spelling compares field by field:
bool
bool
Notice operator!= is defined in terms of operator== rather than repeating the comparison. That's not laziness, it's a correctness habit: if someone later changes what "equal" means for Point, there's exactly one place to update, and != stays consistent by construction. (If your compiler targets C++20, the language will synthesize != from == automatically - one less function to write - but understanding the manual version is what tells you why that shortcut is safe.)
Indexing with operator[]
Container-like types overload operator[] to give array syntax. The trick worth knowing here is the const overload pair:
;
The non-const version returns int& - a reference you can assign through, so box[0] = 5; compiles. The const version returns const int& - read-only, called when box itself is const. The compiler picks whichever overload matches the const-ness of the object you're indexing. Without the const version, a const IntBox& parameter couldn't be indexed at all. This pair shows up constantly in real STL-style container code, which is exactly what you're about to look at in phases 11 and 12.
What not to overload
Operator overloading has one famous cautionary tale: std::cin >> x and std::cout << x mean "extract" and "insert," borrowed from bit-shift for a purpose that has nothing to do with shifting bits. It works because streams committed to it consistently and everyone learned the convention. That's the bar: an overloaded operator should do the thing a reader would guess from its symbol, applied to your type. + should combine two things into a bigger thing. == should mean "these are equivalent," not "start a network request." If you find yourself overloading + to mean "log this and return void," stop and write a named function instead - point.add(other) is clearer code than a + that lies about what it does.
A few overloads are also just off the table. &&, ||, and , can technically be overloaded, but doing so throws away short-circuit evaluation and sequencing guarantees the built-in versions have, which surprises callers in ways that are hard to debug - avoid them. You also can't overload an operator when both operands are built-in types (int + int can't be redefined), and you can't invent new operator tokens; you're restricted to the fixed set the language already has symbols for.
Recap
- Operator overloading is a function call in disguise:
a + bbecomesa.operator+(b)oroperator+(a, b). - Use a member function when your type is always the left operand; use a free function when it isn't -
operator<<for printing is the standard example, since the left operand isstd::ostream, not your class. friendgrants one specific outside function access to private members - the right tool for stream operators that need to read your class's internals.- Define
operator!=in terms ofoperator==so there's one source of truth for equality. operator[]typically comes in a const/non-const pair, returningT&orconst T&to match the caller's access level.- Overload only what reads naturally from the symbol. If the operator's meaning isn't obvious at a glance, write a named function instead.
Check yourself
[
{
"q": "Why must `operator<<` for printing a custom type be written as a free function, not a member of your class?",
"choices": [
"Member operators can't return a value, so chaining wouldn't work",
"The left operand in `std::cout << myPoint` is `std::ostream`, which you can't add members to",
"Free functions run faster than member functions in C++",
"`<<` can only be a member function when overloaded for arithmetic types"
],
"answer": 1,
"explain": "A member function is invoked through its left operand; since the left operand here is std::ostream, not your class, only a free function can be the one called."
},
{
"q": "Why does the guide define `operator!=` by calling `operator==` and negating it, instead of writing a separate field-by-field comparison?",
"choices": [
"Negation is faster at runtime than a second comparison",
"C++ requires operator!= to be implemented in terms of operator==",
"If the definition of equality changes later, there's only one place to update, so != stays correct automatically",
"Only operator== is allowed to access private members"
],
"answer": 2,
"explain": "Deriving != from == means there's a single source of truth for what 'equal' means, so a later change to == can't leave != out of sync."
},
{
"q": "A function takes `const IntBox&`. What lets it call `box[0]` inside that function?",
"choices": [
"A single `operator[]` returning `int&` is enough",
"A const `operator[]` returning `const int&`, callable on a const object, must exist",
"Marking `operator[]` as `friend`",
"Making `operator[]` a template function"
],
"answer": 1,
"explain": "The compiler picks the overload matching the object's const-ness; without a const version, a const IntBox can't be indexed at all."
}
]
← Phase 8: Copy, Move & the Rule of Five · Phase 10: Templates & Generic Programming →
Check your understanding 3 questions
1. Why must `operator<<` for printing a custom type be written as a free function, not a member of your class?
2. Why does the guide define `operator!=` by calling `operator==` and negating it, instead of writing a separate field-by-field comparison?
3. A function takes `const IntBox&`. What lets it call `box[0]` inside that function?