Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Custom Stream Input/Output
Enable std::cout << and std::cin >> for your custom types.
Overloading the I/O Operators
For classes with multiple member variables, printing each variable individually becomes tedious. Consider this class:
class Vector2D
{
private:
double m_x{};
double m_y{};
public:
Vector2D(double x = 0.0, double y = 0.0)
: m_x{x}, m_y{y}
{}
double getX() const { return m_x; }
double getY() const { return m_y; }
};
To print an instance, you'd need:
Vector2D velocity{3.5, 2.0};
std::cout << "(" << velocity.getX() << ", " << velocity.getY() << ")";
This is cumbersome. A reusable print() member function helps:
class Vector2D
{
private:
double m_x{};
double m_y{};
public:
Vector2D(double x = 0.0, double y = 0.0)
: m_x{x}, m_y{y}
{}
double getX() const { return m_x; }
double getY() const { return m_y; }
void print() const
{
std::cout << "(" << m_x << ", " << m_y << ")";
}
};
This is better, but has downsides. Since print() returns void, it can't be used mid-expression:
int main()
{
const Vector2D velocity{3.5, 2.0};
std::cout << "Velocity: ";
velocity.print();
std::cout << '\n';
}
It would be cleaner to write:
Vector2D velocity{3.5, 2.0};
std::cout << "Velocity: " << velocity << '\n';
Overloading operator<< makes this possible!
Overloading operator<<
Overloading operator<< is similar to overloading other binary operators, except the parameter types differ.
Consider std::cout << velocity. The operator is <<, the left operand is std::cout (type std::ostream), and the right operand is velocity (type Vector2D). Our overloaded function signature:
// std::ostream is the type of std::cout
friend std::ostream& operator<<(std::ostream& out, const Vector2D& vec);
Implementation is straightforward - use operator<< to output the Vector2D's data members:
#include <iostream>
class Vector2D
{
private:
double m_x{};
double m_y{};
public:
Vector2D(double x = 0.0, double y = 0.0)
: m_x{x}, m_y{y}
{}
friend std::ostream& operator<<(std::ostream& out, const Vector2D& vec);
};
std::ostream& operator<<(std::ostream& out, const Vector2D& vec)
{
// Access members directly (friend function)
out << "(" << vec.m_x << ", " << vec.m_y << ")";
return out; // Return std::ostream for chaining
}
int main()
{
const Vector2D velocity{3.5, 2.0};
std::cout << velocity << '\n';
return 0;
}
This is similar to our print() function, except std::cout becomes parameter out (which references std::cout when called).
The tricky part is the return type. Returning std::ostream by value fails because std::ostream disallows copying.
We return the left parameter by reference. This not only prevents copying, it also enables chaining: std::cout << velocity << '\n'.
How? Due to precedence/associativity, this evaluates as (std::cout << velocity) << '\n'. The first part (std::cout << velocity) calls our overloaded operator<<, which returns std::cout. The expression becomes std::cout << '\n', which then evaluates normally.
Returning the left-hand parameter by reference is safe here - since it was passed by the caller, it still exists when our function returns.
Here's a complete example:
#include <iostream>
class Vector2D
{
private:
double m_x{};
double m_y{};
public:
Vector2D(double x = 0.0, double y = 0.0)
: m_x{x}, m_y{y}
{}
friend std::ostream& operator<<(std::ostream& out, const Vector2D& vec);
};
std::ostream& operator<<(std::ostream& out, const Vector2D& vec)
{
out << "(" << vec.m_x << ", " << vec.m_y << ")";
return out;
}
int main()
{
Vector2D position{5.0, 3.0};
Vector2D velocity{1.5, -0.5};
std::cout << position << " moving at " << velocity << '\n';
return 0;
}
Output:
(5, 3) moving at (1.5, -0.5)
If Vector2D provided public getters, operator<< could be a non-friend.
Overloading operator>>
Overloading the input operator is analogous. std::cin has type std::istream. Here's Vector2D with overloaded operator>>:
#include <iostream>
class Vector2D
{
private:
double m_x{};
double m_y{};
public:
Vector2D(double x = 0.0, double y = 0.0)
: m_x{x}, m_y{y}
{}
friend std::ostream& operator<<(std::ostream& out, const Vector2D& vec);
friend std::istream& operator>>(std::istream& in, Vector2D& vec);
};
std::ostream& operator<<(std::ostream& out, const Vector2D& vec)
{
out << "(" << vec.m_x << ", " << vec.m_y << ")";
return out;
}
// Vector2D must be non-const to be modified
std::istream& operator>>(std::istream& in, Vector2D& vec)
{
// This version subject to partial extraction (see below)
in >> vec.m_x >> vec.m_y;
return in;
}
int main()
{
std::cout << "Enter x and y coordinates: ";
Vector2D point{1.0, 2.0}; // Non-zero test data
std::cin >> point;
std::cout << "You entered: " << point << '\n';
return 0;
}
With input 4.5 7.2:
You entered: (4.5, 7.2)
With invalid input 4.5x 7.2:
You entered: (4.5, 2)
This produces a weird hybrid: one user value (4.5), one unchanged value (2). This is a partial extraction problem.
Guarding Against Partial Extraction
When extracting a single value, extraction either fails or succeeds. With multiple values, partial extraction can occur.
In the invalid input case above: extraction to m_x succeeds (4.5), leaving x 7.2 in the stream. Extraction to m_y fails on x, so m_y gets 0 and the stream enters failure mode.
Partial extraction is never desirable. Sometimes it's dangerous (imagine a denominator getting zero, causing division by zero later).
We can make this transactional - all-or-nothing. Extract to temporary variables first, then update the object only if all extractions succeed:
std::istream& operator>>(std::istream& in, Vector2D& vec)
{
double x{};
double y{};
if (in >> x >> y) // If all extractions succeeded
vec = Vector2D{x, y}; // Update object
return in;
}
Now extraction either fully succeeds or leaves the object unchanged.
`if (in >> x >> y)` is equivalent to:
```cpp in >> x >> y; if (in) ```
For consistency with fundamental types (which are set to 0 on failed extraction), you might want to reset the object to default on failure:
std::istream& operator>>(std::istream& in, Vector2D& vec)
{
double x{};
double y{};
in >> x >> y;
vec = in ? Vector2D{x, y} : Vector2D{};
return in;
}
Handling Semantically Invalid Input
Extraction can fail in different ways. When operator>> fails to extract anything, std::cin automatically enters failure mode, which the caller can check.
But what about extractable but semantically invalid values (e.g., a magnitude that must be positive)? Since extraction succeeded, std::cin won't enter failure mode automatically.
We can manually set failure mode by calling std::cin.setstate(std::ios_base::failbit):
std::istream& operator>>(std::istream& in, Vector2D& vec)
{
double x{};
double y{};
in >> x >> y;
// Check for NaN or infinity
if (std::isinf(x) || std::isinf(y) || std::isnan(x) || std::isnan(y))
in.setstate(std::ios_base::failbit); // Manually set failure mode
vec = in ? Vector2D{x, y} : Vector2D{};
return in;
}
Conclusion
Overloading operator<< and operator>> makes printing and inputting custom classes intuitive and consistent with built-in types.
Quiz
Add overloaded operator<< and operator>> to this Coordinate class. operator>> should prevent partial extraction and fail if either latitude is outside [-90, 90] or longitude is outside [-180, 180].
class Coordinate
{
private:
double m_latitude{};
double m_longitude{};
public:
Coordinate(double lat = 0.0, double lon = 0.0)
: m_latitude{lat}, m_longitude{lon}
{}
// Add operator<< and operator>>
};
int main()
{
Coordinate loc1{};
std::cout << "Enter latitude and longitude: ";
std::cin >> loc1;
Coordinate loc2{};
std::cout << "Enter another location: ";
std::cin >> loc2;
std::cout << "Location 1: " << loc1 << '\n';
std::cout << "Location 2: " << loc2 << '\n';
return 0;
}
Sample run:
Enter latitude and longitude: 40.7128 -74.0060
Enter another location: 51.5074 -0.1278
Location 1: (40.7128, -74.006)
Location 2: (51.5074, -0.1278)
Show Solution
#include <iostream>
class Coordinate
{
private:
double m_latitude{};
double m_longitude{};
public:
Coordinate(double lat = 0.0, double lon = 0.0)
: m_latitude{lat}, m_longitude{lon}
{}
friend std::ostream& operator<<(std::ostream& out, const Coordinate& coord);
friend std::istream& operator>>(std::istream& in, Coordinate& coord);
};
std::ostream& operator<<(std::ostream& out, const Coordinate& coord)
{
out << "(" << coord.m_latitude << ", " << coord.m_longitude << ")";
return out;
}
std::istream& operator>>(std::istream& in, Coordinate& coord)
{
double lat{};
double lon{};
in >> lat >> lon;
// Check for valid ranges
if (lat < -90.0 || lat > 90.0 || lon < -180.0 || lon > 180.0)
in.setstate(std::ios_base::failbit);
if (in)
coord = Coordinate{lat, lon};
return in;
}
int main()
{
Coordinate loc1{};
std::cout << "Enter latitude and longitude: ";
std::cin >> loc1;
Coordinate loc2{};
std::cout << "Enter another location: ";
std::cin >> loc2;
std::cout << "Location 1: " << loc1 << '\n';
std::cout << "Location 2: " << loc2 << '\n';
return 0;
}
Summary
Overloading operator<<: Allows natural output syntax like std::cout << myObject. Takes std::ostream& as first parameter and the object as second, returns std::ostream& by reference to enable chaining.
Overloading operator>>: Enables natural input syntax like std::cin >> myObject. Takes std::istream& as first parameter and non-const object reference as second, returns std::istream& by reference for chaining.
Return by reference: Both I/O operators return their left-hand parameter by reference (not by value) to prevent copying and enable chaining like std::cout << obj1 << obj2.
Partial extraction problem: When extracting multiple values, extraction can fail partway through, leaving some members updated and others not. This produces inconsistent object state.
Transactional extraction: Extract to temporary variables first, then update the object only if all extractions succeed. This makes extraction all-or-nothing.
Semantic validation: For extractable but semantically invalid values (like coordinates outside valid ranges), manually set failure mode using in.setstate(std::ios_base::failbit) so callers can detect the error.
Overloading I/O operators makes custom classes work naturally with streams, providing the same intuitive syntax as built-in types. The key considerations are returning by reference for chaining, preventing partial extraction for safety, and validating semantic constraints beyond just type compatibility.
Custom Stream Input/Output - Quiz
Test your understanding of the lesson.
Practice Exercises
Overloading I/O Operators
Implement operator<< and operator>> for custom classes to enable seamless integration with std::cout and std::cin. Practice proper stream handling.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!