CRTP (Curiously Recurring Template Pattern)
Static polymorphism through template inheritance
🔄 What is CRTP?
CRTP is a design pattern where a class inherits from a template base class, passing itself as the template parameter. This enables static polymorphism, compile-time method resolution, and eliminates virtual function overhead while maintaining polymorphic behavior.
// CRTP pattern
template<typename Derived>
class Base {
// Base class uses Derived type
};
class Child : public Base<Child> {
// Child inherits from Base<Child>
};
Key CRTP Concepts
Static Polymorphism
Polymorphism resolved at compile time
static_cast<Derived*>(this)
->derivedMethod();
No Virtual Overhead
No vtable lookup, direct function calls
// No virtual keyword needed
void interface() { /* ... */ }
Code Reuse
Share common functionality across types
template<typename T>
class Mixin { /* shared code */ };
Type Safety
Compile-time type checking
Derived& derived() {
return static_cast<Derived&>(*this);
}
🔹 Basic CRTP Implementation
The Curiously Recurring Template Pattern (CRTP) is a C++ technique where a class derives from a template base class using itself as the template argument. This enables static polymorphism, allowing method calls to be resolved at compile-time for efficiency. It's commonly used for adding functionality through mixins, like a Cloneable interface, without the overhead of virtual functions.
#include <iostream>
using namespace std;
// CRTP Base class
template<typename Derived>
class Shape {
public:
void draw() {
// Static cast to derived type and call its method
static_cast<Derived*>(this)->drawImpl();
}
void info() {
cout << "Shape info: ";
static_cast<Derived*>(this)->drawImpl();
}
double area() {
return static_cast<Derived*>(this)->calculateArea();
}
};
// Derived classes
class Circle : public Shape<Circle> {
private:
double radius;
public:
Circle(double r) : radius(r) {}
void drawImpl() {
cout << "Drawing a circle with radius " << radius << endl;
}
double calculateArea() {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Shape<Rectangle> {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
void drawImpl() {
cout << "Drawing a rectangle " << width << "x" << height << endl;
}
double calculateArea() {
return width * height;
}
};
// Template function that works with any Shape
template<typename T>
void processShape(Shape<T>& shape) {
shape.draw();
cout << "Area: " << shape.area() << endl;
}
int main() {
Circle circle(5.0);
Rectangle rectangle(4.0, 6.0);
// Direct usage
circle.draw();
rectangle.draw();
cout << "Circle area: " << circle.area() << endl;
cout << "Rectangle area: " << rectangle.area() << endl;
// Template function usage
processShape(circle);
processShape(rectangle);
return 0;
}
Output:
Drawing a circle with radius 5
Drawing a rectangle 4x6
Circle area: 78.5397
Rectangle area: 24
Drawing a circle with radius 5
Area: 78.5397
Drawing a rectangle 4x6
Area: 24
🔹 CRTP for Interface Implementation
CRTP can simulate interfaces without virtual functions, enforcing compile-time contracts. By defining pure abstract methods in a templated base class that casts to the derived type, you ensure derived classes implement specific behaviors. For instance, a Comparable interface can enforce operators like == and < by redirecting calls to the derived class's implementation. This is used in classes like Student and Product to compare grades or prices. The approach eliminates v-table overhead, improves inlining, and catches missing implementations at compile time rather than runtime.
#include <iostream>
#include <vector>
using namespace std;
// CRTP Interface
template<typename Derived>
class Printable {
public:
void print() {
static_cast<Derived*>(this)->printImpl();
}
void printWithBorder() {
cout << "==================" << endl;
static_cast<Derived*>(this)->printImpl();
cout << "==================" << endl;
}
};
template<typename Derived>
class Comparable {
public:
bool isEqual(const Derived& other) {
return static_cast<const Derived*>(this)->compareImpl(other);
}
bool isLess(const Derived& other) {
return static_cast<const Derived*>(this)->lessImpl(other);
}
};
// Class implementing multiple CRTP interfaces
class Student : public Printable<Student>, public Comparable<Student> {
private:
string name;
int grade;
public:
Student(string n, int g) : name(n), grade(g) {}
// Implementation for Printable
void printImpl() {
cout << "Student: " << name << " (Grade: " << grade << ")" << endl;
}
// Implementation for Comparable
bool compareImpl(const Student& other) const {
return name == other.name && grade == other.grade;
}
bool lessImpl(const Student& other) const {
return grade < other.grade;
}
// Getters
string getName() const { return name; }
int getGrade() const { return grade; }
};
class Product : public Printable<Product>, public Comparable<Product> {
private:
string name;
double price;
public:
Product(string n, double p) : name(n), price(p) {}
void printImpl() {
cout << "Product: " << name << " ($" << price << ")" << endl;
}
bool compareImpl(const Product& other) const {
return name == other.name && price == other.price;
}
bool lessImpl(const Product& other) const {
return price < other.price;
}
};
int main() {
Student alice("Alice", 85);
Student bob("Bob", 92);
Student alice2("Alice", 85);
// Using Printable interface
alice.print();
bob.printWithBorder();
// Using Comparable interface
cout << "Alice == Alice2: " << alice.isEqual(alice2) << endl;
cout << "Alice < Bob: " << alice.isLess(bob) << endl;
Product laptop("Laptop", 999.99);
Product mouse("Mouse", 25.50);
laptop.print();
cout << "Laptop < Mouse: " << laptop.isLess(mouse) << endl;
return 0;
}
Output:
Student: Alice (Grade: 85)
==================
Student: Bob (Grade: 92)
==================
Alice == Alice2: 1
Alice < Bob: 1
Product: Laptop ($999.99)
Laptop < Mouse: 0
🔹 CRTP vs Virtual Functions
CRTP offers static polymorphism, binding calls at compile time, whereas virtual functions use dynamic dispatch at runtime. Virtual functions introduce overhead via v-table lookups and hinder inlining, but allow runtime flexibility and heterogeneous collections. CRTP, in contrast, generates specialized code for each derived type, enabling aggressive compiler optimizations and zero-cost abstractions. However, CRTP cannot store different derived types in a single container without type erasure. Choose virtual functions for dynamic, extensible hierarchies and CRTP for performance-critical, type-specific logic where compile-time resolution is acceptable.
CRTP Advantages:
- Performance: No vtable lookup overhead
- Compile-time: Errors caught at compile time
- Inlining: Better optimization opportunities
- Memory: No vtable pointer in objects
Virtual Functions Advantages:
- Runtime polymorphism: Can change behavior at runtime
- Simplicity: Easier to understand and use
- Flexibility: Can store different types in same container
- Dynamic binding: Works with unknown types at compile time
// CRTP approach - compile-time polymorphism
template<typename Derived>
class CRTPBase {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
class CRTPDerived : public CRTPBase<CRTPDerived> {
public:
void implementation() { cout << "CRTP implementation" << endl; }
};
// Virtual function approach - runtime polymorphism
class VirtualBase {
public:
virtual void interface() = 0;
virtual ~VirtualBase() = default;
};
class VirtualDerived : public VirtualBase {
public:
void interface() override { cout << "Virtual implementation" << endl; }
};
// Usage comparison
void useCRTP() {
CRTPDerived obj;
obj.interface(); // Direct call, no overhead
}
void useVirtual() {
VirtualBase* obj = new VirtualDerived();
obj->interface(); // Virtual call, vtable lookup
delete obj;
}