1

I have a code sample which behaves strange for me. With inheritance in C++ one can declare array of pointers to base class with pure virtual function (aka Interface) and call derived member functions over it;

class Base {

public:

    virtual void call() = 0;

};

class Derived1 : public Base {

public:

    void call() override final {

        std::wcout << L"derived 1" << std::endl;

    }

};

class Derived2 : public Base {

public:

    void call() override final {

        std::wcout << L"derived 2" << std::endl;

    }

};

int main() {

    Base* b[2];    

    b[0] = new Derived1;
    b[1] = new Derived2;

    for (int i = 0; i < 2; ++i) {

        b[i]->call();

    }

    return 0;

}

Which gives:

derived 1
derived 2

just as planned. But when I trying following code sample it makes me a little bit confusing:

class Base {

public:

    virtual Base* print() = 0;

    template<typename T>
    Base& operator<<(const T &_val) {

        std::wcout << L" d0 << " << _val;
        return *this;

    }

};

class Derived1 : public Base {

public:

    Derived1* print() override final {

        return this;

    }

    template<typename T>
    Derived1& operator<<(const T &_val) {

        std::wcout << L" d1 << " << _val;
        return *this;

    }

};

class Derived2 : public Base {

public:

    Derived2* print() override final {

        return this;

    }

    template<typename T>
    Derived2& operator<<(const T &_val) {

        std::wcout << L" d2 << " << _val;
        return *this;

    }

};

int main() {

    Base* b[2];

    b[0] = new Derived1;
    b[1] = new Derived2;

    for (int i = 0; i < 2; ++i) {

        std::wcout << typeid(*b[i]->print()).name();
        *b[i]->print() << 7 << 7;
        std::wcout << std::endl;

    }

    return 0;

}

The output is:

8Derived1 d0 << 7 d0 << 7
8Derived2 d0 << 7 d0 << 7

Which means that only Base's operator<< was called (But prints() return types seems to be correst).

The question is why it behaves like so?

UPD:


Seems like I need static polymorphism here without virtual functions. But how could this be achieved? I need an array of different Derived classes to perform actions in operator<< on any data type. UPD2:


Looks like I can use type erasure for operator<< parameter. But how can I restore type inside derived's operator<< then? (For example if I suggest to use boost::any)

