Consider the following:
std::cout << '\n' << "Added: " << book << '\n';
// Output
Added: "what if?" by Randall Munroe
This statement contains multiple << operators that are chained in a single instruction. It reads as if it’s a sentence. The insertion operator (<<) operators on a C-style string literal, an escape sequence, a class object and a function.
- How does
<<know how to interact with each unique type? - How do all of these instructions resolve into a clean
std::coutstatement
Three things to take away:
- Operators in C++ produce a result.
- Stream insertion returns the stream by reference.
- Operator overloading lets
<<work with user-defined types such asBook.
What do operators return?
When we do 22 + 4 in maths, the operands (22, 4) are manipulated by an operator (+). The result is a new value.
In C++, this new output value that arises from an operation is called a return value. This can be used as an operand as part of a new operation/expression. This is what makes chaining possible — each operation feeds its result into the next.
operator<< returns the stream
When writing to std::cout, the << operator is called the stream insertion operator.
This expression:
std::cout << "Added: ";
inserts text into the output stream.
The important detail is that stream insertion returns the stream itself.
Conceptually, this:
std::cout << "Added: " << book << '\\n';
groups like this:
((std::cout << "Added: ") << book) << '\\n';
Step by step:
std::cout << "Added: " → returns std::cout
std::cout << book → returns std::cout
std::cout << '\\n' → returns std::cout
Each insertion returns std::cout by reference, so the next insertion has a stream to operate on.
That is why chaining works.
Operator overloading
The Standard Library already provides overloads of operator<< for many built-in and standard types.
That is why this works:
#include <iostream>
#include <string>
int main(){
int age { 36 };
double height { 1.82 };
std::string name { "Ada" };
std::cout << name << " is " << age << " years old and "
<< height << "m tall.\n";
}
std::cout knows how to print std::string, int, double, string literals, and characters because suitable overloads exist.
But if you create your own type, the compiler does not automatically know how to print it.
The example at the beginning of the page is from my library management system program. Book knows how to interact with << since the insertion operator is overloaded to work with Book objects.
// The overloaded function returns an std::ostream
std::ostream& operator <<(std::ostream& outStream, const Book& book) {
outStream << "\\"" << book.m_title << "\\" by " << book.m_author;
// Fancy logic ...
return outStream;
}
Since it returns a std::ostream, we can use operator chaining to operate on it again via <<.
The standard library overloads << to operate on std::ostream, char, int.
Takeaway
Chained output works because operator<< returns std::ostream&. Each insertion writes something to the stream, then hands the same stream to the next insertion. Pair it with operator overloading to get clean code like this:
std::cout << "Added: " << book << '\n';