9.3 — Overloading the I/O operators

For classes that have multiple member variables, printing each of the individual variables on the screen can get tiresome fast. For example, consider the following class:

If you wanted to print an instance of this class to the screen, you’d have to do something like this:

Of course, it makes more sense to do this as a reusable function. And in previous examples, you’ve seen us create print() functions that work like this:

While this is much better, it still has some downsides. Because print() returns void, it can’t be called in the middle of an output statement. Instead, you have to do this:

It would be much easier if you could simply type:

and get the same result. No having to break up output across multiple statements, and no having to remember what you named the print function.

Fortunately, by overloading the << operator, you can!

Overloading operator<<

Overloading operator<< is similar to overloading operator+ (they are both binary operators), except that the parameter types are different.

Consider the expression std::cout << point. If the operator is <<, what are the operands? The left operand is the std::cout object, and the right operand is your Point class object. std::cout is actually an object of type std::ostream. Therefore, our overloaded function will look like this:

Implementation of operator<< for our Point class is fairly straightforward -- because C++ already knows how to output doubles using operator<<, and our members are all doubles, we can simply use operator<< to output the member variables of our Point. Here is the above Point class with the overloaded operator<<.

This is pretty straightforward -- note how similar our output line is to the line in the print() function we wrote previously. The most notable difference is that std::cout has become parameter out (which will be a reference to std::cout when the function is called).

The trickiest part here is the return type. With the arithmetic operators, we calculated and returned a single answer by value (because we were creating and returning a new result). However, if you try to return std::ostream by value, you’ll get a compiler error. This happens because std::ostream specifically disallows being copied.

In this case, we return the left hand parameter as a reference. This not only prevents a copy of std::ostream from being made, it also allows us to “chain” output commands together, such as std::cout << point << std::endl;

You might have initially thought that since operator<< doesn’t return a value to the caller that we should define the function as returning void. But consider what would happen if our operator<< returned void. When the compiler evaluates std::cout << point << std::endl;, due to the precedence/associativity rules, it evaluates this expression as (std::cout << point) << std::endl;. std::cout << point would call our void-returning overloaded operator<< function, which returns void. Then the partially evaluated expression becomes: void << std::endl;, which makes no sense!

By returning the out parameter as the return type instead, (std::cout << point) returns std::cout. Then our partially evaluated expression becomes: std::cout << std::endl;, which then gets evaluated itself!

Any time we want our overloaded binary operators to be chainable in such a manner, the left operand should be returned (by reference). Returning the left-hand parameter by reference is okay in this case -- since the left-hand parameter was passed in by the calling function, it must still exist when the called function returns. Therefore, we don’t have to worry about referencing something that will go out of scope and get destroyed when the operator returns.

Just to prove it works, consider the following example, which uses the Point class with the overloaded operator<< we wrote above:

This produces the following result:

Point(2, 3.5, 4) Point(6, 7.5, 8)

Overloading operator>>

It is also possible to overload the input operator. This is done in a manner analogous to overloading the output operator. The key thing you need to know is that std::cin is an object of type std::istream. Here’s our Point class with an overloaded operator>>:

Here’s a sample program using both the overloaded operator<< and operator>>:

Assuming the user enters 3.0 4.5 7.26 as input, the program produces the following result:

You entered: Point(3, 4.5, 7.26)


Overloading operator<< and operator>> make it extremely easy to output your class to screen and accept user input from the console.

Quiz time

Take the Fraction class we wrote in the previous quiz (listed below) and add an overloaded operator<< and operator>> to it.

The following program should compile:

And produce the result:

Enter fraction 1: 2/3
Enter fraction 2: 3/8
2/3 * 3/8 is 1/4

Here’s the Fraction class:

Show Solution

9.4 -- Overloading operators using member functions
9.2a -- Overloading operators using normal functions

