This is a short introduction to the design paradigm known as the Curiously Recurring Template Pattern, or CRTP. CRTP allows us to share code between classes. These classes may or may not be related; the point is that they must have some kind of shared code. CRTP creates generic code more efficiently than inheritance. It also avoids coupling our classes together through inheritance, which helps to keep our class hierarchies clean and easy-to-understand.
Code Reuse through Inheritance
There are a number of ways to achieve code reuse. One of the most basic is through simple class inheritance, where a base class implements some operations that are shared by the derived classes. Let’s consider we have a class Base
that provides some common operations, which are shared by derived classes A
and B
.
The main criticism with this is that it is hard to get right. Any concrete operation implemented in Base
must be applicable to each of the derived classes. If this is not the case for any operation implemented in Base
, then the classes are too tightly coupled.
Inheritance results in tightly-coupled classes that can be confusing to follow. Inheritance is best used when we have pure interface classes, but pure interfaces do not enable code reuse.
Code Reuse through CRTP
CRTP achieves the same code reuse without tightly coupling the classes together. It does this by instantiating a separate base class for each of the derived classes, which results in non-branching, linear, class hierarchies.
The base class (Common
) will implement common code. The derived classes will implement code specific to that individual class. The base class can use any of the class-specific code made publicly available by the derived class.
Implementation Example: A Common Printer
Consider a number of classes that have a name()
operation. We would like to implement a common function that will print each classes' name (for simplicity, it will use std::cout
). There are a number of ways to do this, and we will see how each one works and consider the benefits and drawbacks of each method.
A Common Printer Using CRTP
Since this is a post at CRTP, and we’ve only talked generally about CRTP until now, let’s see how it may be implemented.
Each class has its own name, and so this is the unique (class-specific) information that will be provided by each of the classes. In our example, we’ll have two classes: a Cat
and a Dog
.
The printer is common. It will call std::cout
to print the output of the name()
operation. As a result, it goes into the common templated base class, which we will call Printer<T>
. It’s time to see how this is implemented.
template <typename T>
class Printer {
public:
void print() const
{
const T& derived = static_cast<const T&>(*this);
std::cout << derived.name();
}
};
class Cat : public Printer<Cat> {
public:
const char* name() const
{
return "Meow Machine";
}
};
class Dog : public Printer<Dog> {
public:
const char* name() const
{
return "Woofer";
}
};
Let’s look at this step-by-step. First, we have the simple Cat
and Dog
classes, which implement their class-specific code. These curiously inherit from our CRTP class Printer
, passing their own class as the template parameter. This means that Cat
and Dog
will inherit all the public information from the Printer
, specialized on their class.
The Printer
statically casts itself into the template class T
and stores it in derived
. At this point, the Printer
can access any of the public members of the derived class. This conversion is safely done at compile time, and doesn’t result in any instructions.
Also note that we use const&
for our CRTP type. The const
is only valid if everything is, actually, const
(and should be omitted if this is not the case). Some older examples of CRTP may use pointers instead of references. As a matter of style, I prefer references whenever possible (because references cannot be null, which eliminates some error checking).
A Common Printer Using Inheritance
The above example can be implemented easily using inheritance as well. Our Printer
will be a base class that implements the print()
function and leaves name()
as a purely virtual function that each implementation can provide. First, the UML diagram will look something like this:
And the implementation will look like this:
class Printer {
public:
virtual ~Printer() = default;
virtual const char* name() const = 0;
void print() const
{
std::cout << name();
}
};
class Cat : public Printer {
public:
const char* name() const override
{
return "Meow Machine";
}
};
class Dog : public Printer {
public:
const char* name() const override
{
return "Woofer";
}
};
Importantly, you can see a one-to-one mapping between CRTP and inheritance. This is an important point: CRTP is largely analogous to conventional inheritance. The primary benefit is that we have not coupled two fundamentally unrelated classes, Cat
and Dog
, to create a common printer.
There are also some technical drawbacks as well. Since we have virtual functions, each of our classes is saddled with a virtual function table. Inheritance results in more bloat and is slower than CRTP. In the inheritance example where we instantiate a Dog
and a Cat
object and call print()
on them, the resulting code using inheritance is 91 lines of instructions using gcc-11.1
with -O3
. The CRTP example above results in 26 lines of instructions. Of course, lines of instructions is not necessarily proportional to speed, and without doing a benchmark we don’t know how much faster the CRTP example is in practice.
Use Cases for CRTP: Facade Pattern
CRTP allows for code reuse between classes that are otherwise (potentially) unrelated. For example, you may have a number of classes that act like standard containers. They may implement common operations like size()
and operator[]
as well as some value-added class-specific operators. Fundamentally, these classes will be unrelated, but they share similar code. Vandevoorde et al calls this the facade pattern.
There are a number of potential use cases for facades.
- In pre-C++20, a single
operator<
could be defined that is extended by the facade to defineoperator>
,operator==
, andoperator!=
. (Note that, as of C++20, the spaceship operator can be used to achieve this). - Facades can be defined to provide common container-like interfaces for otherwise unrelated types.
References
D. Vandevoorde, N. Josuttis, D. Gregor. C++ Templates: The Complete Guide. 2nd edition. Addison-Wesley: New Jersey, 2017.
J. Boccara, “The Curiously Recurring Template Pattern (CRPT),” FluentC++, 12 May 2017. [Online]. Accessed 18 May 2017.
>> Home