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
- 2. Why It Works
- 3. Static Polymorphism
- 4. Mixins
- 5. Barton–Nackman Trick
- 6. Real-World Examples
- 7. Pitfalls
- 8. Related Patterns and Modern Alternatives
- 9. References
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>referencesMyClassbeforeMyClassis fully defined. Works because the base's member function bodies aren't instantiated until used, so they can callimplementationeven thoughMyClasswas 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 whenMyClassparses its base-clause: the class template definition ofBaseis known, even thoughMyClassitself is still incomplete.- Member function bodies of
Baseare lazily instantiated at point of use; by thenMyClassis complete, sostatic_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>andShape<Square>are unrelated types. Heterogeneous polymorphism requiresstd::variantor 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
friendinside 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 givesTashared_from_this()returningstd::shared_ptr<T>to itself. The base must knowTto 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>— suppliesempty(),front(),back(),operator[],operator boolfrom the user-implementedbegin()/end()ofView.- Eigen / Blaze / xtensor — expression templates —
MatrixBase<Derived>letsMatrix + Matrixreturn a lightweightSum<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 slicing —
Base<D> b = d;silently slices and the static cast insideBasethen 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 Derivedline plus a private constructor stops anyone writingclass Evil : public Base<D>— onlyDcan derive fromBase<D>. -
Hidden incomplete-type traps — using
sizeof(Derived)or members in the base's class definition (not method bodies) fails becauseDerivedis incomplete there. -
Wrong
Derivedparameter —class B : public Base<A>compiles butstatic_cast<A*>(this)is undefined behavior. Concept orstatic_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
- Coplien, Curiously Recurring Template Patterns, C++ Report, 1995.
- Barton & Nackman, Scientific and Engineering C++, Addison-Wesley, 1994.
- Vandevoorde, Josuttis, Gregor. C++ Templates: The Complete Guide (2nd ed.) — CRTP chapter.
- Alexandrescu, Modern C++ Design, 2001 — policy + CRTP interplay.
- cppreference: enable_shared_from_this
- cppreference: ranges::view_interface
- P0847 — Deducing this (C++23).
- Fluent C++ — The Curiously Recurring Template Pattern