CRTP — Curiously Recurring Template Pattern
Static polymorphism, mixins, and compile-time interface enforcement via CRTP — zero virtual overhead, resolved entirely at compile time.
CRTP (Curiously Recurring Template Pattern)since C++98A pattern where a class D publicly inherits from a template base Base<D>, allowing the base to call methods on the derived type through a static_cast, achieving static (compile-time) polymorphism with no virtual dispatch overhead.
Overview
CRTP works because when Derived inherits from Base<Derived>, the base template is instantiated with Derived as its parameter. Inside Base<Derived>, the actual runtime type is always Derived — the static_cast<Derived*>(this) is defined behavior, not a hack. The compiler resolves all dispatch at instantiation time; no vtable is generated, no indirect branch is taken at runtime.
Three principal uses:
- Static polymorphism — uniform interface over unrelated concrete types without
virtual - Mixins — inject operators and behaviors into derived classes without multiple virtual inheritance
- Interface enforcement — guarantee that derived types implement required methods, caught at compile time
CRTP predates C++11 and works from C++98 onward. C++20 Concepts improve the ergonomics of enforcing derived-side requirements. C++23's deducing this (explicit object parameters) supersedes some CRTP patterns entirely, but CRTP remains essential for pre-C++23 codebases and for patterns that Concepts alone cannot replace.
Syntax
template<typename Derived>
class Base {
public:
void interface_method() {
// Downcast is safe: Base<D> is only ever a subobject of D
static_cast<Derived*>(this)->impl();
}
protected:
~Base() = default; // non-virtual protected: prevents delete-through-base
};
class Concrete : public Base<Concrete> {
public:
void impl() { /* ... */ }
};The static_cast is the critical ingredient. reinterpret_cast would be undefined behavior; the static_cast is valid because Derived contains Base<Derived> as a subobject, so the pointer relationship holds. The cast is entirely free at runtime — the compiler eliminates it.
Examples
Static polymorphism
The canonical use: a uniform interface without vtable cost. This matters when you have template functions that operate on many shape types in tight numerical loops.
template<typename Derived>
class Shape {
public:
double area() const {
return static_cast<const Derived*>(this)->area_impl();
}
double perimeter() const {
return static_cast<const Derived*>(this)->perimeter_impl();
}
void describe() const {
std::println("area={:.4f} perimeter={:.4f}", area(), perimeter());
}
protected:
~Shape() = default;
};
class Circle : public Shape<Circle> {
double r_;
public:
explicit Circle(double r) : r_(r) {}
double area_impl() const { return std::numbers::pi * r_ * r_; } // C++20: std::numbers
double perimeter_impl() const { return 2.0 * std::numbers::pi * r_; }
};
class Rectangle : public Shape<Rectangle> {
double w_, h_;
public:
Rectangle(double w, double h) : w_(w), h_(h) {}
double area_impl() const { return w_ * h_; }
double perimeter_impl() const { return 2.0 * (w_ + h_); }
};
// Works for any Shape<T> — resolved at instantiation, zero overhead
template<typename T>
void print_shape(const Shape<T>& s) { s.describe(); }
Circle c{5.0};
Rectangle r{4.0, 6.0};
print_shape(c); // area=78.5398 perimeter=31.4159
print_shape(r); // area=24.0000 perimeter=20.0000Note that Shape<Circle> and Shape<Rectangle> are distinct, unrelated types. You cannot place both in a std::vector<Shape<??>> — the heterogeneous container problem is the primary structural limitation of CRTP.
Mixin: Barton-Nackman operator injection
Inject a full set of comparison operators from a single user-defined operator<. This is the technique behind the historical Barton-Nackman trick and is the pattern that C++20's operator<=> largely replaces for comparisons — but the general mixin mechanism remains useful far beyond comparisons.
template<typename Derived>
struct TotallyOrdered {
friend bool operator==(const Derived& a, const Derived& b) {
return !(a < b) && !(b < a);
}
friend bool operator!=(const Derived& a, const Derived& b) { return !(a == b); }
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); }
};
struct Celsius : TotallyOrdered<Celsius> {
double value;
explicit Celsius(double v) : value(v) {}
friend bool operator<(const Celsius& a, const Celsius& b) {
return a.value < b.value;
}
};
Celsius boiling{100.0}, freezing{0.0};
assert(boiling > freezing); // derived from < via mixin
assert(freezing != boiling);Using friend functions defined inside the base template makes argument-dependent lookup work correctly — the operators are found when the compiler looks up names for Celsius arguments.
Per-type instance counting
Each Counter<T> instantiation has its own inline static counter, giving exact per-type counts without any manual wiring:
template<typename Derived>
class Counter {
public:
static int live_count() noexcept { return count_; } // noexcept: C++11
protected:
Counter() noexcept { ++count_; }
Counter(const Counter&) noexcept { ++count_; }
~Counter() noexcept { --count_; }
private:
inline static int count_ = 0; // inline static: C++17
};
class Connection : public Counter<Connection> { /* ... */ };
class Session : public Counter<Session> { /* ... */ };
{
Connection a, b, c;
Session s;
assert(Connection::live_count() == 3);
assert(Session::live_count() == 1);
}
assert(Connection::live_count() == 0);C++20: enforcing the interface with Concepts
Prior to C++20, a missing impl() in Derived produced a cryptic error at the call site inside the base. With Concepts, you can catch it early and emit a readable diagnostic:
template<typename T>
concept HasAreaImpl = requires(const T& t) {
{ t.area_impl() } -> std::convertible_to<double>;
};
template<HasAreaImpl Derived> // C++20
class Shape {
public:
double area() const {
return static_cast<const Derived*>(this)->area_impl();
}
protected:
~Shape() = default;
};The Concept constraint fires at the point of instantiation, naming exactly what is missing.
C++23: deducing this as an alternative
C++23 introduces explicit object parameters, which collapse many CRTP patterns into simpler non-template base classes:
// C++23 — no CRTP needed for this pattern
struct Printable {
void print(this auto const& self) { // C++23: explicit object parameter
self.print_impl();
}
};
struct Widget : Printable {
void print_impl() const { std::println("Widget"); }
};
Widget w;
w.print(); // resolves Widget::print_impl() without CRTPDeducing this covers the mixin and static polymorphism cases but does not handle patterns that require the base to name the derived type explicitly (e.g., returning Derived by value, per-type static storage, or template metaprogramming that inspects Derived). CRTP remains necessary for those.
Best Practices
Protect the destructor. If users should not delete through a base pointer, make the destructor protected (as shown above). Only add a virtual destructor to the base if you deliberately want runtime-polymorphic deletion — which defeats the purpose of CRTP.
Use friend free functions for operators. Defining operators as friend inside the template body participates correctly in ADL and avoids implicit conversions on the wrong argument.
Enforce with Concepts in C++20+. Replace silent hard-to-diagnose instantiation errors with Concept constraints on Derived. This also serves as machine-readable documentation of what the base requires.
Prefer deducing this for new code in C++23+. For pure mixin injection where the base does not need to name the derived type, deducing this is simpler, avoids the repetitive : Base<Self> boilerplate, and supports chains of mixins without diamond-inheritance concerns.
Keep the base thin. CRTP bases are not interfaces in the OOP sense — they are behavior injectors. Mixing large amounts of state into a CRTP base complicates the object layout and makes the pattern harder to reason about.
Common Pitfalls
Heterogeneous containers are impossible
Shape<Circle> and Shape<Rectangle> share no common base. You cannot store them together:
// Does not compile — no common type
std::vector<Shape<?>*> shapes;
// Solutions:
// 1. Add a separate abstract base with virtual dispatch
// 2. std::variant<Circle, Rectangle> (C++17)
// 3. Type-erased wrapper (see: type erasure idiom)Calling base methods without the cast
A common mistake is forgetting the cast and accidentally calling the base version:
template<typename Derived>
struct Base {
void process() {
do_work(); // calls Base::do_work() — wrong
static_cast<Derived*>(this)->do_work(); // correct
}
void do_work() { /* base fallback */ }
};If Derived::do_work() is non-virtual and the cast is omitted, the base version runs silently. There is no compiler warning.
The incomplete-type trap during instantiation
The derived class is incomplete at the point of inheritance, which means the base constructor cannot call derived methods. The static_cast is only safe in non-constructor member functions:
template<typename D>
struct Base {
Base() {
// UNDEFINED BEHAVIOR — D is incomplete at construction time
static_cast<D*>(this)->init();
}
};Move any initialization that requires the fully-constructed Derived into a separate init() call or a factory function.
CRTP for static interface enforcement
CRTP can make "pure virtual" requirements a compile-time contract. Rather than relying on a runtime = 0 function that simply omits a vtable entry, you can require that Derived exposes exactly the methods the base needs — and fail early with a clear error.
// C++17 — static_assert inside the base constructor to enforce the interface
template<typename Derived>
class Serializable {
public:
std::string serialize() const {
static_assert(
requires(const Derived& d) { d.to_string(); }, // C++20 ad-hoc requires
"Derived must implement to_string() const"
);
return static_cast<const Derived*>(this)->to_string();
}
protected:
~Serializable() = default;
};With C++20 Concepts the enforcement moves to the class template parameter, so the diagnostic appears at the point of instantiation rather than deep inside the base:
// C++20
template<typename T>
concept Stringifiable = requires(const T& t) {
{ t.to_string() } -> std::convertible_to<std::string>;
};
template<Stringifiable Derived>
class Serializable {
public:
std::string serialize() const {
return static_cast<const Derived*>(this)->to_string();
}
protected:
~Serializable() = default;
};
struct Good : Serializable<Good> {
std::string to_string() const { return "Good"; }
};
// struct Bad : Serializable<Bad> {}; // C++20: hard error at template argumentMixin pattern via CRTP
CRTP mixins inject reusable behavior into any class that opts in, without touching the class hierarchy. Each mixin is a separate base template; a derived class simply inherits from multiple mixins simultaneously.
// C++11 — composable mixin bases
template<typename Derived>
struct Printable {
void print() const {
std::cout << static_cast<const Derived*>(this)->to_string() << '\n';
}
};
template<typename Derived>
struct Hashable {
std::size_t hash() const noexcept {
auto val = static_cast<const Derived*>(this)->hash_value();
return std::hash<decltype(val)>{}(val);
}
};
// Derive from both — no virtual overhead, no diamond ambiguity
struct Tag : Printable<Tag>, Hashable<Tag> {
std::string name;
explicit Tag(std::string n) : name(std::move(n)) {}
std::string to_string() const { return name; }
std::string hash_value() const { return name; }
};
Tag t{"release"};
t.print(); // release
auto h = t.hash(); // std::hash<std::string>{}("release")Each mixin base is instantiated independently, so there is no shared state between them — just compiled-in method forwarding.
CRTP vs C++20 concepts
CRTP and Concepts solve overlapping but distinct problems. CRTP provides behavior injection and static dispatch at runtime; Concepts constrain template parameters at instantiation time and document requirements in machine-readable form. The two compose well together.
| Concern | CRTP alone | Concepts alone | CRTP + Concepts |
|---|---|---|---|
| Inject behavior | yes | no | yes |
| Constrain template arg | no | yes | yes |
| Readable error on violation | poor (pre-C++20) | yes | yes |
| Zero overhead | yes | yes | yes |
| Requires C++ version | C++98 | C++20 | C++20 |
// C++20 — concept documents the requirement; CRTP fulfills it
template<typename T>
concept CRTPBase = requires(T t) {
// every CRTP-derived type must be default-constructible
// and expose area_impl()
requires std::default_initializable<T>;
{ t.area_impl() } -> std::floating_point;
};
template<CRTPBase Derived>
struct Shape {
double area() const {
return static_cast<const Derived*>(this)->area_impl();
}
protected:
~Shape() = default;
};The Concept documents the interface contract; CRTP dispatches the implementation. Together they make the pattern self-documenting and immediately diagnosable.
CRTP and operator injection
A CRTP base is the canonical way to synthesize a full operator set from a minimal user-supplied primitive. The standard library itself uses this pattern — std::rel_ops (deprecated in C++20) was a non-CRTP version; std::totally_ordered Concept is the modern successor — but the CRTP form is still common in embedded and numerics code.
// C++11 — inject all arithmetic operators from += only
template<typename Derived>
struct AdditiveGroup {
friend Derived operator+(Derived lhs, const Derived& rhs) { return lhs += rhs; }
friend Derived operator-(Derived lhs, const Derived& rhs) { return lhs -= rhs; }
friend Derived operator-(Derived val) {
// unary minus via scaled subtraction
return Derived{} - val;
}
};
struct Vec2 : AdditiveGroup<Vec2> {
float x, y;
Vec2(float x = 0, float y = 0) : x(x), y(y) {}
Vec2& operator+=(const Vec2& o) { x += o.x; y += o.y; return *this; }
Vec2& operator-=(const Vec2& o) { x -= o.x; y -= o.y; return *this; }
};
Vec2 a{1, 2}, b{3, 4};
Vec2 c = a + b; // {4, 6} — injected from AdditiveGroup
Vec2 d = c - a; // {3, 4} — injected from AdditiveGroupThe friend free functions are defined inside the base template body, which means ADL finds them when either argument is of type Derived.
Deducing this (C++23) as a CRTP replacement
C++23 explicit object parameters (deducing this) eliminate most CRTP boilerplate for the mixin and static-dispatch patterns. The base class becomes a plain struct and its methods take a this auto first parameter that deduces to the actual derived type.
// C++23 — CRTP replaced by explicit object parameter
struct Printable {
void print(this const auto& self) {
// 'self' deduces to the most-derived type — no static_cast needed
std::println("{}", self.to_string());
}
};
struct Hashable {
std::size_t hash(this const auto& self) noexcept {
return std::hash<std::string>{}(self.hash_key());
}
};
struct Tag : Printable, Hashable { // no <Tag> parameter — simpler
std::string name;
explicit Tag(std::string n) : name(std::move(n)) {}
std::string to_string() const { return name; }
std::string hash_key() const { return name; }
};
Tag t{"v1.0"};
t.print(); // v1.0
auto h = t.hash();Deducing this does not replace all CRTP uses. It cannot express per-type static storage (each Base<Derived> is its own instantiation with its own statics), cannot return Derived by value from the base, and cannot be used in contexts where the derived type must appear as a template argument to another template.
CRTP with multiple base policies
CRTP composes cleanly when a class needs behavior from several orthogonal policy bases. Each policy is a separate CRTP template; the derived class inherits them all. The linker sees only the methods actually called, so unused policies cost nothing.
// C++17 — three independent CRTP policy bases
template<typename D> struct Loggable {
void log(std::string_view msg) const {
std::println("[{}] {}", static_cast<const D*>(this)->class_name(), msg);
}
};
template<typename D> struct Cloneable {
[[nodiscard]] D clone() const { // C++17: [[nodiscard]]
return *static_cast<const D*>(this);
}
};
template<typename D> struct Comparable {
friend bool operator==(const D& a, const D& b) { return a.key() == b.key(); }
friend bool operator< (const D& a, const D& b) { return a.key() < b.key(); }
};
struct Record
: Loggable<Record>
, Cloneable<Record>
, Comparable<Record>
{
int id;
explicit Record(int i) : id(i) {}
std::string_view class_name() const { return "Record"; }
int key() const { return id; }
};
Record r{42};
r.log("created"); // [Record] created
Record r2 = r.clone(); // copy via Cloneable
assert(r == r2); // via Comparable
assert(!(r < r2));Keep each policy base focused on a single responsibility; the derived class should only need to supply a small set of primitive operations that all policies share.
Pitfall: accessing incomplete derived class
The derived type is incomplete during base class construction, making it unsafe to call derived methods from a base constructor. The object layout for Derived does not exist yet; the vtable pointer (if any) has not been set, and the static_cast reaches memory that has not been initialized.
// C++11 — DO NOT do this
template<typename D>
struct EagerBase {
EagerBase() {
// UNDEFINED BEHAVIOR: D is still being constructed;
// D's members are uninitialized at this point
static_cast<D*>(this)->on_init();
}
};
struct Widget : EagerBase<Widget> {
int value{42}; // not yet initialized when on_init() runs
void on_init() { assert(value == 42); // FAILS — value is 0 or garbage
}
};The correct pattern is to defer initialization to a factory function or a two-phase init() call:
// C++11 — safe two-phase initialization
template<typename D>
struct LazyBase {
void init() {
static_cast<D*>(this)->on_init(); // safe: called after full construction
}
};
struct Widget : LazyBase<Widget> {
int value{42};
void on_init() { assert(value == 42); } // safe
};
Widget w;
w.init(); // caller is responsible for calling init after constructionAlternatively, use a static factory method that constructs the object and then calls init() in one step, preventing the two-phase anti-pattern from leaking into user code.
See Also
std::enable_shared_from_this— standard library CRTP base for safeshared_ptrself-reference- Type Erasure — runtime-polymorphic alternative when heterogeneous containers are needed
- Mixins — non-CRTP behavior injection patterns
- Concepts — C++20 mechanism for constraining CRTP base requirements
- Templates — template fundamentals underlying CRTP