san
san

Reputation: 4254

CRTP versus direct implementation of the function in the "derived"

I am trying to get a better understanding of CRTP. So far my understanding is that it allows one to write a functions like the following.

template <class T>
void foo(Base<T> x ) { x.do_stuff() }

Now, depending on the actual compile time derived object x that is passed to the function foo(), it will do different things.

However, I could have derived the class Derived from Base and masked/shadowed its do_stuff() with a non-virtual but overridden Derived::do_stuff. So when exactly is it correct to use CRTP, rather the simplest non-trivial example which shows the advantage of CRTP over shadowing/masking.

Upvotes: 4

Views: 874

Answers (1)

Alexandre C.
Alexandre C.

Reputation: 56976

The point of the CRTP is to be able to get the derived object's type without virtuality. If you do

struct B { void foo() const; }
struct D : B { void foo() const; }

void bar(const B& x) { x.foo(); }

then bar calls B::foo and not D::foo when you pass a D object, since foo isn't a virtual function. If you want D::foo to be called, then you need either virtual functions, or CRTP.

With the simplest kind of CRTP:

template <typename>
struct B { void foo() const; }

struct D : B<D> { void foo() const; }

template <typename T>
void bar(const B<T>& x)
{
    static_cast<const T&>(x).foo();
}

this calls D::foo() when you pass to bar a D object.

An alternative CRTP trick, which however forces D to provide an implementation for foo, is

template <typename T>
struct B
{
    void foo() const { static_cast<const T*>(this)->foo_impl(); }
    // default implementation if needed
    // void foo_impl() const { ... }
};

struct D : B<D> { void foo_impl() const { ... } };

template <typename T>
void bar(const B<T>& x) { x.foo(); }

but you still need a template parameter for B (so that foo dispatches correctly), and therefore a template bar function.

Also, if you don't do CRTP, you'd better have a virtual destructor, which might add unwanted overhead for lightweight classes which are meant to be completely inlined. With CRTP you will simply write a protected destructor (private destructor + friend T in C++0x).

Upvotes: 6

Related Questions