Search

12.10 — Printing inherited classes using operator<<

By Alex on November 23rd, 2016 | last modified by Alex on September 21st, 2017

Consider the following program that makes use of a virtual function:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

classBase

{

public:

Base(){}

virtualvoidprint()const{std::cout<<"Base";}

};

classDerived:publicBase

{

public:

Derived(){}

virtualvoidprint()constoverride{std::cout<<"Derived";}

};

intmain()

{

Derivedd;

Base&b =d;

b.print();// will call Derived::print()

return0;

}

By now, you should be comfortable with the fact that b.print() will call Derived::print() (because b is pointing to a Derived class object, Base::print() is a virtual function, and Derived::print() is an override).

While calling member functions like this to do output is okay, this style of function doesn’t mix well with std::cout:

1

2

3

4

5

6

7

8

9

10

11

12

#include <iostream>

intmain()

{

Derivedd;

Base&b =d;

std::cout<<"b is a ";

b.print();// messy, we have to break our print statement to call this function

std::cout<<'\n';

return0;

}

In this lesson, we’ll look at how to override operator<< for classes using inheritance, so that we can use operator<< as expected, like this:

1

std::cout<<"b is a "<<b<<'\n';// much better

The challenges with operator<<

Let’s start by overloading operator<< in the typical way:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

#include <iostream>

classBase

{

public:

Base(){}

virtualvoidprint()const{std::cout<<"Base";}

friendstd::ostream&operator<<(std::ostream&out,constBase&b)

{

out<<"Base";

returnout;

}

};

classDerived:publicBase

{

public:

Derived(){}

virtualvoidprint()constoverride{std::cout<<"Derived";}

friendstd::ostream&operator<<(std::ostream&out,constDerived&d)

{

out<<"Derived";

returnout;

}

};

intmain()

{

Baseb;

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

Derivedd;

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

return0;

}

Because there is no need for virtual function resolution here, this program works as we’d expect, and prints:

Base
Derived

Now, consider the following main() function instead:

1

2

3

4

5

6

7

8

intmain()

{

Derivedd;

Base&bref =d;

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

return0;

}

This program prints:

Base

That’s probably not what we were expecting. This happens because our version of operator<< that handles Base objects isn’t virtual, so std::cout << bref calls the version of operator<< that handles Base objects rather than Derived objects.

Therein lies the challenge.

Can we make Operator << virtual?

If this issue is that operator<< isn’t virtual, can’t we simply make it virtual?

The short answer is no. There are a number of reasons for this.

First, only member functions can be virtualized -- this makes sense, since only classes can inherit from other classes, and there’s no way to override a function that lives outside of a class (you can overload non-member functions, but not override them). Because we typically implement operator<< as a friend, and friends aren’t considered member functions, a friend version of operator<< is ineligible to be virtualized. (For a review of why we implement operator<< this way, please revisit lesson 9.4 -- Overloading operators using member functions).

Second, even if we could virtualize operator<< there’s the problem that the function parameters for Base::operator<< and Derived::operator<< differ (the Base version would take a Base parameter and the Derived version would take a Derived parameter). Consequently, the Derived version wouldn’t be considered an override of the Base version, and thus be ineligible for virtual function resolution.

So what’s a programmer to do?

The solution

The answer, as it turns out, is surprisingly simple.

First, we set up operator<< as a friend in our base class as usual. But instead of having operator<< do the printing itself, we delegate that responsibility to a normal member function that can be virtualized!

Here’s the full solution that works:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

#include <iostream>

classBase

{

public:

Base(){}

// Here's our overloaded operator<<

friendstd::ostream&operator<<(std::ostream&out,constBase&b)

{

// Delegate printing responsibility for printing to member function print()

returnb.print(out);

}

// We'll rely on member function print() to do the actual printing

// Because print is a normal member function, it can be virtualized

virtualstd::ostream&print(std::ostream&out)const

{

out<<"Base";

returnout;

}

};

classDerived:publicBase

{

public:

Derived(){}

// Here's our override print function to handle the Derived case

virtualstd::ostream&print(std::ostream&out)constoverride

{

out<<"Derived";

returnout;

}

};

intmain()

{

Baseb;

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

Derivedd;

std::cout<<d<<'\n';// note that this works even with no operator<< that explicitly handles Derived objects

Base&bref =d;

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

return0;

}

The above program works in all three cases:

Base
Derived
Derived

Let’s examine how in more detail.

First, in the Base case, we call operator<<, which calls virtual function print(). Since our Base reference parameter points to a Base object, b.print() resolves to Base::print(), which does the printing. Nothing too special here.

In the Derived case, the compiler first looks to see if there’s an operator<< that takes a Derived object. There isn’t one, because we didn’t define one. Next the compiler looks to see if there’s an operator<< that takes a Base object. There is, so the compiler does an implicit upcast of our Derived object to a Base& and calls the function (we could have done this upcast ourselves, but the compiler is helpful in this regard). This function then calls virtual print(), which resolves to Derived::print().

Note that we don’t need to define an operator<< for each derived class! The version that handles Base objects works just fine for both Base objects and any class derived from Base!

The third case proceeds as a mix of the first two. First, the compiler matches variable bref with operator<< that takes a Base. That calls our virtual print() function. Since the Base reference is actually pointing to a Derived object, this resolves to Derived::print(), as we intended.

You can. But this is poor code for extensibility. Consider what would happen if you had a Derived2 that also derived from Base. If you passed in a Derived2, it will tell you it's a base when it's actually a Derived2. That's why if you need to do this kind of class identification, it's generally better to use a virtual function.

Subclasses don't inherit friend associations (although our operator<&lt is a friend of Base, that operator is not a friend of Derived just because Derived inherits Base).

But that isn't relevant here. What is relevant here is that an object of type Derived can be trivially upcast to an object of type Base, so there is an version of operator<&lt that can matches our function call to std::cout << d.