Dear Readers!

In this post I’m going to discuss the peculiarities of overloading the output operator (or any other similar operator) for derived classes. As easy and straightforward it may seem for standalone classes, as tricky it is for class trees. Let’s examine the whys on the following class hierarchy:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// myclasses.h
...

class Base {
public:
    virtual ~Base() {}
    // other stuff
};

class Derived : public Base {
public:
    // other stuff
};

class Derived2 : public Derived {
public:
    // other stuff
};

C++

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
// myclasses.cpp

#include "myclasses.h"
...

std::ostream& operator<<(std::ostream& os, const Base& b)
{
//     os << b.print();
    os << "I'm a base class";
    return os;
}

std::ostream& operator<<(std::ostream& os, const Derived& b)
{
//     os << b.print();
    os << "I'm a Derived class";
    return os;
}

std::ostream& operator<<(std::ostream& os, const Derived2& b)
{
//     os << b.print();
    os << "I'm a Derived2 class";
    return os;
}

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// main.cpp

#include "myclasses.h"
...

int main()
{
    std::unique_ptr<Base> der = std::make_unique<Derived>();
    std::cout << *der << std::endl;
    
    std::unique_ptr<Base> der2 = std::make_unique<Derived2>();
    std::cout << *der2 << std::endl;
    
    std::unique_ptr<Derived> der3 = std::make_unique<Derived2>();
    std::cout << *der3 << std::endl;
}

Obviously, the above approach is a naive one and naturally doesn’t lead to the desired results, as there is no inheritance of any sort involved in this. The output speaks for itself:

Bash

I'm a base class
I'm a base class
I'm a Derived class

To mitigate this, one could try all sorts of things to put these operator overloads inside the classes and enable inheritance somehow…

C++

1
2
3
4
5
6
7
// compiler rejects: can't be friend and virtual simultaneously
friend virtual std::ostream& operator<<(std::ostream& os, const Base& b);

// compiler rejects: must take exactly one argument
virtual std::ostream& operator<<(std::ostream& os, const Base& b);

// and so on...

…but the bottom line is, that all these will fail with various compiler error messages. Since there is no easy way, if any, to directly use such operator overloads with inheritance, one could try to get the intended results through an indirect approach:

C++

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
class Base {
public:
    virtual ~Base() {}
    
    virtual std::string print() const {
        std::string message {"I'm a base class"};
        return message;
    }
    // other stuff
};

class Derived : public Base {
public:
    std::string print() const override {
        std::string message {"I'm a Derived class"};
        return message;
    }
    // other stuff
};

class Derived2 : public Derived {
public:
    std::string print() const override {
        std::string message {"I'm a Derived2 class"};
        return message;
    }
    // other stuff
};

C++

1
2
3
4
5
6
7
8
9
10
// myclasses.cpp

#include "myclasses.h"
...

std::ostream& operator<<(std::ostream& os, Base& b)
{
    os << b.print();
    return os;
}

On one side a print() function has been introduced into the classes, which follows all the necessary inheritance rules, and on the other side, the operator overload had to be defined only for the base class, which in turn will always call the appropriate print function in the class tree. The results are now what we originally wanted:

Bash

I'm a Derived class
I'm a Derived2 class
I'm a Derived2 class

This is all nice, but one might probably not like having another public function around, especially considering that its existence is purely technical in nature. In other words, having an implementation detail visible is not desirable, so one can cure this problem with some minor adjustments in the header:

C++

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
// myclasses.h

class Base {
public:
    virtual ~Base() {}
    
    friend std::ostream& operator<<(std::ostream& os, Base& b);
    
private:
    virtual std::string print() const {
        std::string message {"I'm a base class"};
        return message;
    }
    // other stuff
};

class Derived : public Base {
public:
    // other stuff

private:
    std::string print() const override {
        std::string message {"I'm a Derived class"};
        return message;
    }
};

class Derived2 : public Derived {
public:
    // other stuff

private:
    std::string print() const override {
        std::string message {"I'm a Derived2 class"};
        return message;
    }
};

By shifting all the print() functions into the private section of the class, and befriending the operator overload we now have an optimal solution, thus the lesson of the day could be the following:

Use delegation for overloading the output operator (and operators alike)