Ihor Baklykov
  • 543
  • 7
  • 24
  • 2
    Do you really think all that vertical whitespace is adding anything to the readability of your code? –  Apr 10 '18 at 21:30
  • 2
    "The question is why it behaves like so?" Because that is what you programmed it to do. The templated `operator<< ` is not virtual. – Eljay Apr 10 '18 at 21:34
  • @NeilButterworth it's just code style I prefer. Didn't found in rules any suggestions about such code formatting. – Ihor Baklykov Apr 10 '18 at 21:37
  • It means we (and you) have to do a lot of scrolling up and down to try to understand your code. It's ugly, and not a good idea. –  Apr 10 '18 at 21:39
  • @Eljia yeah, because virtual template member is impossible in C++. But print() return types are Derived1 and Derived2, so proper operators << expected to be called, aren't they? – Ihor Baklykov Apr 10 '18 at 21:40
  • 3
    @NeilButterworth I prefer more vertical whitespace than most programmers (although admittedly not this much). It's still readable, though, and unless SO makes a required coding style for questions and answers it isn't worth complaining about. – Daniel H Apr 10 '18 at 21:40
  • @NeilButterworth I saw here questions with bigger code examples and no suggestions about formatting. So please, answer my question if you can – Ihor Baklykov Apr 10 '18 at 21:41
  • @Daniel "It's still readable," - yes, if I put a lot of effort into it - but why would I bother? If the vertical whitespace were removed, I wouldn't have to put that effort in. –  Apr 10 '18 at 21:43
  • "I saw here questions with bigger code examples and no suggestions about formatting" - you didn't see examples that triple the size of the code with needless vertical whitespace. Whenever posting here, you should try to make your code as clear as possible, which generally means making it as short as possible. –  Apr 10 '18 at 21:46
  • 1
    @NeilButterworth The indentations all line up and the vertical whitespace is used consistently. I actually find the unnecessary use of wide strings more distracting because I rarely see that syntax (since wide strings aren't very portable and you should usually prefer the explicit Unicode types). – Daniel H Apr 10 '18 at 21:46

1 Answers1

2

Your operator << () is not virtual, and so, if you call this operator of a base class pointer, always the base class implementation is called.

That's because it's a template method, and template methods can't be virtual (see Can a C++ class member function template be virtual?).

Solution would be to write virtual specialized operator << () for each supported data type (if you really want them to differ between inherited classes).

Here's the changed code:

class Base {

public:

    virtual Base* print() = 0;

    virtual Base& operator<<( int _val ) = 0;

};

class Derived1 : public Base {

public:

    Derived1* print() override final {

        return this;

    }

    Base& operator<<( int _val ) override final {

        std::wcout << L" d1 << " << _val;
        return *this;

    }

};

class Derived2 : public Base {

public:

    Derived2* print() override final {

        return this;

    }

    Base& operator<<( int _val ) override final {

        std::wcout << L" d2 << " << _val;
        return *this;

    }

};

int main() {

    Base* b[2];

    b[0] = new Derived1;
    b[1] = new Derived2;

    for (int i = 0; i < 2; ++i) {

        std::wcout << typeid(*b[i]->print()).name();
        *b[i]->print() << 7 << 7;
        std::wcout << std::endl;

    }

    return 0;

}

Another approach would be, to only implement the differing parts via class hierarchy (here: the class name), and to use the template operator for generic calls.

#include <iostream>
#include <string>

class Base {

public:

    virtual Base* print() = 0;
    virtual const wchar_t* className() const = 0;

    template<typename T>
    Base& operator<<(const T &_val) {

        std::wcout << L" " << className() << L" << " << L" << " << _val;
        return *this;

    }

};

class Derived1 : public Base {

public:
    const wchar_t* className() const override final { return L"d1"; }

    Derived1* print() override final {

        return this;

    }

};

class Derived2 : public Base {

public:
    const wchar_t* className() const override final { return L"d2"; }

    Derived2* print() override final {

        return this;

    }

};

int main() {

    Base* b[2];

    b[0] = new Derived1;
    b[1] = new Derived2;

    for (int i = 0; i < 2; ++i) {

        std::wcout << typeid(*b[i]->print()).name();
        *b[i]->print() << 7 << 7;
        std::wcout << std::endl;

    }

    return 0;

}

The last approach would be to RTTI cast according to the class type, and call the specialized operator << () according to the class type.

#include <iostream>

class Derived1;
class Derived2;

class Base {

public:

    virtual Base* print() = 0;


};

class Derived1 : public Base {

public:

    Derived1* print() override final {

        return this;

    }

    template<typename T>
    Derived1& operator<<(const T &_val) {

        std::wcout << L" d1 << " << _val;
        return *this;

    }

};

class Derived2 : public Base {

public:

    Derived2* print() override final {

        return this;

    }

    template<typename T>
    Derived2& operator<<(const T &_val) {

        std::wcout << L" d2 << " << _val;
        return *this;

    }

};

template<typename T>
Base& operator<<( Base& _base, const T &_val) {

    if( typeid( _base ) == typeid( Derived1 ))
        return dynamic_cast<Derived1*>(&_base)->operator << (_val);
    else
        return dynamic_cast<Derived2*>(&_base)->operator << (_val);
}

int main() {

    Base* b[2];

    b[0] = new Derived1;
    b[1] = new Derived2;

    for (int i = 0; i < 2; ++i) {

        std::wcout << typeid(*b[i]->print()).name();
        *b[i]->print() << 7 << 7;
        std::wcout << std::endl;

    }

    return 0;

}
user2328447
  • 1,807
  • 1
  • 21
  • 27
  • 1
    Yep. And if the desire is to use static polymorphism via the magic of template metaprogramming, then the class hierarchy is not necessary. – Eljay Apr 10 '18 at 21:36
  • Seems like so, but how can I achieve static polymorphism in this example? I need an array of derived classes. – Ihor Baklykov Apr 10 '18 at 21:43
  • But what if i need any type, not only `int`? – Ihor Baklykov Apr 10 '18 at 21:50
  • 1
    You will have to write a specialized implementation for each supported data type... or you would have to "mix" the template and class hierarchy approach... wait, I'll post another example. – user2328447 Apr 10 '18 at 21:52
  • @user2328447 specialized implementation for any type is not an option for me – Ihor Baklykov Apr 10 '18 at 21:54
  • Your suggestion #2 is not an option too, sorry. I need something close to my first example to being able to call different operator<< for different derived classes for any data types – Ihor Baklykov Apr 10 '18 at 22:07
  • I'm planning to use derived1 to output in cout, for example. And derived2 to output in some file. That's why I need different operator<< behavior for every derived – Ihor Baklykov Apr 10 '18 at 22:23
  • 1
    Then you will have to cast... wait, I'll post the last example, I'm going out of options ;) – user2328447 Apr 10 '18 at 22:26
  • Yeah, this looks like what I expected! But are there some disadvantages of using RTTI? – Ihor Baklykov Apr 10 '18 at 22:32
  • 1
    To be honest, I never used builtin RTTI. Usually, when I need RTTI, I just use a simple virtual class type enum getter... this is pretty cheap, and I never needed more. But there are a lot of Q&A here regarding RTTI, just search for it. – user2328447 Apr 10 '18 at 22:34