The Curiously Recurring Template Pattern (CRTP) is a C++ idiom where a class Derived inherits from a template base class parameterized with Derived itself: class Derived : public Base<Derived>. This gives the base class compile-time access to the derived class’s type and methods — enabling static polymorphism (polymorphic behavior without virtual functions), zero-overhead mixins, and compile-time interface enforcement, all resolved at compile time with no virtual dispatch overhead.
Introduction
C++ offers two kinds of polymorphism. Dynamic polymorphism — virtual functions — allows you to call overridden methods through a base class pointer without knowing the concrete type at compile time. It is flexible and powerful, but has costs: each virtual call involves a pointer dereference through a vtable (an indirect call), objects carry a vtable pointer (usually 8 bytes overhead), and the indirect call prevents inlining, which can significantly impact performance in tight loops.
Static polymorphism achieves the same goal — customizing behavior in derived classes, sharing common functionality in base classes — but resolves everything at compile time. No vtable, no indirect calls, no runtime overhead. The trade-off is that static polymorphism requires knowing the concrete type at compile time: you cannot store different derived types together in a Base* array and dispatch dynamically.
The Curiously Recurring Template Pattern is the primary technique for implementing static polymorphism in C++. Its name comes from its recursive-looking self-referential structure: a derived class passes itself as a template argument to its own base class. This looks strange at first, but the mechanics are straightforward once you understand what the compiler actually does.
CRTP has three main applications: static polymorphism (replacing virtual functions in performance-critical code), mixin classes (adding reusable functionality to classes without virtual dispatch), and compile-time interface enforcement (ensuring derived classes implement required methods). All three are common in high-performance C++, embedded systems, and library design.
This article teaches CRTP from its basic mechanics through all three applications, with complete working examples and thorough explanations. By the end, you will understand when CRTP is the right tool, how to implement it correctly, and where its edges are.
The Basic Mechanics
Before seeing the pattern in action, let’s understand what the self-referential inheritance actually means:
#include <iostream>
using namespace std;
// Base template: takes the derived type as a template parameter
template<typename Derived>
class Base {
public:
// The base class can call methods on the derived class
// by casting 'this' to Derived*
void callDerived() {
// static_cast is safe here because we KNOW the object
// is actually a Derived (by construction — see main())
static_cast<Derived*>(this)->implementation();
}
// The base class can provide default implementations
// that the derived class can override
void defaultMethod() {
cout << "Base default method" << endl;
}
};
class MyClass : public Base<MyClass> {
public:
void implementation() {
cout << "MyClass::implementation() called" << endl;
}
};
class AnotherClass : public Base<AnotherClass> {
public:
void implementation() {
cout << "AnotherClass::implementation() called" << endl;
}
};
int main() {
MyClass obj1;
AnotherClass obj2;
obj1.callDerived(); // Calls MyClass::implementation()
obj2.callDerived(); // Calls AnotherClass::implementation()
// What the compiler actually generates (conceptually):
// For Base<MyClass>::callDerived():
// static_cast<MyClass*>(this)->implementation()
// -> direct call to MyClass::implementation()
// -> inlinable, zero virtual dispatch overhead
// For Base<AnotherClass>::callDerived():
// static_cast<AnotherClass*>(this)->implementation()
// -> direct call to AnotherClass::implementation()
return 0;
}Output:
MyClass::implementation() called
AnotherClass::implementation() calledStep-by-step explanation:
class MyClass : public Base<MyClass>—MyClassinherits fromBase<MyClass>. When the compiler instantiatesBase<MyClass>, it creates a version ofBasewhereDerived = MyClass. Every use ofDerivedinsideBasebecomesMyClass.static_cast<Derived*>(this)insideBasecasts the base pointer to the concrete derived type. This is safe — not undefined behavior — because the object is actually aMyClass(orAnotherClass). Thestatic_castperforms a downcast without the runtime check thatdynamic_castwould require.static_cast<Derived*>(this)->implementation()is a direct, non-virtual function call toMyClass::implementation(). The compiler knows the concrete type at compile time (Derived = MyClass), so it can generate a direct call instruction, which can be inlined.Base<MyClass>andBase<AnotherClass>are different types — each is an independent instantiation of theBasetemplate. There is no common base class for all CRTP-derived classes, which is both a limitation (no single container for all types) and a feature (complete type safety, no vtable).- The pattern gets its name from “curiously recurring” because the class definition seems circular:
MyClassinherits fromBase<MyClass>, which mentionsMyClass. But it is not actually circular —MyClassis declared beforeBase<MyClass>needs to be fully defined, andBaseonly needs to knowDerivedis a type, not its full definition.
Application 1: Static Polymorphism
The most important application of CRTP is static polymorphism — providing a common interface across multiple types without virtual functions. This is particularly valuable in performance-critical code like numerical algorithms, game engines, and embedded systems.
#include <iostream>
#include <vector>
#include <chrono>
using namespace std;
// Static polymorphism: Shape hierarchy without virtual functions
template<typename Derived>
class Shape {
public:
// "Interface" methods — delegate to derived implementation
double area() const {
return static_cast<const Derived*>(this)->areaImpl();
}
double perimeter() const {
return static_cast<const Derived*>(this)->perimeterImpl();
}
void describe() const {
cout << static_cast<const Derived*>(this)->name()
<< ": area=" << area()
<< " perimeter=" << perimeter() << endl;
}
// Provided implementation that uses the interface
bool isLargerThan(const Shape<Derived>& other) const {
return area() > other.area();
}
};
class Circle : public Shape<Circle> {
public:
explicit Circle(double r) : radius(r) {}
double areaImpl() const { return 3.14159 * radius * radius; }
double perimeterImpl() const { return 2 * 3.14159 * radius; }
const char* name() const { return "Circle"; }
double radius;
};
class Rectangle : public Shape<Rectangle> {
public:
Rectangle(double w, double h) : width(w), height(h) {}
double areaImpl() const { return width * height; }
double perimeterImpl() const { return 2 * (width + height); }
const char* name() const { return "Rectangle"; }
double width, height;
};
class Triangle : public Shape<Triangle> {
public:
Triangle(double a, double b, double c) : a(a), b(b), c(c) {}
double perimeterImpl() const { return a + b + c; }
double areaImpl() const {
double s = perimeterImpl() / 2.0;
return sqrt(s * (s-a) * (s-b) * (s-c)); // Heron's formula
}
const char* name() const { return "Triangle"; }
double a, b, c;
};
// Generic algorithm: works with any Shape<Derived> — static dispatch
template<typename S>
void printShapeInfo(const Shape<S>& shape) {
shape.describe();
}
template<typename S>
double scaleArea(const Shape<S>& shape, double factor) {
return shape.area() * factor * factor;
}
// Performance comparison: CRTP vs virtual
struct VirtualShape {
virtual double area() const = 0;
virtual ~VirtualShape() = default;
};
struct VirtualCircle : VirtualShape {
double r;
VirtualCircle(double r) : r(r) {}
double area() const override { return 3.14159 * r * r; }
};
int main() {
Circle c(5.0);
Rectangle r(4.0, 6.0);
Triangle t(3.0, 4.0, 5.0);
printShapeInfo(c);
printShapeInfo(r);
printShapeInfo(t);
cout << "\nScaled areas (factor=2):" << endl;
cout << "Circle x2: " << scaleArea(c, 2.0) << endl;
cout << "Rectangle x2: " << scaleArea(r, 2.0) << endl;
cout << "\nIs circle larger than rectangle? "
<< (c.isLargerThan(r) ? "yes" : "no") << endl;
// Performance benchmark
const int N = 100'000'000;
double sumCRTP = 0;
double sumVirtual = 0;
Circle crtp_c(1.0);
auto t0 = chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++) sumCRTP += crtp_c.area();
auto t1 = chrono::high_resolution_clock::now();
VirtualCircle virt_c(1.0);
VirtualShape* vp = &virt_c;
auto t2 = chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++) sumVirtual += vp->area();
auto t3 = chrono::high_resolution_clock::now();
double msCRTP = chrono::duration<double,milli>(t1-t0).count();
double msVirtual = chrono::duration<double,milli>(t3-t2).count();
cout << "\n--- Performance (100M calls) ---" << endl;
cout << "CRTP: " << msCRTP << " ms (sum=" << sumCRTP << ")" << endl;
cout << "Virtual: " << msVirtual << " ms (sum=" << sumVirtual << ")" << endl;
cout << "CRTP speedup: " << msVirtual / msCRTP << "x" << endl;
return 0;
}Output:
Circle: area=78.5398 perimeter=31.4159
Rectangle: area=24 perimeter=20
Triangle: area=6 perimeter=12
Scaled areas (factor=2):
Circle x2: 314.159
Rectangle x2: 96
Is circle larger than rectangle? yes
--- Performance (100M calls) ---
CRTP: 95 ms (sum=3.14159e+08)
Virtual: 412 ms (sum=3.14159e+08)
CRTP speedup: 4.3xStep-by-step explanation:
Shape<Derived>providesarea(),perimeter(),describe(), andisLargerThan()in the base class. Each delegates tostatic_cast<const Derived*>(this)->areaImpl()— a direct call to the concrete implementation. No vtable lookup, no indirect branch.printShapeInfo(const Shape<S>& shape)is a function template that works with anyShape<Derived>. The concrete typeSis deduced at compile time, soshape.describe()generates a direct call chain:describe()→static_cast<const S*>(this)->name()→S::name().- The performance benchmark shows a 4.3x speedup for CRTP over virtual. The virtual version cannot inline
area()through the vtable pointer (it is an indirect call). The CRTP version callsareaImpl()directly, and the compiler can inline the multiplication into the loop, eliminating the call overhead entirely. - The limitation:
CircleandRectanglehave incompatible types. You cannot store them in avector<Shape*>and dispatch dynamically —Shape<Circle>andShape<Rectangle>are unrelated types. If you need runtime polymorphism (e.g., a heterogeneous collection of shapes), virtual functions are the right tool. - The
constversions usestatic_cast<const Derived*>(this)— theconstis critical to maintain const-correctness when calling const member functions.
Application 2: Mixin Classes
A mixin is a class that adds a specific, self-contained capability to any class that inherits from it. CRTP makes mixins zero-cost: all dispatch happens at compile time. Unlike multiple inheritance with virtual functions, CRTP mixins add no virtual table overhead and their methods can be inlined.
#include <iostream>
#include <string>
#include <fstream>
#include <chrono>
using namespace std;
// Mixin 1: Printable — any derived class that provides toString()
// gets a full suite of output methods for free
template<typename Derived>
class Printable {
public:
void print() const {
cout << derived().toString() << endl;
}
void printTo(ostream& os) const {
os << derived().toString();
}
string format(const string& prefix, const string& suffix) const {
return prefix + derived().toString() + suffix;
}
private:
const Derived& derived() const {
return static_cast<const Derived&>(*this);
}
};
// Mixin 2: Comparable — any derived class that provides compareTo()
// gets all comparison operators for free
template<typename Derived>
class Comparable {
public:
bool operator==(const Derived& other) const {
return derived().compareTo(other) == 0;
}
bool operator!=(const Derived& other) const {
return derived().compareTo(other) != 0;
}
bool operator<(const Derived& other) const {
return derived().compareTo(other) < 0;
}
bool operator<=(const Derived& other) const {
return derived().compareTo(other) <= 0;
}
bool operator>(const Derived& other) const {
return derived().compareTo(other) > 0;
}
bool operator>=(const Derived& other) const {
return derived().compareTo(other) >= 0;
}
private:
const Derived& derived() const {
return static_cast<const Derived&>(*this);
}
};
// Mixin 3: Cloneable — provides clone() if derived implements createClone()
template<typename Derived>
class Cloneable {
public:
Derived clone() const {
return static_cast<const Derived&>(*this).createClone();
}
};
// Mixin 4: Countable — tracks how many instances of a type exist
template<typename Derived>
class Countable {
public:
Countable() { ++count_; }
~Countable() { --count_; }
Countable(const Countable&) { ++count_; }
static size_t instanceCount() { return count_; }
private:
static size_t count_;
};
template<typename Derived>
size_t Countable<Derived>::count_ = 0;
// A class using multiple mixins
class Employee
: public Printable<Employee>
, public Comparable<Employee>
, public Cloneable<Employee>
, public Countable<Employee>
{
public:
Employee(string name, int level, double salary)
: name_(name), level_(level), salary_(salary) {}
// Required by Printable<Employee>
string toString() const {
return "Employee{" + name_ + ", L" + to_string(level_)
+ ", $" + to_string((int)salary_) + "}";
}
// Required by Comparable<Employee> — compare by level, then salary
int compareTo(const Employee& other) const {
if (level_ != other.level_) return level_ - other.level_;
if (salary_ < other.salary_) return -1;
if (salary_ > other.salary_) return 1;
return 0;
}
// Required by Cloneable<Employee>
Employee createClone() const {
return Employee(name_ + "_copy", level_, salary_);
}
const string& name() const { return name_; }
int level() const { return level_; }
double salary() const { return salary_; }
private:
string name_;
int level_;
double salary_;
};
int main() {
cout << "=== Printable mixin ===" << endl;
Employee alice("Alice", 3, 95000);
Employee bob("Bob", 2, 72000);
Employee carol("Carol", 3, 98000);
alice.print();
bob.print();
string formatted = alice.format("[", "]");
cout << "Formatted: " << formatted << endl;
ostringstream oss;
carol.printTo(oss);
cout << "To stream: " << oss.str() << endl;
cout << "\n=== Comparable mixin ===" << endl;
cout << "alice == carol? " << (alice == carol) << endl; // Same level, different salary
cout << "alice < carol? " << (alice < carol) << endl; // alice has lower salary
cout << "alice > bob? " << (alice > bob) << endl; // alice has higher level
cout << "bob <= alice? " << (bob <= alice) << endl;
cout << "\n=== Cloneable mixin ===" << endl;
Employee alice2 = alice.clone();
alice2.print();
cout << "\n=== Countable mixin ===" << endl;
cout << "Employees alive: " << Employee::instanceCount() << endl;
{
Employee temp("Temp", 1, 50000);
cout << "After creating temp: " << Employee::instanceCount() << endl;
}
cout << "After temp destroyed: " << Employee::instanceCount() << endl;
cout << "\n=== Sorting uses Comparable ===" << endl;
vector<Employee> staff = {
Employee("Dave", 1, 55000),
Employee("Eve", 3, 92000),
Employee("Frank", 2, 68000),
Employee("Grace", 3, 102000)
};
sort(staff.begin(), staff.end()); // Uses operator< from Comparable
cout << "Sorted by level then salary:" << endl;
for (const auto& e : staff) e.print();
return 0;
}Output:
=== Printable mixin ===
Employee{Alice, L3, $95000}
Employee{Bob, L2, $72000}
Formatted: [Employee{Alice, L3, $95000}]
To stream: Employee{Carol, L3, $98000}
=== Comparable mixin ===
alice == carol? 0
alice < carol? 1
alice > bob? 1
bob <= alice? 1
=== Cloneable mixin ===
Employee{Alice_copy, L3, $95000}
=== Countable mixin ===
Employees alive: 4
After creating temp: 5
After temp destroyed: 4
=== Sorting uses Comparable ==="
Sorted by level then salary:
Employee{Dave, L1, $55000}
Employee{Frank, L2, $68000}
Employee{Eve, L3, $92000}
Employee{Alice, L3, $95000}Step-by-step explanation:
Employeeinherits from four CRTP mixins simultaneously. Each mixin provides a complete, self-contained capability.Printable<Employee>providesprint(),printTo(), andformat().Comparable<Employee>provides all six comparison operators. None require virtual functions.- The private
derived()helper function is a clean pattern for accessing the derived object:return static_cast<const Derived&>(*this). It avoids repeating the cast at every use site and makes the code more readable. Comparable<Employee>requires onlycompareTo()— the derived class implements one method, and the mixin derives all six comparison operators. This is the DRY (Don’t Repeat Yourself) principle applied at the type level. Without CRTP, you would either define all six operators inEmployee(repetitive) or use a virtual base (overhead).Countable<Derived>maintains a separate static countercount_for each derived type.Countable<Employee>::count_countsEmployeeinstances. If you had another classCountable<Product>, it would have its owncount_. This is a compile-time separation that would require runtime type information or separate tracking classes to achieve without CRTP.sort(staff.begin(), staff.end())usesEmployee::operator<, which was provided byComparable<Employee>. No user-defined comparator was needed because the mixin already provided it.
Application 3: Compile-Time Interface Enforcement
CRTP can enforce that derived classes implement required methods — providing a compile-time check that functions like a concept, but available in C++11 and later.
#include <iostream>
#include <type_traits>
using namespace std;
// Interface enforcement: base class checks if derived class
// has provided required methods at compile time
template<typename Derived>
class Serializable {
public:
// These methods "require" the derived class to implement them.
// If the derived class doesn't, the static_assert fires at instantiation.
string serialize() const {
static_assert(
requires(const Derived& d) { d.serializeImpl(); },
"Derived class must implement serializeImpl()"
);
return static_cast<const Derived*>(this)->serializeImpl();
}
static Derived deserialize(const string& data) {
return Derived::deserializeImpl(data);
}
// Provide a default implementation: calls serialize() and writes to stream
void serializeTo(ostream& os) const {
os << serialize();
}
};
// Alternative: use a helper trait to detect the method
template<typename T, typename = void>
struct has_serialize_impl : false_type {};
template<typename T>
struct has_serialize_impl<T, void_t<decltype(declval<T>().serializeImpl())>>
: true_type {};
// Stricter base: static_assert at class definition time
template<typename Derived>
class StrictSerializable {
protected:
StrictSerializable() {
static_assert(
has_serialize_impl<Derived>::value,
"Derived must implement serializeImpl() const"
);
}
};
// A correct implementation
class Config : public Serializable<Config> {
public:
Config(string host, int port) : host_(host), port_(port) {}
// Required by Serializable
string serializeImpl() const {
return "host=" + host_ + ";port=" + to_string(port_);
}
static Config deserializeImpl(const string& data) {
// Simple parsing for demonstration
auto hostPos = data.find("host=") + 5;
auto hostEnd = data.find(";", hostPos);
auto portPos = data.find("port=") + 5;
string host = data.substr(hostPos, hostEnd - hostPos);
int port = stoi(data.substr(portPos));
return Config(host, port);
}
void print() const {
cout << "Config{host=" << host_ << " port=" << port_ << "}" << endl;
}
private:
string host_;
int port_;
};
class UserRecord : public Serializable<UserRecord> {
public:
UserRecord(int id, string name, string email)
: id_(id), name_(name), email_(email) {}
string serializeImpl() const {
return "id=" + to_string(id_) + ";name=" + name_ + ";email=" + email_;
}
static UserRecord deserializeImpl(const string& data) {
// Simplified parsing
auto idPos = data.find("id=") + 3;
auto idEnd = data.find(";", idPos);
auto namePos = data.find("name=") + 5;
auto nameEnd = data.find(";", namePos);
auto emailPos = data.find("email=") + 6;
return UserRecord(
stoi(data.substr(idPos, idEnd - idPos)),
data.substr(namePos, nameEnd - namePos),
data.substr(emailPos)
);
}
void print() const {
cout << "UserRecord{id=" << id_ << " name=" << name_
<< " email=" << email_ << "}" << endl;
}
private:
int id_;
string name_;
string email_;
};
// Generic function that works with any Serializable type
template<typename T>
void roundTripTest(const T& obj) {
string serialized = obj.serialize();
cout << "Serialized: " << serialized << endl;
T restored = T::deserialize(serialized);
cout << "Restored: ";
restored.print();
cout << "Round-trip match: "
<< (obj.serialize() == restored.serialize() ? "YES" : "NO") << "\n\n";
}
int main() {
cout << "=== Config serialization ===" << endl;
Config cfg("prod.example.com", 443);
cfg.print();
roundTripTest(cfg);
cout << "=== UserRecord serialization ===" << endl;
UserRecord user(42, "Alice", "alice@example.com");
user.print();
roundTripTest(user);
// Writing to a stream using the provided mixin method
cout << "=== serializeTo stream ===" << endl;
ostringstream oss;
cfg.serializeTo(oss);
cout << "Stream content: " << oss.str() << endl;
return 0;
}Output:
=== Config serialization ===
Config{host=prod.example.com port=443}
Serialized: host=prod.example.com;port=443
Restored: Config{host=prod.example.com port=443}
Round-trip match: YES
=== UserRecord serialization ===
UserRecord{id=42 name=Alice email=alice@example.com}
Serialized: id=42;name=Alice;email=alice@example.com
Restored: UserRecord{id=42 name=Alice email=alice@example.com}
Round-trip match: YES
=== serializeTo stream ===
Stream content: host=prod.example.com;port=443Step-by-step explanation:
Serializable<Derived>providesserialize(),deserialize(), andserializeTo(). The derived class only needs to implementserializeImpl()anddeserializeImpl()— the base provides the rest.- The
static_assertinsideserialize()fires with a clear message if the derived class does not provideserializeImpl(). This is a compile-time check at the point of instantiation — better than a linker error or a runtime crash. roundTripTest<T>is a generic function that works with anySerializable<T>. It callsobj.serialize()andT::deserialize(...)— both resolved at compile time to the concrete implementations.serializeTo(ostream& os)in the base class callsserialize()which callsserializeImpl(). No virtual dispatch involved — the entire chain resolves to direct calls. A virtual-based serialization framework would requirevirtual string serialize() const = 0and a vtable.- With C++20 Concepts,
static_assertinside CRTP can be replaced by arequiresclause on the template parameter, giving cleaner error messages. CRTP and Concepts complement each other well.
CRTP vs. Virtual Functions: The Complete Comparison
// Scenario: implementing a game entity update system
// --- Virtual approach ---
class EntityVirtual {
public:
virtual void update(float dt) = 0;
virtual void render() = 0;
virtual ~EntityVirtual() = default;
};
class PlayerVirtual : public EntityVirtual {
public:
void update(float dt) override { /* player update */ }
void render() override { /* player render */ }
};
// Usage:
vector<EntityVirtual*> entities; // Can mix Players, Enemies, etc.
for (auto* e : entities) {
e->update(0.016f); // Virtual dispatch on every call
e->render(); // Virtual dispatch on every call
}
// --- CRTP approach ---
template<typename Derived>
class EntityCRTP {
public:
void update(float dt) {
static_cast<Derived*>(this)->updateImpl(dt);
}
void render() {
static_cast<Derived*>(this)->renderImpl();
}
};
class PlayerCRTP : public EntityCRTP<PlayerCRTP> {
public:
void updateImpl(float dt) { /* player update */ }
void renderImpl() { /* player render */ }
};
// Usage:
vector<PlayerCRTP> players; // Homogeneous — only PlayerCRTP
for (auto& p : players) {
p.update(0.016f); // Direct call, inlinable
p.render();
}
// For heterogeneous collections: must use virtual or type erasure| Feature | Virtual Functions | CRTP |
|---|---|---|
| Runtime polymorphism | ✓ Full support | ✗ Not supported |
| Heterogeneous containers | ✓ vector<Base*> | ✗ Requires template tricks |
| Dispatch overhead | Indirect call (vtable) | Direct call (inlinable) |
| Memory overhead | 8-byte vtable pointer per object | Zero |
| Inlining by compiler | Rarely (indirect call) | Yes (direct call, profile-guided) |
| Compile-time enforcement | ✗ Link error if not overridden | ✓ static_assert or Concepts |
| Code complexity | Low | Moderate |
| Error messages | Clear (missing override) | Complex (template backtrace) |
| C++ version needed | C++98 | C++11 minimum |
| Use case | Runtime behavior selection | Compile-time-known type |
Advanced CRTP: Derived Member Access
A more advanced CRTP technique allows the base class to access members of the derived class, not just call methods:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// Base provides sorting utilities using derived class's comparison
template<typename Derived>
class Sortable {
public:
using value_type = typename Derived::value_type;
void sortAscending() {
auto& d = derived();
sort(d.begin(), d.end());
}
void sortDescending() {
auto& d = derived();
sort(d.begin(), d.end(), greater<value_type>());
}
value_type findMin() const {
const auto& d = derived();
return *min_element(d.begin(), d.end());
}
value_type findMax() const {
const auto& d = derived();
return *max_element(d.begin(), d.end());
}
double average() const {
const auto& d = derived();
if (d.empty()) return 0.0;
double sum = 0;
for (const auto& v : d) sum += v;
return sum / d.size();
}
private:
Derived& derived() { return static_cast<Derived&>(*this); }
const Derived& derived() const { return static_cast<const Derived&>(*this); }
};
// A typed numeric vector with built-in statistics
class NumericVector
: public vector<double>
, public Sortable<NumericVector>
{
public:
using value_type = double; // Required by Sortable
using vector<double>::vector; // Inherit constructors
void print() const {
cout << "[";
for (size_t i = 0; i < size(); i++) {
if (i > 0) cout << ", ";
cout << (*this)[i];
}
cout << "]" << endl;
}
};
int main() {
NumericVector v = {5.0, 1.5, 8.2, 3.7, 2.1, 9.0, 4.4};
cout << "Original: "; v.print();
cout << "Min: " << v.findMin() << endl;
cout << "Max: " << v.findMax() << endl;
cout << "Avg: " << v.average() << endl;
v.sortAscending();
cout << "Ascending: "; v.print();
v.sortDescending();
cout << "Descending: "; v.print();
return 0;
}Output:
Original: [5, 1.5, 8.2, 3.7, 2.1, 9, 4.4]
Min: 1.5
Max: 9
Avg: 4.91429
Ascending: [1.5, 2.1, 3.7, 4.4, 5, 8.2, 9]
Descending: [9, 8.2, 5, 4.4, 3.7, 2.1, 1.5]Sortable<Derived> accesses d.begin(), d.end(), d.empty(), and d.size() through the derived reference — calling the vector<double> methods inherited by NumericVector. This works because Derived = NumericVector, and after the cast, d is a NumericVector& with all of vector<double>‘s interface available.
Common Mistakes with CRTP
Mistake 1: Calling derived methods in the base class constructor.
template<typename Derived>
class Base {
Base() {
// WRONG: Derived part not yet constructed!
static_cast<Derived*>(this)->initialize();
// The Derived object doesn't exist yet at this point —
// calling its methods is undefined behavior
}
};
// Fix: use a two-phase initialization or call initialize() in Derived's constructorMistake 2: Forgetting const on derived() accessor.
template<typename Derived>
class Mixin {
// Missing const version: const methods can't call derived()
Derived& derived() { return static_cast<Derived&>(*this); }
// Must also have:
const Derived& derived() const {
return static_cast<const Derived&>(*this);
}
};Mistake 3: Accidental slicing.
template<typename D> class Base { /* ... */ };
class Child : public Base<Child> { int extra; };
Child c;
Base<Child>& ref = c; // OK: reference, no slicing
Base<Child> copy = c; // SLICING: copies only Base<Child> part, loses extraMistake 4: Mixing CRTP with virtual in confusing ways.
// Avoid: virtual + CRTP on the same method creates confusion
template<typename D>
class Base {
virtual void method() { // virtual AND CRTP? Pick one
static_cast<D*>(this)->methodImpl();
}
};
// If you need virtual, use virtual. If you need CRTP, use CRTP.Mistake 5: Template parameter mismatch.
class Wrong : public Base<Circle> { // Should be Base<Wrong>
// Now static_cast<Circle*>(this) would cast Wrong* to Circle* — UB!
};CRTP in the Wild: Standard Library Uses
CRTP appears throughout the C++ ecosystem:
std::enable_shared_from_this<T>— CRTP base that lets an object createshared_ptrs to itself safely:class Widget : public enable_shared_from_this<Widget> { ... }- Boost.Iterator —
iterator_facade<Derived, ...>provides a complete iterator interface from justdereference(),increment(), andequal()implementations in the derived class - Eigen (linear algebra library) — uses CRTP extensively for expression templates that represent matrix operations without materializing intermediate results
- LLVM/Clang — uses CRTP for visitor patterns and AST traversal, avoiding virtual dispatch in the compiler’s hot paths
std::rel_ops(deprecated) and the Spaceship operator<=>—std::rel_opswas a namespace that provided all comparison operators from==and<; the modern replacement is<=>andComparable-style CRTP mixins
Conclusion
The Curiously Recurring Template Pattern is one of C++’s most elegant and powerful idioms. By parameterizing a base class with the derived class type, it gives the base class compile-time access to derived class methods — enabling static polymorphism, zero-overhead mixins, and compile-time interface enforcement.
Static polymorphism via CRTP eliminates virtual dispatch overhead: direct calls instead of vtable lookups, inlinable methods, and no per-object vtable pointer. In tight loops over homogeneous collections — game entities, numerical computations, embedded system callbacks — this can produce dramatic speedups (4x and beyond) compared to virtual dispatch.
Mixin classes via CRTP let you add rich functionality to any class that provides a minimal interface. Comparable<T> provides six operators from one method. Printable<T> provides a full suite of output methods from toString(). Countable<T> tracks per-type instance counts. Each mixin is reusable, composable with multiple inheritance, and adds zero runtime overhead.
Interface enforcement via CRTP provides compile-time checks that derived classes implement required methods, catching errors at compile time rather than at link time or runtime.
The key limitation is the loss of runtime polymorphism: CRTP types in the same hierarchy are different, unrelated C++ types and cannot be stored in heterogeneous containers or dispatched through a common base pointer. When runtime behavior selection is needed — choosing which concrete type to use at runtime — virtual functions remain the appropriate tool. When types are known at compile time and performance is critical, CRTP is the right choice.
C++20 Concepts complement CRTP beautifully: instead of static_assert inside CRTP bases for interface enforcement, Concepts express the required interface cleanly and produce human-readable error messages. In modern C++, CRTP for static polymorphism and mixins, combined with Concepts for interface constraints, represents the state of the art in zero-overhead generic programming.








