0

A couple days ago I found an interesting C++ construct named Curiously Recurring Template Pattern (usually known under abbreviation CRTP). From that time I have been attempting to fully understand how does this trick work.

Let's say I have following code snippet

#include <iostream>

template<class T>
struct GraphicalObject
{
    void draw()
    {
        static_cast<T*>(this)->draw();
    }
};

struct Circle : GraphicalObject<Circle>
{
    void draw()
    {
        std::cout << "Drawing circle!" << std::endl;
    }
};

struct Square : GraphicalObject<Square>
{
    void draw()
    {
        std::cout << "Drawing square!" << std::endl;
    }
};

int main(int argc, char** argv) {
    
    Square s;
    Circle c;
    
    s.draw();
    c.draw();
    
    return 0;
}

All the "magic" behind the CRTP is obviously in the line static_cast<T*>(this)->draw(). But how does it work in detail? My understanding was following. Let's say I have the Circle c.

In case the compiler see this declaration it is a signal for him to instantiate the GraphicalObject template with argument Circle. I think that this results in creation of the struct GraphicalObject<Circle> by the compiler

struct GraphicalObject<Circle>
{
 void draw()
 {
  static_cast<Circle*>(this)->draw();
 }
};

So struct with a special version of the draw method is created. The struct GraphicalObject<Circle> forms the base struct for the Circle struct.

In the second step the instance c of the Circle struct is created i.e. the implicit constructor of the Circle is invoked. Due to the fact that the Circle is inherited from the GraphicalObject<Circle> the implicit constructor of that struct is invoked at first. This results in creation of an instance of the GraphicalObject<Circle> inside the c object in my opinion. Is it right? Then I think that the draw method declared in the Circle struct overrides the draw method declared in the GraphicalObject<Circle> struct. But this doesn't make sense for me. I would rather expect that the "specialized" version of the draw method comming from the GraphicalObject<Circle> shall not be overridden. Please can you tell me what is wrong in my understanding how the CRTP works?

Steve
  • 805
  • 7
  • 27
  • `Circle::draw` doesn't override `GraphicalObject::draw` (as not virtual) but hides it. – Jarod42 Mar 19 '21 at 16:36
  • And indeed, idea is to use different name. – Jarod42 Mar 19 '21 at 16:38
  • @Jarod42 thank you for your response. Does it mean that my understanding how the CRTP works is basically correct but the `Circle::draw` hides the `GraphicalObject::draw` instead of override it? – Steve Mar 19 '21 at 16:57

1 Answers1

1

You're not doing anything polymorphically in main which would demonstrate the features of CRTP. Try this instead and see what happens:

template <typename T>
void drawObject(GraphicalObject<T>& obj) {
    obj.draw();
}

int main() {
    Square s;
    Circle c;
    
    s.draw();
    c.draw();

    drawObject(s);  // what happens here?

    GraphicalObject<Circle>& c_ref = c;
    drawObject(c_ref);  // what happens here?
}
Woodford
  • 3,746
  • 1
  • 15
  • 29
  • Thank you for your response. Do I understand correctly that in case I call the `drawObject(s)` the slicing takes place and the `s` is sliced to the `GraphicalObject`. Due to this the polymorphic version of the `draw` method is invoked at first? – Steve Mar 22 '21 at 07:39
  • I think you've got it. The "sliced" ([not really slicing](https://stackoverflow.com/questions/274626/what-is-object-slicing)) `obj` in `drawObject` calls the base class `draw` method, which then calls the child class `draw` in turn. This lets you create pseudo-virtual functions without defining them as `virtual` (i.e. static polymorphism). – Woodford Mar 22 '21 at 18:08