CRTP—the Curiously Recurring Template Pattern—is a C++ idiom where a class Derived inherits from a template base class instantiated with Derived itself:
template <class Derived>
struct Base { /* ... */ };
struct Derived : Base<Derived> { /* ... */ };
It looks odd at first, but it unlocks a powerful set of techniques: static dispatch (often called “static polymorphism”), mixins, compile-time interfaces, and zero-overhead customization points.
TL;DR: When to reach for CRTP
- Reach for CRTP when you want a shared public API + per-type customization with no vtables and good inlining opportunities.
- Avoid CRTP when you need runtime substitutability (one base type for heterogeneous storage) or when template-heavy error messages / build times are already a pain.
- If you need both: use CRTP internally and wrap it with type erasure or
std::variantat the boundary.

Caption: CRTP shape: Derived : Base<Derived>.
What you should notice: the base is parameterized by the derived type, which lets the base “reach into” derived at compile time.
What problem does CRTP solve?
Static dispatch means the compiler decides which function body to call at compile time based on the concrete type (typically via templates), enabling aggressive optimization and inlining.
Classic runtime polymorphism uses virtual functions:
struct Shape {
virtual ~Shape() = default;
virtual double area() const = 0;
};
struct Circle : Shape {
double r;
double area() const override { return 3.14159 * r * r; }
};
This is flexible, but it has costs and constraints (often, not always):
- Virtual dispatch (indirection) and often inhibited inlining
- Per-object vptr overhead (typically one pointer)
- Dynamic lifetime semantics (polymorphic base, safe deletion via base pointer)
- Can be harder for the optimizer to devirtualize across translation units without LTO
CRTP provides an alternative: behavior is resolved at compile time, so calls can inline and there’s no vtable.
The core idea: “base calls derived”
In CRTP, the base template uses static_cast to access the derived type:
#include <iostream>
template <class Derived>
struct Printable {
void print() const {
// Call into Derived without virtual functions
static_cast<const Derived&>(*this).print_impl();
}
};
struct User : Printable<User> {
void print_impl() const {
std::cout << "User::print_impl()\n";
}
};
Now Printable<User>::print() is a non-virtual function, but it still dispatches to User::print_impl()—statically.
Static dispatch vs “polymorphism” (terminology)
People often say CRTP gives “static polymorphism,” but that can be confusing because many readers associate polymorphism with substitutability (i.e., “I can store different derived objects behind one Base* and call virtuals”).
CRTP gives static dispatch: you get “polymorphic-like” customization, but you generally do not get runtime substitutability through a single common base type.
Tradeoffs (state them early)
Right after the first CRTP example, the key trade is:
- You gain: zero vtables, strong inlining/optimization opportunities, reusable mixins/default methods.
- You give up: a single base type for heterogeneous storage (because
Printable<User>andPrintable<Admin>are different types), and you may pay in compile time/code size due to template instantiations.
A small but important rule (compile-time interface)
The base can only call methods that actually exist on Derived. If Derived doesn’t implement print_impl(), you’ll get a compile-time error.
For example, if you write:
struct Broken : Printable<Broken> {
// missing: void print_impl() const;
};
void f() {
Broken b;
b.print();
}
You’ll typically see an error along the lines of (compiler wording varies):
error: no member named 'print_impl' in 'Broken'
That’s the “compile-time interface” idea made concrete: the contract is enforced by instantiation.
A practical example: a CRTP interface with a default implementation
Let’s build an interface for “things that can be serialized” with a default to_string() that calls into a required serialize_to().
#include <ostream>
#include <sstream>
#include <string>
#include <string_view>
template <class Derived>
struct Serializable {
std::string to_string() const {
std::ostringstream os;
static_cast<const Derived&>(*this).serialize_to(os);
return os.str();
}
};
struct Point : Serializable<Point> {
int x{}, y{};
void serialize_to(std::ostream& os) const {
os << "Point(" << x << ", " << y << ")";
}
};
You get:
- A reusable
to_string()implementation - No virtuals
- Compile-time enforcement that
Pointprovidesserialize_to(std::ostream&)

