Amadeus
Amadeus

Reputation: 10655

CRTP. Trying to understand an given example

While I was trying to understand CRTP, I came across this example, where it is a bit fuzzy to me. I can achieve the same results if I do something simpler like this:

#pragma once
#include <iostream>
template <typename T>
class Base
{
public:
    void method() {
        static_cast<T*>(this)->method();
    }
};

class Derived1 // : public Base<Derived1>   <-- commented inherintance
{
public:
    void method() {
        std::cout << "Derived1 method" << std::endl;
    }
};


class Derived2 // : public Base<Derived2>   <-- commmented inherintance
{
public:
    void method() {
        std::cout << "Derived2 method" << std::endl;
    }
};


#include "crtp.h"
int main()
{
    Derived1 d1;
    Derived2 d2;
    d1.method();
    d2.method();
    return 0;
}

My question is: what is the purpose of CRTP here? After thought a bit I guess this use is to allow something like this:

template<typename T>
void call(Base<T>& x)
{
    x.method();
}

and call it like this

int main()
{
    Derived1 d1;
    Derived2 d2;

   call(d1);
   call(d2)
}

Am I correct?

Upvotes: 0

Views: 229

Answers (2)

Xeren Narcy
Xeren Narcy

Reputation: 875

After thought a bit I guess this use is to allow something like this:

template<typename T>
void call(Base<T>& x)
{
    x.method();
}

and call it like this

int main()
{
    Derived1 d1;
    Derived2 d2;

    call(d1);
    call(d2);
}

You are correct, this is one possible way to use the CRTP example provided.

However it must be noted - as you have - that the example's main() shows a poor use-case as it is given (calling the derived methods directly) since we do not need CRTP or inherited methods for the example to work.


Regarding this:

What vtables truly provide is using the base class (pointer or reference) to call derived methods. You should show how it is done with CRTP here.

To appreciate this it's prudent to explain why we would want polymorphism in the first place. Before anything else that must be understood, or CRTP looks like nothing more than a neat trick with templates.

Your initial guess is spot on - you would want polymorphism when you want to access behavior / a method of a derived class, that overrides (or defines) behavior that we need available via the base class.

In ordinary inheritance like this:

struct A { void method() {} };
struct B : A { void method() {} };
int main () {
    B b;
    b.method();
    return 0;
}

What happens is B::method() is fired, yes, but had we omitted B::method() then it would be actually calling A::method(). In this sense B::method() overrides A::method().

If we wanted to call B::method(), but only had an A object, this is insufficient:

int main () {
    B b;
    A *a = &b;
    a->method(); // calls A::method();
    return 0;
}

There are two ways to achieve this (at least): dynamic polymorphism or static polymorphism.

Dynamic Polymorphism

struct A1 {
    virtual ~A1(){}
    virtual void method() {}
};
struct B1 : A1 {
    virtual ~B1(){}
    virtual void method() override {}
};
int main () {
    B1 b;
    A1 *a = &b;
    a->method(); // calls B1::method() but this is not known until runtime.
    return 0;
}

In detail, because A1 has virtual methods, there exists a special table (the vtable) for this class where a function pointer is stored for every virtual method. This function pointer can be overridden by derived classes (in a sense), which is what every instance of B1 does - sets to it B1::method.

Because this information exists at runtime, the compiler doesn't need to know any more than to look up the function pointer for A1::method and call whatever it points to, whether its A1::method or B1::method. Conversely because it's done at runtime the process tends to be slower than knowing the type beforehand...

Static Polymorphism

The same example re-done in a way that avoids the use of a vtable:

template < class T > struct A2 {
    void method() {
        T *derived_this = static_cast<T*>(this);
        derived_this->method();
    }
};
struct B2 : A2 < B2 > {
    void method() {}
};
int main () {
    B2 b;
    A2<B2> *a = &b; // typically seen as a templated function argument
    a->method(); // calls B2::method() statically, known at compile-time
    return 0;
}

This time we're not using dynamic lookups of anything, the exact same behavior is seen using templates and works with template argument type deduction as per your example function template<class T> call(Base<T>*);

The main difference between the two methods is when the resolution from A::method to B::method using A* type is performed. CRTP allows the compiler to know about this derived method since we're using templates to swap from a base to a derived type - hence static polymorphism. A vtable is employed with virtual functions / methods, and "performs the swap" (wrong but helps to think of it that way) indirectly, by storing a function pointer to the correct class's method.

What @Etherealone was asking the answerer (real word?) is to demonstrate what I've just shown in the above - how CRTP can be used to invoke derived-class methods using a base class pointer, instead of relying on the vtable (a layer of indirection to achieve the same, without knowing the derived type itself in the calling code).

Upvotes: 1

zzn
zzn

Reputation: 2455

Yes, the template function "call" can do the same job. But CRTP sometimes might be the better. usage for example:

     Base<Derived1> *d1 = new Derived1;
     d1->method();

Upvotes: 0

Related Questions