some tips about the curiously recurring template pattern (CRTP) in cpp. The example comes from this video.
Naive version
Assume there is general class Shape, and then there is Square and Rectangle, which is derived class. The code looks like this
struct Shape { virtual ~Shape() = default; };
struct Square : public Shape { // when using new to create the object // the value in parathesis can be the initilizer // for example // int* p = new int(7); means dynamically allocated int with value 7 // the value of the int is default if we use the new int() Square* Clone() { return new Square(*this); } int x = 1; };
struct Rectangle : public Shape { Rectangle* Clone() { return new Rectangle(*this); } int x = 1; int y = 2; };
int main() { Square s1; // the only difference of the clone function is the type Square* s3 = s1.Clone(); return 0; }
The issue of the code is that (1) the Clone function looks same for two subclass and it looks redoundant. Besides, (2) if there is a pointer to Shape, it can not call the Clone function, since it does not know what is the concrete type and the Clone function is not in the Base class.
Polymorphic version
One common strategy is to use the pure virtual function in the base class and then implement concrete functions in subsequent class.
struct Square : public Shape { // when using new to create the object // the value in parathesis can be the initilizer // for example // int* p = new int(7); means dynamically allocated int with value 7 // the value of the int is default if we use the new int() virtual Square* Clone() override { std::cout << "clone Square" << std::endl; return new Square(*this); } int x = 1; };
struct Rectangle : public Shape { virtual Rectangle* Clone() override { std::cout << "clone Rectangle" << std::endl; return new Rectangle(*this); } int x = 1; int y = 2; };
int main() { // the only difference of the clone function is the type // although we can use the virtual function // there are still two clones implementation Square s1; Square* s2 = s1.Clone();
struct Rectangle : public Shape { int x = 1; int y = 2; };
int main() { // the only difference of the clone function is the type // although we can use the virtual function // there are still two clones implementation Square s1; //Shape* s2 = s1.Clone<Square>(); /* error here: no matching constructor for initialization of 'Square' */ Shape* s2 = s1.Clone<Shape>();
for (auto s : shapelist) { Shape* tempShape = s->Clone<Shape>(); }
return 0; }
In this code, we use the template function in the parent class. But the issue is, the parent class do not know the information about the subclass, it can not call the correct clone function implemented by derived class.
That is how curiously recurring template pattern works here, it use another struct as the intermediate layer to help find the correct version of the class.
//the middle layer class that knows the information of the dereived class template <typename T> struct ShapeCRTP : public Shape { virtual Shape* Clone() override { std::cout << "clone ShapeCRTP " << typeid(T).name() << std::endl; return new T(*static_cast<T*>(this)); }; };
struct Square : public ShapeCRTP<Square> { int x = 1; };
struct Rectangle : public ShapeCRTP<Rectangle> { int x = 1; int y = 2; };
int main() { // the only difference of the clone function is the type // although we can use the virtual function // there are still two clones implementation Square s1; Shape* s2 = s1.Clone();
for (auto s : shapelist) { Shape* tempShape = s->Clone(); }
return 0; }
In this code, the concrete class inheriet from the ShapeCRTP and also provide their own class information during the declaration. When call the clone function, it does not need to add the template name signature, since the related information is set to the ShapeCRTP during the initilization of the class. By this way, we achieve the goal: (1) do not contains the repeated function or similar implementation in the child class (2) support the polymorphic and use the parent class to manage all the derived class.