Curiously Recurring Template Pattern (CRTP)
Introduction#
A pattern in which a class inherits from a class template with itself as one of its template parameters. CRTP is usually used to provide static polymorphism in C++.
The Curiously Recurring Template Pattern (CRTP)
CRTP is a powerful, static alternative to virtual functions and traditional inheritance that can be used to give types properties at compile time. It works by having a base class template which takes, as one of its template parameters, the derived class. This permits it to legally perform a static_cast
of its this
pointer to the derived class.
Of course, this also means that a CRTP class must always be used as the base class of some other class. And the derived class must pass itself to the base class.
Let’s say you have a set of containers that all support the functions begin()
and end()
. The standard library’s requirements for containers require more functionality. We can design a CRTP base class that provides that functionality, based solely on begin()
and end()
:
#include <iterator>
template <typename Sub>
class Container {
private:
// self() yields a reference to the derived type
Sub& self() { return *static_cast<Sub*>(this); }
Sub const& self() const { return *static_cast<Sub const*>(this); }
public:
decltype(auto) front() {
return *self().begin();
}
decltype(auto) back() {
return *std::prev(self().end());
}
decltype(auto) size() const {
return std::distance(self().begin(), self().end());
}
decltype(auto) operator[](std::size_t i) {
return *std::next(self().begin(), i);
}
};
The above class provides the functions front()
, back()
, size()
, and operator[]
for any subclass which provides begin()
and end()
. An example subclass is a simple dynamically allocated array:
#include <memory>
// A dynamically allocated array
template <typename T>
class DynArray : public Container<DynArray<T>> {
public:
using Base = Container<DynArray<T>>;
DynArray(std::size_t size)
: size_{size},
data_{std::make_unique<T[]>(size_)}
{ }
T* begin() { return data_.get(); }
const T* begin() const { return data_.get(); }
T* end() { return data_.get() + size_; }
const T* end() const { return data_.get() + size_; }
private:
std::size_t size_;
std::unique_ptr<T[]> data_;
};
Users of the DynArray
class can use the interfaces provided by the CRTP base class easily as follows:
DynArray<int> arr(10);
arr.front() = 2;
arr[2] = 5;
assert(arr.size() == 10);
Usefulness: This pattern particularly avoids virtual function calls at run-time which occur to traverse down the inheritance hierarchy and simply relies on static casts:
DynArray<int> arr(10);
DynArray<int>::Base & base = arr;
base.begin(); // no virtual calls
The only static cast inside the function begin()
in the base class Container<DynArray<int>>
allows the compiler to drastically optimize the code and no virtual table look up happens at runtime.
Limitations:
Because the base class is templated and different for two different DynArray
s
it is not possible to store pointers to their base classes in an type-homogenous array as one could generally do with normal inheritance where the base class is not dependent on the derived type:
class A {};
class B: public A{};
A* a = new B;
CRTP to avoid code duplication
The example in Visitor Pattern provides a compelling use-case for CRTP:
struct IShape
{
virtual ~IShape() = default;
virtual void accept(IShapeVisitor&) const = 0;
};
struct Circle : IShape
{
// ...
// Each shape has to implement this method the same way
void accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
// ...
};
struct Square : IShape
{
// ...
// Each shape has to implement this method the same way
void accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
// ...
};
Each child type of IShape
needs to implement the same function the same way. That’s a lot of extra typing. Instead, we can introduce a new type in the hierarchy that does this for us:
template <class Derived>
struct IShapeAcceptor : IShape {
void accept(IShapeVisitor& visitor) const override {
// visit with our exact type
visitor.visit(*static_cast<Derived const*>(this));
}
};
And now, each shape simply needs to inherit from the acceptor:
struct Circle : IShapeAcceptor<Circle>
{
Circle(const Point& center, double radius) : center(center), radius(radius) {}
Point center;
double radius;
};
struct Square : IShapeAcceptor<Square>
{
Square(const Point& topLeft, double sideLength) : topLeft(topLeft), sideLength(sideLength) {}
Point topLeft;
double sideLength;
};
No duplicate code necessary.