The CRTP pattern is typically used to avoid dynamic dispatch when it can be statically determined what implementation method to call for a particular method.
In your example, both A
and B
would inherit from a single base class, which provides the print()
method. The base class, let's call it Print
, is a template whose template argument is a class that provides f()
. The twist that earned this pattern the "curious" moniker is that the subclasses must inherit the base class templatized over the subclass. This allows the subclasses to access the base class's print
method, but obtaining a version of the base class - and by extension the version of print
- that invokes their own f
.
Here is an example of working code:
#include <iostream>
template<typename F>
class Print {
public:
void print() {
F& final = static_cast<F&>(*this);
std::cout << "Result of f() is: " << final.f() << std::endl;
}
};
class A: public Print<A> {
public:
std::string f(){
return "A";
}
};
class B: public Print<B> {
public:
std::string f(){
return "B";
}
};
int main() {
A a;
B b;
a.print();
b.print();
}
Although the print
implementation is reused among A
and B
, there are no virtual methods here, nor is there virtual (run-time) dispatch or run-time checks. The one cast present is a static_cast<>
whose safety is duly verified by the compiler.
This is possible because for every use of Print<F>
, the compiler knows exactly what F
is. Thus Print<A>::print
is known to invoke A::f
, while Print<B>::print
invokes B::f
, all known at compile time. This enables the compiler to inline and otherwise optimize such calls just like any other non-virtual method calls.
The downside is that there is no inheritance, either. Note that B
is not derived from A
- if it were, the pattern wouldn't work and both A::print
and B::print
would print A
, since that's what Print<A>
outputs. More fundamentally, you cannot pass B*
where an A*
is expected - that's undefined behavior. In fact, A
and B
don't share any common superclass, the Parent<A>
and Parent<B>
classes are completely separate. Loss of run-time dispatch, with both its drawbacks and benefits, and enabling static dispatch instead, is a fundamental tradeoff of static polymorphism.