Caption: Call chain for the Serializable CRTP example.
What you should notice: to_string() lives once in the base template, but the “hook” (serialize_to) comes from the derived type.
Static dispatch in action (and how it differs from virtual)
With virtual functions, you can store heterogeneous objects behind a pointer/reference to base:
#include <memory>
#include <iostream>
std::unique_ptr<Shape> s = std::make_unique<Circle>();
std::cout << s->area();
With CRTP, the “polymorphism” is typically per concrete type. You usually write algorithms as templates:
template <class T>
double area_of(const T& shape) {
return shape.area();
}
Why heterogeneous containers don’t work “by default”
The core reason is type identity:
Base<Circle>andBase<Square>are different, unrelated types.- There is no single
Baseyou can point to that represents “anyBase<Derived>.”
So you can’t do std::vector<Base<?>> in C++.
A common hybrid: CRTP + type erasure (actionable)
A practical pattern is:
- Use CRTP internally for fast, inlinable implementation.
- Expose a runtime-polymorphic wrapper when you need heterogeneity.
Pseudocode sketch (type-erased wrapper):
// Runtime wrapper: one stable type you can put in containers.
struct AnyShape {
struct VTable {
double (*area)(const void*);
void (*destroy)(void*);
};
const VTable* vt{};
void* obj{};
template <class T>
AnyShape(T x) {
static const VTable v{
/*area=*/[](const void* p) { return static_cast<const T*>(p)->area(); },
/*destroy=*/[](void* p) { delete static_cast<T*>(p); }
};
vt = &v;
obj = new T(std::move(x));
}
~AnyShape() { vt->destroy(obj); }
double area() const { return vt->area(obj); }
};
This gives you a std::vector<AnyShape> while still allowing CRTP-based implementations behind the scenes.
CRTP mixins: adding capabilities via inheritance
CRTP shines as a mixin mechanism: small base templates that add a feature to any derived type.
Example: add a clone() method that returns the concrete type.
#include <memory>
template <class Derived>
struct Cloneable {
std::unique_ptr<Derived> clone() const {
return std::make_unique<Derived>(static_cast<const Derived&>(*this));
}
};
struct Widget : Cloneable<Widget> {
int id{};
};
Constraints to be aware of:
- This requires
Derivedto be copy-constructible. - If cloning needs custom behavior (or move-only types), use a hook instead:
template <class Derived>
struct Cloneable2 {
std::unique_ptr<Derived> clone() const {
return static_cast<const Derived&>(*this).clone_impl();
}
};
Compile-time “interfaces” and better errors with concepts
CRTP predates C++20 concepts, so missing methods can produce noisy template errors. Concepts can make the contract explicit.
#include <concepts>
#include <ostream>
#include <sstream>
#include <string>
template <class T>
concept StreamSerializable = requires(const T& t, std::ostream& os) {
{ t.serialize_to(os) } -> std::same_as<void>;
};
template <StreamSerializable Derived>
struct Serializable {
std::string to_string() const {
std::ostringstream os;
static_cast<const Derived&>(*this).serialize_to(os);
return os.str();
}
};
Now, instead of “no member named serialize_to … deep in templates,” you’re more likely to get a focused diagnostic like:
error: type
Xdoes not satisfyStreamSerializable
That’s a big readability win in large codebases.
CRTP for customization points (policy-based design)
CRTP can implement a framework where the base provides an algorithm and the derived customizes certain steps.
#include <string_view>
template <class Derived>
struct ParserBase {
// Choose const/non-const intentionally: parsing often mutates internal state.
bool parse(std::string_view input) {
static_cast<Derived&>(*this).on_begin();
// ... shared parsing logic using input ...
static_cast<Derived&>(*this).on_end();
return true;
}
};
struct JsonParser : ParserBase<JsonParser> {
void on_begin() { /* ... */ }
void on_end() { /* ... */ }
};
This is essentially the Template Method pattern, but resolved at compile time (no virtual hooks).

