CRTP (Curiously Recurring Template Pattern)


  • Description: A class derives from a template instantiated with itself (class D : public Base<D>), giving the base static access to the derived type — enables zero-overhead static polymorphism, mixins, and the Barton–Nackman trick.
  • My Notion Note ID: K2C-2-24
  • Created: 2026-05-22
  • Updated: 2026-05-22
  • License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io

Table of Contents


1. The Idiom

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

class MyClass : public Base<MyClass> {
public:
    void implementation() { /* ... */ }
};
  • Named by Coplien (1995) after observing it independently invented many times.
  • "Curiously recurring" — Base<MyClass> references MyClass before MyClass is fully defined. Works because the base's member function bodies aren't instantiated until used, so they can call implementation even though MyClass was incomplete when the base was instantiated.
  • C++ template idiom — not really a GoF design pattern, but listed alongside Strategy / Template Method / Policy-Based Design because it solves overlapping problems.

2. Why It Works

  • Base<MyClass> is a complete type when MyClass parses its base-clause: the class template definition of Base is known, even though MyClass itself is still incomplete.
  • Member function bodies of Base are lazily instantiated at point of use; by then MyClass is complete, so static_cast<Derived*>(this)->implementation() is valid.
  • Compiler sees the concrete derived type at compile time → no virtual dispatch → inlinable.

A common mistake: putting a Derived member (not method body) into the base:

template <typename Derived>
class Base {
    Derived d_;          // ERROR: Derived is incomplete here
};

Derived d_ requires Derived's size at class-definition time — but Derived isn't complete yet. Method bodies escape this because they're instantiated later.


3. Static Polymorphism

  • Replace virtual dispatch with compile-time dispatch. Same shape as the Template Method Pattern pattern, no vtable cost.
#include <iostream>

template <typename Derived>
class Shape {
public:
    void draw() const { static_cast<const Derived*>(this)->drawImpl(); }
    double area() const { return static_cast<const Derived*>(this)->areaImpl(); }
};

class Circle : public Shape<Circle> {
public:
    explicit Circle(double r) : r_(r) {}
    void drawImpl()   const { std::cout << "circle r=" << r_ << "\n"; }
    double areaImpl() const { return 3.14159 * r_ * r_; }
private:
    double r_;
};

class Square : public Shape<Square> {
public:
    explicit Square(double s) : s_(s) {}
    void drawImpl()   const { std::cout << "square s=" << s_ << "\n"; }
    double areaImpl() const { return s_ * s_; }
private:
    double s_;
};

template <typename S>
void render(const Shape<S>& s) { s.draw(); }   // resolved at compile time

int main() {
    Circle c{1.5}; render(c);
    Square q{2.0}; render(q);
}
  • No std::vector<Shape*>Shape<Circle> and Shape<Square> are unrelated types. Heterogeneous polymorphism requires std::variant or a virtual base.

4. Mixins

Mixins inject capabilities into a class. Each mixin is a CRTP base that adds members + free functions using the derived's interface.

#include <iostream>

template <typename Derived>
struct Printable {
    void print(std::ostream& os) const { os << static_cast<const Derived&>(*this).toString(); }
};

template <typename Derived>
struct Comparable {
    friend bool operator!=(const Derived& a, const Derived& b) { return !(a == b); }
    friend bool operator< (const Derived& a, const Derived& b) { return a.compare(b) <  0; }
    friend bool operator> (const Derived& a, const Derived& b) { return b <  a; }
    friend bool operator<=(const Derived& a, const Derived& b) { return !(b < a); }
    friend bool operator>=(const Derived& a, const Derived& b) { return !(a < b); }
};

class Version : public Printable<Version>, public Comparable<Version> {
public:
    int compare(const Version& o) const;
    bool operator==(const Version& o) const;
    std::string toString() const;
};

Version implements one primitive per axis (compare, ==, toString); the mixins fill in the rest at compile time.

C++20 <=> (three-way comparison) replaces the comparable mixin: declare auto operator<=>(const Version&) const = default; and all six relational operators come for free.


5. Barton–Nackman Trick

  • Barton & Nackman, Scientific and Engineering C++ (1994).
  • Define a non-member function (typically an operator) as a friend inside a CRTP base class. The friend is injected into the enclosing namespace only when the template is instantiated, exactly once per concrete derived type → no ODR collisions, automatic ADL discovery.
