Overloading the << Operator

Learn how to overload the << operator, so our custom types can stream information directly to the console using std::cout

Ryan McCombe
Updated

So far, we've been using a simple terminal prompt to view the output of our program. Later in this course, we'll switch to rendering graphics using SDL.

But, there are some useful tricks we have that can help us make the most of our terminal for building out quick programs.

We've seen how we can stream numbers and strings to the terminal using the << operator:

std::cout << "Hello World " << 42;

We'd like to be able to do something similar with our custom objects. For example:

#include <iostream>

class Character {/*...*/} int main() { Character Enemy; std::cout << Enemy; }

To do this, we need to overload the << operator to be usable with std::cout as the left operand, and Character as the right operand.

Inspecting that in our IDE, or checking the documentation, would reveal it to be a std::ostream, which is an output stream.

Because std::cout is the left operand, the std::ostream reference needs to be the first argument for our overload, whilst the Character& is the second argument. So, our overload would look like this:

void operator<< (
  std::ostream& Stream,
  const Character& Enemy
) {
  Stream << "Hello World";
}

With that, our code will now compile, with "Hello World" being streamed to the terminal:

#include <iostream>

class Character {/*...*/}
void operator<<(std::ostream&, const Character&) int main() { Character Enemy; std::cout << Enemy; }
Hello World

Chaining << Operators

We want to make two updates to make our overload more useful. Firstly, we want to be able to chain << operators, just like we can when streaming other types. For example, we want to be able to do things like this:

std::cout << MyCharacter << '\n';

To enable this, we need to ensure our overload function returns a reference to the stream we're writing to. So, we update the return type of our overload, and return the stream that was used as the left operand:

std::ostream& operator<<(
  std::ostream& Stream,
  const Character& Enemy
) {
  Stream << "Hello World";
  return Stream;
}

With that, we can now chain additional << operators after streaming a Character object:

#include <iostream>

class Character {/*...*/}
void operator<<(std::ostream&, const Character&) int main() { Character Enemy; std::cout << Enemy << "!!!"; }
Hello World!!!

Serializing Objects

Secondly, we want our Character output to generate some meaningful output, so we can stream it rather than "Hello World".

Representing the state of objects as a simple string is sometimes referred to as serialization. We'll see more of this in the next course, as it unlocks features like saving files to the user's hard drive and communicating over the internet, enabling programs like multiplayer games.

For now, we can just create a simple function that generates a string that stores some info about our object, in this format:

[Level] Name (Health / MaxHealth)

To accomplish this, we could add a public function that uses the std::format techniques we covered in the previous lesson:

class Character {
public:
  std::string Serialize() const {
    return std::format(
      "[Level {}] {} ({} / {})",
      Level, Name, Health, MaxHealth);
  }
private:
  std::string Name { "Goblin Warrior" };
  int Level { 15 };
  int MaxHealth { 150 };
  int Health { 90 };
};

Finally, let's update our overload to use this function:

std::ostream& operator<<(
  std::ostream& Stream,
  const Character& Enemy
) {
  Stream << Enemy.Serialize();
  return Stream;
}

With that, everything is in place. Developers can now easily stream information about our objects to std::cout like any other type:

#include <iostream>

class Character {/*...*/}
void operator<<(std::ostream&, const Character&) int main() { Character Enemy; std::cout << Enemy; }
[Level 15] Goblin Warrior (90 / 150)

Summary

In this lesson, we explored how to overload the << operator in C++, enabling us to stream custom objects directly to the console using std::cout. Through practical examples, we demonstrated chaining operators and serializing objects for more effective output. Key topics included:

  • Understanding the concept and implementation of overloading the << operator for user-defined types.
  • Learning how to make the overloaded operator compatible with std::cout by using std::ostream& and a reference to our custom type as parameters.
  • Exploring the technique of chaining << operators by returning a reference to the stream.
  • An introduction to object serialization, through implementing a Serialize method in our class.
Next Lesson
Lesson 58 of 60

System Calls and Terminal Management

This lesson introduces system calls, and how to use them to clear the terminal

Have a question about this lesson?
Answers are generated by AI models and may not have been reviewed for accuracy