CRTP
Curiously Recurring Template Pattern
1. What is CRTP?
A CRTP is where a Derived class:
- Inherits from a template class.
- Uses the derived class itself as a template parameter of the base class.
CRTT enables static polymorphism
, reducing the overhead of dynamic dispatch(dynamic polymorphism) when using virtual functions
.
1 |
|
2. Two Forms of CRTP
2.1 Static Interface
This usage of CRTP is really similar to the runtime polymorphism, where the Base class represents the interface(no actual implementation) and the Derived one represents the detailed implementation of that interface.
The difference is that there is no vtable and associated vptr in the code. All the polymorphic calls are resolved at compile time, avoiding the runtime virtual function look-up cost. Since all the information is known at compile time, It is the developers’ responsibility to carefully design the inheritance hierarchy and make sure the right polymorphic calls are correctly implemented, otherwise, we will get runtime errors of “undefined reference” to some functions.
1 |
|
2.2 Adding Functionalities
Adding functionalities(also called mixin pattern) allows the Base class to provide reusable functionalities that can operate on Derived classes. For example, let’s say we have different logging behaviors for different Systems.
1 | class Application : public Base<Application> { |
If other systems also require this logging capability, do we need to duplicate the same implementation in each new derived class? With CRTP
, we can encapsulate the common functionality in the Base class and seamlessly apply it to all Derived classes, eliminating redundant code.
1 |
|
3. Can we remove static_cast?
As we probably can see, we have to mannually cast the Base this
pointer to a Derived type in order to use Derived functions. Extensive use of static_cast
makes the code looks heavy. Are we able to get rid of it?
The answer is YES. In C++ 23, we can use explicit object parameter
to explicitly use the Derived object as a parameter, avoiding the need of static_cast
.
1 |
|
4. A Perfect Replacement for Virtual Functions?
Though CRTP
avoids the runtime overhead, it is not always a perfect replacement for virtual functions. I think the two most significant limitations are:
One major downside of
CRTP
is that every derived class creates a new instantiation of the base class template, which can lead to code bloat and prevent shared base-class behavior across different derived types.With virtual functions, you can store objects of different derived types in a
std::vector<Base*>
. With CRTP, this is not possible, as the base class is templated and does not define a common type.