Caption: Compile-time Template Method: base algorithm, derived hooks.
What you should notice: the “framework” (parse) is shared, while the customization points (on_begin, on_end) are provided by the derived type.
Common pitfalls and how to avoid them
1) Calling derived methods too early (construction/destruction)
During base class construction, the derived part isn’t fully constructed yet. In CRTP, avoid calling derived hooks from base constructors/destructors.
Bad:
template <class D>
struct Base {
Base() { static_cast<D*>(this)->init(); } // risky
};
A safe, named pattern: two-phase init (explicit init step).
template <class D>
struct Base {
void init_base() {
static_cast<D*>(this)->init();
}
};
struct Good : Base<Good> {
Good() {
// derived is fully constructed at this point
init_base();
}
void init() { /* ... */ }
};
Alternative: a static factory (create()) that constructs and then calls an init hook.
2) Slicing and “pretending” CRTP is runtime polymorphism
CRTP does not automatically give you a safe base-pointer interface for heterogeneous collections.
You can write:
Printable<User>* p = nullptr;
…but that pointer is only meaningful for the exact instantiation Printable<User>. It doesn’t help you store Printable<Admin> in the same container, because that’s a different type.
Prefer stack objects or smart pointers in real code; and if you need heterogeneity:
- Use
std::variantfor a closed set of types - Use type erasure (custom wrapper,
std::function-style, or libraries like Boost.TypeErasure) - Use classic virtual interfaces
3) Code bloat and compile times
Static dispatch duplicates code per instantiation. Mitigations include:
- Keep CRTP bases small and headers clean.
- Mark tiny CRTP functions
inlineand/orconstexprwhere appropriate. - Isolate heavy logic into non-templated helper functions (e.g., in a
.cpp) that take primitive or erased inputs. - Use LTO and
-ffunction-sections/ICF where available.
4) Access control and friend declarations
Sometimes you want the derived to keep *_impl methods private while still letting the CRTP base call them. One workaround is making the base a friend:
template <class>
struct Printable;
struct User : Printable<User> {
private:
friend struct Printable<User>; // per-derived friendship
void print_impl() const { /* ... */ }
};
Note: friending scales poorly if you have many mixins. Alternatives:
- Make the hook
protected(convention:protected: void print_impl() const;) - Make hooks public but documented as “implementation detail” (
*_implnaming convention)
When to use CRTP (and when not to)
Use CRTP when:
- You want zero-overhead customization (no vtables)
- You’re building reusable building blocks (mixins)
- You can accept template-based APIs (algorithms as templates)
- Performance and inlining opportunities matter
Avoid or reconsider CRTP when:
- You need runtime polymorphism with heterogeneous collections
- ABI stability matters (templates expose implementation details)
- Compile times and code size are already a major constraint
- You expect significant debugging/diagnostics cost (template-heavy code can be harder to read without concepts)
A mental model that makes CRTP click
Think of CRTP as:
- The base class provides public API and shared code
- The derived class provides customization hooks
- The “dispatch” is just the compiler selecting the right instantiation (static dispatch)
If you’ve used Rust traits with monomorphization, Swift generics, or C++ templates with concepts, CRTP is the C++ inheritance-flavored version of that idea.

Caption: Static dispatch vs runtime dispatch.
What you should notice: runtime dispatch chooses an override at runtime via a vtable; CRTP chooses the target at compile time via the concrete type.
Summary
CRTP is a deceptively simple pattern:
Derived : Base<Derived>- Base calls derived via
static_cast<Derived&>(*this)
In return, you get static dispatch, mixins, and compile-time interfaces—often with excellent performance characteristics. Use it when compile-time composition fits, and reach for runtime polymorphism or type erasure when you truly need dynamic behavior.
Further reading / related patterns
- Policy-based design (Alexandrescu, Modern C++ Design)
- Barton–Nackman trick (friend injection, often discussed alongside CRTP)
std::enable_shared_from_this<T>as a CRTP-ish standard-library example- Type erasure patterns ("Any" wrappers, small-buffer optimization, custom vtables)