Skip to content

The Curiously Recurring Template Pattern (CRTP) and Static Polymorphism

Beginning of the lesson

The Curiously Recurring Template Pattern (CRTP) and Static Polymorphism

Compile-time dispatch as a zero-overhead alternative to virtual functions

Big Idea: The Curiously Recurring Template Pattern is an idiom in which a class derives from a base template parameterised by the derived class itself: class Widget : public Base<Widget>. Because Base knows the exact derived type at compile time, it can call the derived class's methods directly through a static_cast, with no virtual table, no indirect call, and full inlining. This turns polymorphism into a compile-time decision.
By the end you will be able to:
  • explain the runtime cost of virtual dispatch and when it is worth avoiding,
  • read and write the CRTP shape and understand why the static_cast is safe here,
  • use CRTP to build static interfaces, inject shared behaviour as mixins, and generate boilerplate operators,
  • weigh compile-time dispatch against runtime dispatch and pick the right tool honestly.

1. Recap: what runtime polymorphism costs

You already know virtual functions from the beginner and intermediate courses. When you mark a method virtual and call it through a base pointer or reference, the compiler cannot know the concrete type, so it dispatches at run time through a virtual table (vtable): each polymorphic object carries a hidden vptr that points at its class's table of function addresses.

That flexibility is not free. Every virtual call is an indirect call through a pointer the compiler cannot usually predict, which means:

  • the target is not known at compile time, so the call cannot be inlined and the optimiser cannot see through it;
  • each object grows by one pointer (vptr), which matters for small, numerous objects;
  • the indirection can cost a branch mispredict or cache miss in hot loops.
struct Shape {
    virtual double area() const = 0;   // resolved at run time via the vtable
    virtual ~Shape() = default;
};

double totalArea(const std::vector<Shape*>& v) {
    double s = 0;
    for (const Shape* p : v)
        s += p->area();                 // indirect call, cannot be inlined
    return s;
}

For most code this cost is negligible and the flexibility is exactly what you want. But when the concrete type is already known at compile time - a numeric kernel, an expression template, a container adaptor - paying for dynamic dispatch is pure waste. CRTP lets you keep the "call the derived implementation" shape while resolving it at compile time.

Continue this lesson on YourAIZone

The full lesson has the complete explanation, worked examples, common mistakes, tests with instant checking and an AI tutor. Start right away as a guest, or create an account to keep your progress.

In the full lesson

  • The complete explanation
  • Worked examples
  • Common mistakes and how to avoid them
  • Test questions: 15
  • AI tutor that answers questions about the topic
  • Progress tracking for students, parents and teachers