Curiously Recurring Template Pattern (CRTP)
Learned in CS247.
When to use this pattern?
There are 2 use cases:
- Reduce boilerplate code while maintaining compile-time polymorphism
- Polymorphic cloning
So it seems that with CRTP, you always have 3 layers in the hierarchy, due to the nature of the templates, which means that they are actually different superclasses, so you have to have these superclasses inherit from a higher superclass that doesn’t use templates.
Motivation
Introduced after showcasing Visitor Pattern, and how there was much code, because there is going to be a lot of methods: if we have subclasses of and subclasses of : different methods to write.
Annoying part: Boilerplate - write the following in each DerivedEnemy
:
class DerivedEnemy: public Enemey {
public:
void beStruckBy(Weapon& w) {
w.strike(*this);
}
};
- must be one for every single
DerivedEnemy
- Cannot put it in
Enemy
:
class Enemy {
public:
void beStruckBy(Weapon& w) {
w.strike(*this); // this is just an Enemy
}
};
However, notice that this doesn’t work - the type of this
is wrong. It’s not telling us what the dynamic type is.
Solution to fixing the boilerplate code is to use Curiously Recurring Template Pattern (CRTP).
Example 1
Template our superclass with a type parameter - inherit, AND substitute the derived class type.
template<typename T> class Base {
...
};
class Derived: public Base<Derived> { // At this point, we know Derived is a class.
... // use T in Base as if had provided a forward declaration
}
How do we use this to fix the boilerplate code?
template<typename T> class Enemy {
public:
void beStruckBy() {
w.strike(*static_cast<T*>(this));
}
};
class Monster: public Enemy<Monster> {...};
class Turtle: public Enemy<Turtle> {...};
This sort of works
Weapon* w = ...;
Turtle t{...};
t.beStruckBy(*w); // Calls Enemy<Turtle>::beStruckBy
Cast this
from type Enemy<Turtle>*
to Turtle*
allows us to override into Rock::Strike(Turtle&)
and Stick::Strike(Turtle&)
.
Issue: Now, we have different superclasses for each Enemy
Because Enemy<Turtle>
and Enemy<Monster>
are different classes: can no longer use Enemy
s polymorphically - no vector<Enemy*>
allowed!
Solution: Add another layer of inheritance.
class Enemy {
public:
virtual void beStruckBy(Weapon& w) = 0;
virtual ~Enemy() {}
};
template<typename T> class EnemyBeStruck: public Enemy {
public:
void beStruckBy(Weapon& w) override {
w.strike(*static_cast<T*>(this));
}
virtual ~EnemyBeStruck() = 0;
}
template<typename T> EnemyBeStruck<T>::~EnemyBeStruck<T>() {}
class Turtle: public EnemyBeStruck<Turtle> {...}
class Monster: public EnemyBeStruck<Monster> {...}
Now, we have a public interface by which all our concrete enemies follow: can all beStruckBy
weapons. We use the virtual method in Enemy
to resolve beStruckBy
to either EnemyBeStruck<Turtle>
or EnemyByStruck<Monster>
. Then just static_cast
to T*
- and we’re good.
Weapon* w = ...;
Enemy* e = new Turtle{...} / new Monster{...};
e->beSstruckBy(*w);
Example #2
Another problem CRTP can solve: polymorphic cloning.
Recall abstract books:
Say I have:
AbstractBook* b = ...;
I want a deep copy of whatever b points to. I cannot just do this:
AbstractBook* b2 = new AbstractBook{*b};
This attempts to create an AbstractBook
by invoking its constructor. Wrong for 2 reasons:
AbstractBook
is abstract, cannot instantiate those objects- Ignoring what we’re actually pointing at, we actually want to invoke a constructor that depends on the dynamic type of
b
We can provide a virtual clone
method for the purpose of solving this:
class AbstractBook {
public:
virtual AbstractBook* clone() = 0;
virtual ~AbstractBook() {};
};
class Text: public AbstractBook {
public:
Text* clone() override {
return new Text{title, author, length, topic};
}
};
// Comic and NormalBook are similar
AbstractBook* b = ...;
AbstractBook* b2 = b->clone();
Instead use the copy ctor in each of our clone
methods to simplify the implementation.
class Text: public AbstractBook {
public:
Text* clone() override {
return new Text{*this};
}
};
Exact same code in MermaidBook
and Comic
- just the type of this
and the type of ctor which is changing. Once again, we can use CRTP.
class AbstractBook {
public:
virtual AbstractBook* clone() = 0;
virtual ~AbstractBook() {};
};
template<typename T> class BookClonable: public AbstractBook {
public:
T* clone() override {
return new T{*static_cast<T*>(this)};
}
virtual ~BookClonable() = 0;
};
template<typename T>
BookClonable<T>::~BookClonable<T>() {};
class Text: public BookClonable<Text> {...};
class Comic: public BookClonable<Comic> {...};
AbstractBook* b = new Text{...} / new Comic {...};
AbstractBook* b2= b->clone();
b->clone
is virtual, so if b
points at a Comic
, we call BookClonable<Comic>::clone
- static_cast
this
into a Comic*
and invoke the Comic
copy constructor with the Comic&
.
Provided for all subclasses - reduces boilerplate code.