CRTP - Curiously Recurring Template Pattern

Author: thothonegan
Tags: crtp development c++

Lets talk about an interesting thing in C++ : CRTP.

CRTP is the Curiously Recurring Template Pattern. At its simplest, its having a template use the current class as a parameter. e.g.

template 
class Parent
{};

class Child : public Parent
{};

Why is this useful? One major case is 'static polymorphism'. First, we have normal polymorphism which works via virtual in C++.

class Object
{
    public:
        float volume () const { return v_volume; }

    private:
        virtual float v_volume () const = 0; // returns the volume of the object
};

class Cube final : public Object
{
    public:
        float width, height, depth;
    private:
        float v_volume () const override { return width * height * depth; }
};

This allows you to do two things.

  1. All subclasses of Object are required to override v_volume to be used.
  2. If you have an Object* or an Object&, you can call volume without knowing what type of object it is.

And it comes with some minimal costs:

  1. Any call to v_volume has to go through the vtable, which costs a little performance. This also prevents inlining.

But this is C++. We like having our cake, and eating it too. So is there a way we can get some of the benefits without any of the costs? Namely, can we enforce child classes to meet an interface, without paying any cost? Lets do the same with CRTP.

template 
class Object
{
    #define RTHIS() static_cast (this);
    public:
        float volume () const { return RTHIS()->r_volume(); }
        
        /* we assume child class provides r_volume() */
    #undef RTHIS
};

class Cube : public Object
{
    public:
        float width, height, depth;
        
    private:
        /* CRTP function - called by our parent class */
        float r_volume () const { return width * height * depth; }
};

So now it acts the same as the other, calling cube.volume() will give you the volume. So what have we gained?

  1. Calls the volume are now inlineable. Since the dispatch is at compile time, there can be zero cost just like calling r_volume() normally.
  2. We keep the requirement of meeting the interface.

What have we lost?

  1. Complexity. CRTP is a little more complex to understand compared to normal inheritance.
  2. Base class objects. You cannot have an Object* which dynamically at runtime calls the correct volume function. It must be able to evalute it at compile time, or else.
  3. Multiple overrides. With virtual, you could have a subclass of Cube provide its own v_volume() overriding the one in Cube. You cannot do this in CRTP, unless you inherit from Object again, or turn Cube inself into a template. Which makes things even messier.
  4. Error messages are a bit worse. With virtuals its an easy 'you didnt override this function' error. With CRTP it becomes 'this template is written wrong!' because the compiler can't tell who was supposed to fill it in.

CRTP definitely is a lot more complex then normal virtuals, and is more limited where it can be useful. However, if you're dealing with performance critical code and don't care about the features you lose, CRTP is invaluable. In Wolf for example, PointerInterface is a CRTP class that defines how a pointer must act and is required to be 0 cost compared to a normal pointer. CRTP is a perfect case for this.