158 comments to 9.3 — Overloading the I/O operators

  • Benjamin

    And hi again,

    I am playing around with classes and overloading operators. Within 10 minutes I encountered like 2 things, I do not know how to solve or how to solve them best. As they span different lessons, I decided to put them here, which is as far as i read until now:

    I wrote a little class to represent complex numbers. As of now, I overloaded the "+" and "<<" operator. Each of which works fine. The following code compiles without any problem:

    Now the problems:

    1) It seems I cannot simplify my code as

    It throws the error "cannot bind ‘std::ostream {aka std::basic_ostream<char>}’ lvalue to ‘std::basic_ostream<char>&&’"

    Why is that?

    2) If I want to add one of my Complex numbers to an anonymous object like

    I can’t use the overloaded operator "+" as I wrote it, because it accepts a reference to a Complex-object. I understand that. How can I tell the compiler to use a reference, if possible, and the value, if a reference cannot be provided? I first thought I can use different prototypes for "operator+", but this leads to an ambiguity (How is the compiler supposed to know, whether to copy the value or take the reference, if I pass a variable?). Overloading "+" with just one function that accepts values seems unsatisifying to me. I like passing by reference 🙂

    • nascardriver

      Hi again Benjamin!
      I fixed your code by adding the const keyword (Lesson 8.10) wherever no modifications to the objects are made.

      Unfortunately I cannot tell you why this fixed it, so you’ll have to wait for Alex or someone else to give you an explanation.

    • Alex

      Nascardriver hit upon the correct solution, even if he didn’t understand why.

      The short answer is this:

      Parameter number is a non-const reference. Non-const references can only bind to l-values (not r-values). So this works when you pass in r (which is an l-value), but not y + z (which is an r-value temporary).

      To fix this, just make number a const reference. Const references can bind to both l-values and r-values, thus solving your issue.

      The same goes for your operator+ -- Complex{3,4} is an r-value, and your operator+ is taking non-const reference parameters. Making the parameters const will allow you to add both l-values and r-values.

      Const is really important to use appropriately with references, so make sure you understand this before proceeding. 🙂

      • Benjamin

        Thanks for both of your answers. Reading, re-reading and also re-re-reading the lessons plus solving the quizes and re-solving the quizes and also re-re- … well no, i better stop here now. Point is, the more I read, the more I grasp the concepts.

        Given the following summary, did I get the core of it right?

        1. Chapter 1.3:

        - "An r-value refers to values that are not associated with a persistent memory address."

        => references can not refer to an address that doesn’t exist, explaining why passing in an expression and/or an anonymous object, as I tried, didn’t work. But:

        2. Chapter 6.11a:

        - "However, when a reference to a const value is initialized with an r-value, the lifetime of the r-value is extended to match the lifetime of the reference."
        - "References to const values are particularly useful as function parameters because of their versatility. A const reference parameter allows you to pass in […] a literal, or the result of an expression:"

        => const references are capable of taking literals, expressions etc. In that case the corresponding r-value’s lifetime is expanded to the lifetime of the const reference.

        Therefore, the rule that you mentioned a 1000 times: const everything that doesn’t need to be non-const for a reason.

  • Luhan

    Could you explain a little bit further about how cout is an object which get all the information with the operator <<, and do the output?

    • Alex

      std::cout is an object that is provided by the C++ standard to do console output. std::cout knows how to output fundamental types (e.g. int, float) and C-style strings. How exactly it does that, I don’t know.

      But for our custom classes, we can override operator<< and then leverage the things std::cout already knows how to do to make it output what we want our class to output.

  • Angmar

    Hi, I ran into a peculiar situation and the error message is not making sense at all. In line 48, if I save a fraction from the the return value of the overloaded ‘*’ operator, cout seems to be okay with it. However I can’t directly use temporary value obtained from f1*f2. I wonder why is that?

    • Alex

      The problem here is that f3 is an l-value, and f1 * f2 is an r-value. You overloaded operator<&lt to take a Fraction by reference. References can only bind to l-values. If you update your overloaded operator to take const reference Fraction parameter, then it should work.

  • PierreLaCroix

    why is it not possible to do the operator overloading as oneliner like this:

    I tried that in my own code and for ostream it works as oneliner but for oFstream it doesnt:

    The error i get is:
    …..cpp:64: Fehler: invalid initialization of reference of type ‘std::ofstream& {aka std::basic_ofstream<char>&}’ from expression of type ‘std::basic_ostream<char>’
         return out << myObj.stringmember;

    btw this site is awesome!

    • Alex

      I’m not sure. I tried your Point example above in Visual Studio 2017 and it compiled and ran fine. Do you get the same error in the Point case?

      • PierreLaCroix

        Yes I do…

        Copied your point code, ran it & it worked and then added this:

        main.cpp:29: Fehler: invalid initialization of reference of type ‘std::ofstream& {aka std::basic_ofstream<char>&}’ from expression of type ‘std::basic_ostream<char>’
             return out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ")";

        Btw I use Qt Creator…

        • Alex

          Sorry, I missed the detail that you were trying to do this with ofstream, not ostream.

          The class hierarchy for file streams is: ios_base <- ios <- ostream <- ofstream std::cout is of type ostream. Therefore, when you try and override operator<< using ofstream (instead of ostream), when you pass in std::cout, it fails, because an ostream can't be converted to an ofstream (which is a superclass of ostream).

  • Tamara

    Hello, I’ve one question.
    I’ve stumbled on a piece of code which, when overloading the operators >> or <<, inside the function body, instead of the the reference in or out directly uses cin or cout. So, it looks like this:
    istream& operator>>(istream&in, const Point& point)
    cin>>"input something";
    return in;
    Or in the case of the operator << it looks like this:
    ostream& operator<<(ostream&in, const Point& point)
    cout<<"print something";
    return cout;

    My question is, does this work and how?

    • Alex

      If the caller was passing in cout for ostream (or cin for istream) anyway, then it’s essentially the same. But if the caller is passing in some other stream (like cerr instead of cout) then this code will still print to cout, which is incorrect.

  • Alan

    Hi, Alex, I got one thing to ask regarding to the quiz.

    Is it better to use in.ignore(32767, ‘/’) than declare a char variable to extract the ‘/’ separator? Also, we can put in.ignore(32767, ‘n’) right after in >> m_denominator. By “better” I mean to handle unwilling situations, but I am not sure whether this will reduce the efficiency or not.

    • Alex

      Yes, it’s better to use ignore than extract a single char, just in case there’s more than one char worth of additional input buffered. You shouldn’t call ignore unless you have a specific reason to. Otherwise it’s just extraneous.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter