(1) The topmost class in the hierarchy looks like:
template <typename T>
class A {
public:
void bar() const {
// do something and then call foo (possibly) in the derived class:
foo();
}
void foo() const {
static_cast<const T*>(this)->foo();
}
protected:
~A() = default;
// Constructors should be protected as well.
};
A<T>::foo() behaves similarly to a pure virtual method in the sense that it doesn't have a "default implementation" and calls are directed to derived classes. However, this doesn't prevent A<T> from being instantiated as a non base class. To get this behavior A<T>::~A() is made protected.
Remark: Unfortunately a GCC bug turns special member functions public when = default; is used. In this case, one should used
protected:
~A() {}
Still, protecting the destructor is not enough for the cases where a call to a constructor is not matched by a call to the destructor (this might happen via operator new). Hence, it's advisable to protect all constructors (including copy- and move-constructor) as well.
When instantiations of A<T> should be allowed and A<T>::foo() should behave like a non-pure virtual method, then A should be similar to the template class B below.
(2) Classes in the middle of the hierarchy (or the topmost one, as explained in the paragraph above) look like:
template <typename T = void>
class B : public A<B<T>> { // no inherinace if this is the topmost class
public:
// Constructors and destructor
// boilerplate code :-(
void foo() const {
foo_impl(std::is_same<T, void>{});
}
private:
void foo_impl(std::true_type) const {
std::cout << "B::foo()\n";
}
// boilerplate code :-(
void foo_impl(std::false_type) const {
if (&B::foo == &T::foo)
foo_impl(std::true_type{});
else
static_cast<const T*>(this)->foo();
}
};
Constructors and destructors are public and T defaults to void. This allows objects of type B<> to be the most derived in the hierarchy and makes this legal:
B<> b;
b.foo();
Notice also that B<T>::foo() behaves as a non pure virtual method in the sense that, if B<T> is the most derived class (or, more precisely, if T is void), then b.foo(); calls the "default implementation of foo()" (which outputs B::foo()). If T is not void, then the call is directed to the derived class. This is accomplished through tag dispatching.
The test &B::foo == &T::foo is required to avoid an infinite recursive call. Indeed, if the derived class, T, doesn't reimplement foo(), the call static_cast<const T*>(this)->foo(); will resolve to B::foo() which calls B::foo_impl(std::false_type) again. Furthermore, this test can be resolved at compile time and the code is either if (true) or if (false) and the optimizer can remove the test altogether (e.g. GCC with -O3).
(3) Finally, the bottom of the hierarchy looks like:
class C : public B<C> {
public:
void foo() const {
std::cout << "C::foo()\n";
}
};
Alternatively, one can remove C::foo() entirely if the inherited implementation (B<C>::foo()) is adequate.
Notice that C::foo() is similar to a final method in the sense that calling it does not redirected the call to a derived class (if any). (To make it non final, a template class like B should be used.)
(4) See also:
How to avoid errors while using CRTP?