Can I use CRTP with multiple derived classes, and use them polymorphically?
Asked Answered
P

1

6

I have such hierarchy of classes:

template <class Type>
class CrtpBase
{
protected:
    Type& real_this()
    {
        return static_cast<Type&>(*this);
    }
};

template <class ChildType>
class Base : CrtpBase<ChildType>
{
public:
    void foo()
    {
        this->real_this().boo();
    }
};

class Derived1 : public Base<Derived1>
{
public:
    void boo { ... }
};

class Derived2 : public Base<Derived2>
{
public:
    void boo { ... }
};

The thing is, I want to use my classes in this way:

std::vector<Base*> base_vec;
base_vec.push_bach(new Derived1());
base_vec.push_bach(new Derived2());
.........
base_vec[0]->foo();

But this isn't possible, because base class for all derived classes is different (actually Base isn't a type at all, it's template). So, is there a way to use crtp with multiple derived classes, alongside with polymorphism?

Pulverable answered 15/4, 2016 at 8:49 Comment(0)
K
5

Indeed there is, you need to add the appropriate non-template base class too:

class AbstractBase
{
public:
  virtual ~AbstractBase() {}

  virtual void foo() = 0;
};


template <class ChildType>
class Base : CrtpBase<ChildType>, public AbstactBase
{
  void foo() override { this->real_this().boo(); }
};

Then, declare your vector as std::vector<AbstractBase*>.

This does indeed introduce the overhead of dynamic dispatch (which you were probably trying to avoid by using CRTP), but dynamic dispatch is the only way to get runtime polymorphism in C++.

It can still be beneficial, though. For example, if the implementation of foo is shared by all the derived classes, but calls into many different boo-style functions (with each derived class having a different implementation of those), you will only pay the dynamic dispatch cost once when invoking foo, and then all the calls made within foo are dispatched statically, CRTP-style.

On the other hand, if it's just one call to a boo-like function within foo, you may as well make boo virtual, put non-virtual foo into the base, thus getting rid of CRTP. The cost will be the same then: a non-virtual dispatch (foo) and a virtual one (boo).


Side note, you should strongly consider storing smart pointers in the std::vector; owning raw pointers are bad practice.

Ked answered 15/4, 2016 at 8:56 Comment(8)
The all thing I used crtp, was to avoid virtual functions, so what is the meaning of this solution? Introduce crtp class to avoid virtual functions, then use abstract class with virtual function to solve the issue? nonsensePulverable
@Pulverable Well, CRTP is a compile-time solution, which means you have compile-time dispatch based on different types. Then you asked for having the same type, which means runtime dispatch and thus virtual functions. Notice that boo() is still dispatched statically.Ked
@Angew, without crtp also there would be only a need one runtime dispatch, in this case I have one runtime and one static, probably worse than without using crtp. Probably there is no solution to my question, just wanted to check whether there is a hack to this.Pulverable
re your side note, sure, I just was lazy to type some extra words.Pulverable
@Pulverable You can't have your cake and eat it too. If you want runtime polymorphism (and you want, going by the last snippet in the answer) then you need virtual functions at some point. You don't need it in the base hierarchy, you can employ type erasure to use polymorphism non-intrusively.Barbie
@Barbie yes, totally agreePulverable
@Pulverable Whether it's worse or better with CRTP depends on the details. If you have one foo, whose Base implementation calls 20 different boos internally, it's better to use CRTP for the boos and have only foo dispatch virtually. Of course, if it's just one-to-one as in the example, it's probably just extra overhead.Ked
@Angew, yes you are right, this is good tactic actually! In my case it's one to one.Pulverable

© 2022 - 2024 — McMap. All rights reserved.