CRTP Deep Dive: C++ Static Polymorphism


The Paradox of Recursive Inheritance

Inheritance is slow. Recursion is confusing. So naturally, C++ developers decided to combine them into one of the most terrifying idioms in the language: the Curiously Recurring Template Pattern (CRTP).

To the uninitiated, it looks like a typo:

// The recursive definition
class Derived : public Base<Derived> { ... };

A class inheriting from a template of itself? It feels illegal. It feels like an infinite loop. But it’s actually the secret sauce for static polymorphism.

Unlike Java or C# (or standard C++ virtual functions), CRTP resolves everything at compile time. No vtables. No indirect jumps. Just raw, inlined speed.

Mechanics and Fundamentals

The engine of CRTP is the static_cast.

In traditional inheritance, the base class doesn’t know about the derived class. In CRTP, the base class assumes it knows the derived type and casts itself to it.

template <typename Derived>
class Base {
public:
    void interface() {
        // The compile-time dispatch
        static_cast<Derived*>(this)->implementation();
    }
};

This cast has zero runtime overhead. It’s just a directive to the compiler: “Trust me, I know what I’m doing.”

The Performance Landscape: Static vs. Dynamic

Why do we do this? Why not just use virtual functions?

Because virtual functions are slow.

  1. Memory Load: Fetch the vptr.
  2. Table Lookup: Find the function address.
  3. Indirect Jump: Jump to the address (goodbye, branch predictor).

In CRTP, the compiler sees everything. It devirtualizes the call. It inlines the code. The final machine code looks exactly as if you manually wrote the logic in the main loop. It is the definition of a Zero-Cost Abstraction.

Use Cases: When CRTP is Useful

1. Static Interfaces

Imagine a trading engine supporting NASDAQ, NYSE, and CME. You want a common interface OrderGateway, but you can’t afford a virtual call on every order.

CRTP lets you define a rigid interface that collapses into direct function calls at compile time.

2. Mixins (Functionality Injection)

Implementing !=, >, <=, >= is boring. With CRTP, you just implement < and ==, and a Comparable<T> mixin generates the rest for you.

template <typename Derived>
struct Comparable {
    friend bool operator!=(const Derived& lhs, const Derived& rhs) {
        return !(lhs == rhs);
    }
    // ...
};

It’s like multiple inheritance, but useful.

The Future: C++23 and “Deducing This”

Here’s the funny part: after decades of using CRTP, C++23 basically killed it.

A new feature called Explicit Object Parameters (“Deducing This”) allows you to deduce the derived type without the template inheritance hack.

struct Base {
    // 'Self' is deduced to be the actual derived type
    template <typename Self>
    void func(this Self&& self) {
        self.impl();
    }
};

It’s cleaner. It handles const and rvalue references automatically. It’s the future.

Conclusion

CRTP is quintessential C++: powerful, dangerous, and unapologetically focused on performance.

It’s not for everyone. If you use it for a GUI button handler, you’re over-engineering. But if you’re building a high-frequency trading system or a game engine, it’s a vital tool.

Use it. But maybe wait for C++23 if you can.