C++ Curiously Recurring Template Pattern (CRTP)

Cengizhan Varlı
5 min readAug 9, 2024

--

The Curiously Recurring Template Pattern (CRTP) is a powerful idiom in C++ that enables static polymorphism. Unlike traditional polymorphism achieved through virtual functions, CRTP provides compile-time polymorphism, which can lead to more efficient and safer code.

CRTP is a design pattern where a class Derived inherits from a template class Base that takes Derived as a template parameter. This pattern can be used to avoid the runtime overhead associated with virtual functions.

Basic structure of CRTP;

template <typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}

void implementation() {
// Default implementation or static assert
}
};

class Derived : public Base<Derived> {
public:
void implementation() {
// Derived class implementation
}
};

In this structure, Base provides an interface method that calls the implementation method of the Derive class. The static_cast<Derived*>(this) allows the Base class to call methods from the Derived class, achieving compile-time polymorphism.

Unlike virtual functions that resolve at runtime, CRTP resolves at compile time. This can lead to performance improvements because it eliminates the need for virtual table lookups. Virtual functions incur runtime overhead due to the maintenance of a vtable (virtual table). CRTP avoids this overhead by resolving method calls at compile time. CRTP can be used to implement various design patterns, such as mixins and policy-based design, allowing for more flexible and reusable code.

Comparing CRTP and Virtual Functions

To illustrate the differences between CRTP and virtual functions, let’s consider an example where both approaches are used to achieve polymorphism.

First, let’s look at an example using traditional inheritance. We have a Base class and a derived Derived class.

#include <iostream>

class Base {
public:
void interface() const {
std::cout << "Base::interface called.\n";
implementation();
}

virtual void implementation() const {
std::cout << "Base::implementation called.\n";
}
};

class Derived : public Base {
public:
void implementation() const override {
std::cout << "Derived::implementation called.\n";
}
};

int main() {
Derived d;
d.interface();
return 0;
}

OUTPUT:

Base::interface called.
Derived::implementation called.

In this example, the Derived class inherits from the Base class and overrides the implementation function. The interface function is defined in the Base class and calls the implementation function of the Derived class. However, this structure incurs a slight performance overhead due to the use of the virtual function table (vtable). Also, virtual functions are resolved at runtime, which imposes certain limitations on type safety.

Now, let’s write the same example using CRTP:

#include <iostream>

template <typename Derived>
class BaseCRTP {
public:
void interface() const {
std::cout << "BaseCRTP::interface called.\n";
static_cast<const Derived*>(this)->implementation();
}

void implementation() const {
std::cout << "BaseCRTP::implementation called.\n";
}
};

class DerivedCRTP : public BaseCRTP<DerivedCRTP> {
public:
void implementation() const {
std::cout << "DerivedCRTP::implementation called.\n";
}
};

int main() {
DerivedCRTP d;
d.interface();
return 0;
}

OUTPUT:

BaseCRTP::interface called.
DerivedCRTP::implementation called.

In this example, the BaseCRTP class is a template class, and the derived class passes its own type as a template parameter to BaseCRTP. This allows the interface function in BaseCRTP to directly call the implementation function of the DerivedCRTP class.

Of course, If implemantation function is not implemented by DerivedCRTP class, function of the BaseCRTP class would be called like follows;

#include <iostream>

template <typename Derived>
class BaseCRTP {
public:
void interface() const {
std::cout << "BaseCRTP::interface called.\n";
static_cast<const Derived*>(this)->implementation();
}

void implementation() const {
std::cout << "BaseCRTP::implementation called.\n";
}
};

class DerivedCRTP : public BaseCRTP<DerivedCRTP> {

};

int main() {
DerivedCRTP d;
d.interface();
return 0;
}

OUTPUT:

BaseCRTP::interface called.
BaseCRTP::implementation called.

An Example Using CRTP

We’ll create a template base class Shape that will take the derived class as a template parameter.

#include <iostream>
#include <cmath>

template <typename Derived>
class Shape {
public:
// Common interface for calculating area
double area() const {
return static_cast<const Derived*>(this)->calculateArea();
}
};

And then, we define the derived classes Circle, Rectangle and Triangle, each of which will provide its own implementation of the calculateArea() function.


class Circle : public Shape<Circle> {
public:
Circle(double radius) : m_radius(radius)
{}

double calculateArea() const {
return M_PI * m_radius * m_radius;
}

private:
double m_radius;
};

class Rectangle : public Shape<Rectangle> {
public:
Rectangle(double width, double height) : m_width(width), m_height(height)
{}

double calculateArea() const {
return m_width * m_height;
}

private:
double m_width, m_height;
};

class Triangle : public Shape<Triangle> {
public:
Triangle(double base, double height) : m_base(base), height_(m_height)
{}

double calculateArea() const {
return 0.5 * m_base * m_height;
}

private:
double m_base, m_height;
};

Now, let’s create instances of these shapes and calculate their areas using the area() function from the Shape base class.

int main() {
Circle circle(5.0);
Rectangle rectangle(4.0, 6.0);
Triangle triangle(3.0, 7.0);

std::cout << "Area of the circle: " << circle.area() << "\n";
std::cout << "Area of the rectangle: " << rectangle.area() << "\n";
std::cout << "Area of the triangle: " << triangle.area() << "\n";

return 0;
}

OUTPUT:

Area of the circle: 78.5398
Area of the rectangle: 24
Area of the triangle: 10.5

In this example, the Shape class provides a common interface area() that derived classes implement using calculateArea(). The key difference with CRTP is that this interface is resolved at compile time, avoiding the runtime overhead of virtual function calls. The area() function in the Shape class calls calculateArea() using static_cast<const Derived*>(this)->calculateArea(). This cast ensures that the correct calculateArea function from the derived class is called, based on the actual type of the object. Since there are no virtual function calls, the code is more efficient, especially in performance-critical applications. The compiler can inline these calls and apply other optimizations, leading to faster execution. CRTP also enhances type safety. The Shape class template enforces that the derived class must implement the calculateArea() function. If a derived class does not implement this function, a compile-time error will occur. This pattern also allows for greater flexibility. For instance, if a new shape class needs to be added, it can be easily integrated without modifying the base Shape class, adhering to the Open/Closed Principle.

Conclusion

CRTP resolves inheritance relationships at compile-time, allowing for better optimization and avoiding the use of the virtual function table. CRTP provides a slight performance boost by making direct calls instead of relying on virtual functions. CRTP offers greater type safety during inheritance, preventing incorrect type usage.

Unlike traditional inheritance, which relies on runtime mechanisms like virtual tables, CRTP provides a more performant alternative by resolving polymorphic behavior at compile time.

CRTP is not just a theoretical concept but a practical tool used in many real-world applications, particularly in performance-critical systems. It allows developers to harness the power of compile-time metaprogramming while retaining the expressiveness and clarity of object-oriented design.

However, with great power comes great responsibility. CRTP, like any advanced C++ feature, requires careful design and consideration. Misuse or overuse can lead to code that is difficult to understand, maintain, or debug. Thus, while CRTP can greatly enhance the efficiency and safety of your code, it should be employed judiciously, with a clear understanding of its implications.

NOTE

The purpose of this document is not a recommendation but only an explanation

Thank a lot.

--

--

Responses (3)