template <typename Derived>
struct AddableMixin {
    friend Derived operator+(Derived a, const Derived& b) { a += b; return a; }
    // Defined inline; one definition per Derived instantiation.
};

class Bignum : public AddableMixin<Bignum> {
public:
    Bignum& operator+=(const Bignum& other);
};

Bignum x, y;
auto z = x + y;       // Found via ADL; uses the friend injected by AddableMixin<Bignum>.
  • Historically the only way to "add" a free operator from a base in pre-C++98 days. Still appears in heavy template libraries (Boost.Operators originally used this trick).
  • Modern alternative: free function template constrained by a concept, or <=> for comparisons.

6. Real-World Examples

  • std::enable_shared_from_this<T> — CRTP base that gives T a shared_from_this() returning std::shared_ptr<T> to itself. The base must know T to type the returned pointer.
class Session : public std::enable_shared_from_this<Session> {
public:
    void start() { auto self = shared_from_this(); /* ... */ }
};
  • std::ranges::view_interface<View> — supplies empty(), front(), back(), operator[], operator bool from the user-implemented begin()/end() of View.
  • Eigen / Blaze / xtensor — expression templatesMatrixBase<Derived> lets Matrix + Matrix return a lightweight Sum<MatrixA, MatrixB> that gets evaluated lazily into the destination matrix. Removes temporaries; the entire expression is one fused loop.
  • Boost.Iterator iterator_facade<Derived, ...> — derive + implement a few primitives (dereference, increment, equal), get a full iterator interface.
  • LLVM ilist_node, User, etc. — many internal helper bases.

7. Pitfalls

  • Object slicingBase<D> b = d; silently slices and the static cast inside Base then misbehaves. CRTP bases should be non-copyable or only used as base subobjects.

  • Multiple-instantiation bloat — each derived class gets its own copy of every base method. Acceptable for small bases; problematic if base has lots of code → consider out-of-line helpers in a non-template implementation file.

  • Private members in Derived — base can't call them via the static cast unless it's a friend. Private base + friend trick:

    template <typename Derived>
    class Base {
        friend Derived;                             // Derived may grant friendship back
        Base() = default;                           // private ctor → only Derived can construct
        void invoke() { static_cast<Derived*>(this)->impl(); }   // ok if impl is private
    };
    class D : public Base<D> {
        friend class Base<D>;                       // grant base access to private impl
    private:
        void impl();
    };
    

    The friend Derived line plus a private constructor stops anyone writing class Evil : public Base<D> — only D can derive from Base<D>.

  • Hidden incomplete-type traps — using sizeof(Derived) or members in the base's class definition (not method bodies) fails because Derived is incomplete there.

  • Wrong Derived parameterclass B : public Base<A> compiles but static_cast<A*>(this) is undefined behavior. Concept or static_assert(std::derived_from<Derived, Base<Derived>>) inside the base catches it.

  • Multiple CRTP bases with conflicting members — mixins overlap → ambiguous call. Disambiguate with explicit MixinA<D>::name().

  • Compile-error fallout — type errors in the derived's primitive surface in the base's instantiated function body, often dozens of frames deep. Concepts (C++20) help by stating the requirements directly.


8. Related Patterns and Modern Alternatives

  • CRTP vs virtual — compile-time vs runtime dispatch. CRTP: zero-overhead, inlinable, no heterogeneous container. Virtual: vtable indirection, supports vector<Base*>. Choose per use case; they're often combined (virtual at the top, CRTP at hot leaves).
  • CRTP vs Policy Based Design — both are template idioms. CRTP: derive-from-base, base talks back to derived. Policy-Based: host class takes behaviour policies as template parameters and inherits from them. Often combined: a policy class itself uses CRTP for its mixin behaviour.
  • CRTP vs C++20 concepts — concepts express "these members must exist" without forcing inheritance. For static polymorphism with no shared implementation, a concept + free function template is cleaner. CRTP still wins when the base provides shared code (mixin behaviour, common method implementations).
  • CRTP in State Pattern / Strategy Pattern / Template Method Pattern — drop-in static-polymorphism replacement for the virtual variants when types are known at compile time.
  • Deducing-this (C++23)void f(this Self&& self) removes the need for CRTP in many mixin cases: a base member function gets the derived type via the deduced explicit object parameter. Expect mixin libraries to migrate over time.

9. References