Curiously Recurring Template Pattern

C

chsalvia

The "Curiously Recurring Template Pattern" (CRTP) recently caught my
attention as a nice trick which allows static polymorphism. In other
words, it lets you build extensible classes without the overhead of
virtual function calls. (But of course, with the limitation of
compile-time resolving.)

Basically, you create a templated Base Class which defines an
interface function that uses a static_cast to call a member function
in Derived. Then the Derived class inherits from the templated Base
class with Derived as a template argument.

template <class Derived>
struct Base
{
void work()
{
static_cast<Derived*> (this)->work();
}
};

struct Derived : public Base<Derived>
{
void work()
{
cout << "Derived class function." << endl;
}
};

template <class T>
void func(Base<T>& Object)
{
Object.work();
}

int main()
{
Derived D;
func(D);
}

This is a nice way to make extensible classes without using runtime
polymorphism. But the more I think about it, it seems kind of
pointless. If you want to build extensible classes with static
polymorphism, you can just use templates to simply do something like
this:

template <class T>
void func(T& Object)
{
Object.work();
}

This way any class that defines a work() member function can be passed
to func(), and the correct member function will be called. This is a
lot simpler than CRTP. So what's the real advantage of CRTP then?
The only advantage I see is that CRTP works with the inheritance
hierarchy, just like normal polymorphism, rather than letting you use
any class which defines a certain member function name.

Am I correct here, or am I overlooking the value of CRTP somehow?
 
Z

Zeppe

The "Curiously Recurring Template Pattern" (CRTP) recently caught my
attention as a nice trick which allows static polymorphism. In other
words, it lets you build extensible classes without the overhead of
virtual function calls. (But of course, with the limitation of
compile-time resolving.)

Basically, you create a templated Base Class which defines an
interface function that uses a static_cast to call a member function
in Derived. Then the Derived class inherits from the templated Base
class with Derived as a template argument.

template <class Derived>
struct Base
{
void work()
{
static_cast<Derived*> (this)->work();
}
};

struct Derived : public Base<Derived>
{
void work()
{
cout << "Derived class function." << endl;
}
};

template <class T>
void func(Base<T>& Object)
{
Object.work();
}

int main()
{
Derived D;
func(D);
}

This is a nice way to make extensible classes without using runtime
polymorphism. But the more I think about it, it seems kind of
pointless. If you want to build extensible classes with static
polymorphism, you can just use templates to simply do something like
this:

template <class T>
void func(T& Object)
{
Object.work();
}

This way any class that defines a work() member function can be passed
to func(), and the correct member function will be called. This is a
lot simpler than CRTP. So what's the real advantage of CRTP then?

It's very different. Think about that, instead:

int main()
{
Base* d = new Derived();
func(*d);
}

ups! Your template approach will fail! But CRTP don't. The main
advantage of CRTP is that you can lose the reference to the real
instantiated class without losing the polymorphism. This is very useful,
for example, when you want to build a heterogeneus container like:

std::vector<Base*> v;

(Notice that you may still need a virtual destructor if the container is
the owner of the objects).

Regards,

Zeppe
 
D

Dizzy

Zeppe said:
It's very different. Think about that, instead:

int main()
{
Base* d = new Derived();
func(*d);
}

That cannot work with CRTP. You probably mean Base<Derived>* d = new
Derived(); otherwise it doesn't compile ("Base" is a family of structs,
there is no type Base from which you derived, but instead you derive from
ups! Your template approach will fail! But CRTP don't. The main
advantage of CRTP is that you can lose the reference to the real
instantiated class without losing the polymorphism.

No you cannot lose the type, that's the point (otherwise compile time
resolving of the function to call wouldnt work). The base class when
instantiated has all the knowledge about the Derived. So you cannot lose
it, you have it there all along so you need template functions as the OP
said.
This is very useful,
for example, when you want to build a heterogeneus container like:

std::vector<Base*> v;

Again impossible with CRTP.
(Notice that you may still need a virtual destructor if the container is
the owner of the objects).

Maybe you mean something different than the OP and as such I am missing your
point?
 
M

Michael DOUBEZ

(e-mail address removed) a écrit :
The "Curiously Recurring Template Pattern" (CRTP) recently caught my
attention as a nice trick which allows static polymorphism.
[snip]
This is a nice way to make extensible classes without using runtime
polymorphism. But the more I think about it, it seems kind of
pointless. If you want to build extensible classes with static
polymorphism, you can just use templates to simply do something like
this:

template <class T>
void func(T& Object)
{
Object.work();
}

This way any class that defines a work() member function can be passed
to func(), and the correct member function will be called. This is a
lot simpler than CRTP. So what's the real advantage of CRTP then?
The only advantage I see is that CRTP works with the inheritance
hierarchy, just like normal polymorphism, rather than letting you use
any class which defines a certain member function name.

Am I correct here, or am I overlooking the value of CRTP somehow?

CRTP splits generic and concrete functionality by delegating to its
derived class.

Concerning function call, there is no difference with a simple template
where the delegation is made by composition (as in your example).

IMO, what CRTP allows is in particular:
- class width operations (typically counting the instances of a class)
- static polymorphism which allows the inheritance or cooperation of
traits and useful specialization under metaprogramming (factorizing
operations by example).
- marginally, the call to protected members without virtual call
overhead(http://accu.org/index.php/journals/296).

Michael
 
Z

Zeppe

Dizzy said:
No you cannot lose the type, that's the point (otherwise compile time
resolving of the function to call wouldnt work). The base class when
instantiated has all the knowledge about the Derived. So you cannot lose
it, you have it there all along so you need template functions as the OP
said. [snip]
Maybe you mean something different than the OP and as such I am missing your
point?

Not at all. My bad, I didn't pay attention while answering. Obviously
you can't lose the type, as you said. I guess then that there are no
differences with a simple template at a functionality level, but I think
it makes sense to integrate the static polymorphism at a class level
(i.e., not via a template function call). For example, this principle
can enable for the implementation of functionalities through composition
(as Michael said, counting elements; another example can be the
singleton class functionality)...

Regards,

Zeppe
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,153
Members
46,699
Latest member
AnneRosen

Latest Threads

Top