An Implementation Helper For The Curiously Recurring Template Pattern
In this final episode of the series on the Curiously Recuring Template Pattern, let’s see an implementation that makes it easier to write CRTP classes.
In case you missed an episode in the series, here they are:
- The CRTP, episode One: Definition
- The CRTP, episode Two: What the CRTP can bring to your code
- The CRTP, episode Three: An implementation helper for the CRTP
Getting rid of static_cast
Writing repeated static_cast
s in CRTP base classes quickly becomes cumbersome, as it does not add much meaning to the code:
template <typename T> struct NumericalFunctions { void scale(double multiplicator) { T& underlying = static_cast<T&>(*this); underlying.setValue(underlying.getValue() * multiplicator); } ... };
It would be nice to factor out these static_cast
s. This can be achieved by forwarding the underlying type to a higher hierarchy level:
template <typename T> struct crtp { T& underlying() { return static_cast<T&>(*this); } T const& underlying() const { return static_cast<T const&>(*this); } };
Plus it deals with the case where the underlying object is const, which we hadn’t mentioned yet.
This helper can be used the following way:
template <typename T> struct NumericalFunctions : crtp<T> { void scale(double multiplicator) { this->underlying().setValue(this->underlying().getValue() * multiplicator); } ... };
Note that the static_cast
is gone and a this->
appeared. Without it the code would not compile. Indeed, the compiler is not sure where underlying
is declared. Even if it is declared in the template class crtp
, in theory nothing guarantees that this template class won’t be specialized and rewritten on a particular type, that would not expose an underlying
method. For that reason, names in template base classes are ignored in C++.
Using this->
is a way to include them back in the scope of functions considered to resolve the call. There are other ways to do it, although they are arguably not as adapted to this situation. In any case, you can read all about this topic in Effective C++ Item 43.
Anyway, the above code relieves you from writing the static_cast
s, which become really cumbersome when they are several of them.
All this works if you class only add one functionality via CRTP, but it stops working if there are more.
Adding several functionalities with CRTP
For the sake of the example let’s split our CRTP classes into two: one that scales values and one that squares them:
template <typename T> struct Scale : crtp<T> { void scale(double multiplicator) { this->underlying().setValue(this->underlying().getValue() * multiplicator); } }; template <typename T> struct Square : crtp<T> { void square() { this->underlying().setValue(this->underlying().getValue() * this->underlying().getValue()); } };
And add these two functionalities to the Sensitivity
class:
class Sensitivity : public Scale<Sensitivity>, public Square<Sensitivity> { public: double getValue() const { return value_; } void setValue(double value) { value_ = value; } private: double value_; };
This looks ok at first glance but does not compile as soon as we call a method of either one of the base class!
error: 'crtp<Sensitivity>' is an ambiguous base of 'Sensitivity'
The reason is that we have a diamond inheritance here:
I tried to solve this with virtual inheritance at first, but quickly gave this up because I didn’t find how to do it simply and without impacting the clients of the crtp
class. If you have a suggestion, please, voice it!
Another approach is to steer away from the diamond inheritance (which sounds like a good idea), by having every functionality (scale, square) inherit from its own crtp
class. And this can be achieved by… CRTP!
Indeed, we can add a template parameter to the crtp
class, corresponding to the base class. Note the addition of the crtpType
template parameter.
EDIT: as suggested by Daniel Houck in the comments section, the private-constructor-and-friend-with-derived technique should also be applied on this template template parameter here, because it forces Scale
to inherit from the right crtp. Note that it doesn’t force Sensitivity
to inherit from the right CRTP though, so the friend and private constructor are still needed in Scale
and Square
(thanks to Amos Bird for pointing this out).
template <typename T, template<typename> class crtpType> struct crtp { T& underlying() { return static_cast<T&>(*this); } T const& underlying() const { return static_cast<T const&>(*this); } private: crtp(){} friend crtpType<T>; };
Note that the template parameter is not just a typename
, but rather a template<typename> class
. This simply means that the parameter is not just a type, bu rather a template itself, templated over a type whose name is omitted. For example crtpType
can be Scale
.
This parameter is here to differentiate types only, and is not used in the implementation of crtp
(except for the technical check in the friend declaration). Such an unused template parameter is called a “phantom type” (or to be more accurate here we could call it a “phantom template”).
The class hierarchy now looks like the following:
and we’re good to go.
A CRTP on a CRTP. Templates are so much fun.
Related articles:
- The CRTP, episode One: Definition
- The CRTP, episode Two: What the CRTP can bring to your code
Share this post!