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;
}

🧠 Test Your Knowledge

What is the main advantage of CRTP over virtual functions?