29

A friend of mine asked me "how to use CRTP to replace polymorphism in a multilevel inheritance". More precisely, in a situation like this:

struct A {

  void bar() {
    // do something and then call foo (possibly) in the derived class:
    foo();
  }

  // possibly non pure virtual
  virtual void foo() const = 0;
}

struct B : A {
  void foo() const override { /* do something */ }
}

struct C : B {
  // possibly absent to not override B::foo().
  void foo() const final { /* do something else */ }
}

My friend and I understand that CRTP is not a drop-in replacement for polymorphism but we are interested in cases where both patterns can be used. (For the sake of this question, we are not interested in pros and cons of each pattern.)

  1. This question has been asked before but it turned out the the author wanted to implement the named parameter idiom and his own answer focus on this problem more than on the CRTP. On the other hand, the most voted answer seems to be just about a derived class method calling its homonym in the base class.

  2. I came up with an answer (posted below) which has quite a lot of boilerplate code and I wonder if there are simpler alternatives.

Community
  • 1
  • 1
Cassio Neri
  • 17,293
  • 5
  • 43
  • 66

7 Answers7

16

(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?

Community
  • 1
  • 1
Cassio Neri
  • 17,293
  • 5
  • 43
  • 66
13

Note: This isn't specifically a solution to the "final override" problem, but to the CRTP multi-level inheritance problem in general (since I haven't found an answer anywhere on how to do it, and I think my findings would be useful).

EDIT: I've posted a solution to the final override problem here

I recently learned of CRTP and its potential as a static replacement for runtime polymorphism. After searching for a while to see whether CRTP could be used as a like-for-like "drop-in" replacement for polymorphism, such that you could use multi-level inheritance and the like, I have to say, I was pretty surprised that I couldn't find a proper generic solution anywhere without boilerplate which could scale indefinitely. After all, why not try to make CRTP a drop-in replacement for polymorphism, given all of its performance benefits? Some investigation ensued, and here's what I came up with:

The problem:

The classic CRTP pattern creates a "loop" of accessibility between the CRTP interface and the implementation class. (The CRTP interface class has access to the "base" implementation class via a static cast of itself to the template parameter type, and the implementation class inherits the public interface from the CRTP interface class.) When you create a concrete implementation, you are closing the loop, making it very difficult to inherit from the concrete implementation class, such that whatever derives from it also behaves in a polymorphic manner.

Classic CRTP single-level inheritance

The solution:

Separate the pattern into three concepts:

  • The "abstract interface class", i.e. the CRTP interface.
  • The "inheritable implementation class", which can be indefinitely inherited from by other inheritable implementation classes.
  • The "concrete class", which combines the abstract interface with the desired inheritable implementation class, and closes the loop.

Here's a diagram to help illustrate:

Multiple-level inheritance with CRTP

The trick is to pass the concrete implementation class as the template parameter all the way down through all of the inheritable implementation classes into the abstract interface class.

With this approach you can:

  1. inherit implementations indefinitely,
  2. call the highest implemented method in a CRTP multi-level inheritance chain from any level,
  3. design each implementation in an hierarchy agnostic manner,
  4. forget having to use boilerplate code (well, no more than with classic single-level CRTP anyway),

which reflects virtual/runtime polymorphism perfectly.

Example code:

#include <iostream>

template <class Top>
struct CrtpInterface
{
  void foo()
  {
    std::cout << "Calling CrtpInterface::foo()\n";
    fooImpl();
  }
  void foo2()
  {
    std::cout << "Calling CrtpInterface::foo2()\n";
    fooImpl2();
  }
  void foo3()
  {
    std::cout << "Calling CrtpInterface::foo3()\n";
    fooImpl3();
  }
  void foo4()
  {
    std::cout << "Calling CrtpInterface::foo4()\n";
    fooImpl4();
  }

// The "pure virtual functions"
protected:
  inline void fooImpl()
  {
    top().fooImpl();
  }
  inline void fooImpl2()
  {
    top().fooImpl2();
  }
  inline void fooImpl3()
  {
    top().fooImpl3();
  }
  inline void fooImpl4()
  {
    top().fooImpl4();
  }
  inline Top& top()
  {
    return static_cast<Top&>(*this);
  }
};

template<class Top>
class DefaultImpl : public CrtpInterface<Top>
{
  using impl = CrtpInterface<Top>;
  friend impl;

  void fooImpl()
  {
    std::cout << "Default::fooImpl()\n";
  }

  void fooImpl2()
  {
    std::cout << "Default::fooImpl2()\n";
    std::cout << "Calling foo() from interface\n";
    impl::foo();
  }

  void fooImpl3()
  {
    std::cout << "Default::fooImpl3()\n";
    std::cout << "Calling highest level fooImpl2() from interface\n";
    impl::fooImpl2();
  }

  void fooImpl4()
  {
    std::cout << "Default::fooImpl4()\n";
    std::cout << "Calling highest level fooImpl3() from interface\n";
    impl::fooImpl3();
  }
};

template<class Top>
class AImpl : public DefaultImpl<Top>
{
  using impl = CrtpInterface<Top>;
  friend impl;

  void fooImpl()
  {
    std::cout << "A::fooImpl()\n";
  }
};

struct A : AImpl<A>
{
};

template<class Top>
class BImpl : public AImpl<Top>
{
  using impl = CrtpInterface<Top>;
  friend impl;

  protected:
    BImpl()
      : i{1}
    {
    }

  private:
    int i;
    void fooImpl2()
    {
      std::cout << "B::fooImpl2(): " << i << "\n";
    }
};

struct B : BImpl<B>
{
};

template<class Top>
class CImpl : public BImpl<Top>
{
  using impl = CrtpInterface<Top>;
  friend impl;

  protected:
    CImpl(int x = 2)
      : i{x}
    {
    }

  private:
    int i;
    void fooImpl3()
    {
      std::cout << "C::fooImpl3(): " << i << "\n";
    }
};

struct C : CImpl<C>
{
  C(int i = 9)
    : CImpl(i)
  {
  }
};

template<class Top>
class DImpl : public CImpl<Top>
{
  using impl = CrtpInterface<Top>;
  friend impl;

  void fooImpl4()
  {
    std::cout << "D::fooImpl4()\n";
  }
};

struct D : DImpl<D>
{
};

int main()
{
  std::cout << "### A ###\n";
  A a;
  a.foo();
  a.foo2();
  a.foo3();
  a.foo4();

  std::cout << "### B ###\n";
  B b;
  b.foo();
  b.foo2();
  b.foo3();
  b.foo4();

  std::cout << "### C ###\n";
  C c;
  c.foo();
  c.foo2();
  c.foo3();
  c.foo4();

  std::cout << "### D ###\n";
  D d;
  d.foo();
  d.foo2();
  d.foo3();
  d.foo4();
}

Which prints:

### A ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
### B ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
### C ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 9
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
C::fooImpl3(): 9
### D ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 2
Calling CrtpInterface::foo4()
D::fooImpl4()

Using this approach, and a "variant-style" wrapper (built using some sechsy variadic templates and macros, maybe I'll post that later), which acted like a pointer to a virtual abstract base class, I was able to effectively create a vector of CRTP classes inheriting from the same interface.

I measured the performance in comparison to a vector of like-for-like virtual classes, all based on an equivalent virtual interface, and I found that with this approach, depending on the scenario, I could achieve a performance increase of up to 8x! This is very encouraging, given the relatively little overhead needed to generate a functionally polymorphic CRTP class hierarchy!

Community
  • 1
  • 1
Mark Mitchinson
  • 171
  • 1
  • 4
4

After realising that my original answer didn't actually deal with the final override question at hand, I thought I'd add to it. I wanted to come up with a "final override" solution in a similar manner as my previous answer.

The problem:

The CRTP interface class always redirects via a static cast to the highest derived class. This is at odds with the concept of a "final" function: if the desired "final" function is not implemented on the highest derived class, and is "overridden" by a higher class (since you cannot give a function a "final" property unless it is virtual, which we are trying to avoid in CRTP), the CRTP interface will redirect not to the desired "final" function but to the "override".

The solution:

Split the interface into three concepts:

  • The abstract interface class without any of the redirecting functions, which inherits:
  • an abstract redirecting class whose redirecting functions redirect to the highest derived class, unless one or more of the redirecting functions is overridden by:
  • a concrete "redirect override" class, which overrides the redirecting functions with an implementation.

When instantiating the concrete implementation class, instead of passing the concrete implementation class as a template parameter through all of the "inheritable implementation classes" into the interface, we pass the redirecting class which the interface will inherit from as the template parameter.

When we want to make a function "final", we simply create a "redirect override class" which inherits from the abstract redirecting class, and overrides the redirecting function which we want to make final. Then we pass this new "redirect override class" as a parameter through all of the inheritable implementation classes.

With this approach:

  1. "final" functions are called directly rather than redirected via a cast (unless you need the "final" function to be implemented in the inheritable implementation class rather than the redirect override class),
  2. "final" functions cannot be overridden by any future user code,
  3. each "final" function only requires an extra ImplFinal class per level of inheritance, with no extra boilerplate.

This all sounds very complicated, so here is a flow diagram I made in attempt to make things easier to understand:

DImpl and EImpl have final functions which cannot be overridden when either DImpl or EImpl are inherited from:

Example code:

#include <iostream>
#include <type_traits> 

template <class Top>
struct Redirect
{
protected:
  // The "pure virtual functions"
  inline void fooImpl()
  {
    top().fooImpl();
  }
  inline void fooImpl2()
  {
    top().fooImpl2();
  }
  inline void fooImpl3()
  {
    top().fooImpl3();
  }
  inline void fooImpl4()
  {
    top().fooImpl4();
  }
  inline Top& top()
  {
    // GCC doesn't allow static_cast<Top&>(*this) 
    // since Interface uses private inheritance
    static_assert(std::is_base_of<Redirect, Top>::value, "Invalid Top class specified.");
    return (Top&)(*this);
  }
};

// Wraps R around the inner level of a template T, e.g:
// R := Redirect, T := X, then inject_type::type := Redirect<X>
// R := Redirect, T := A<B<C<X>>>, then inject_type::type := A<B<C<Redirect<X>>>>
template<template<class> class R, class T>
struct inject_type
{
  using type = R<T>;
};

template<template<class> class R, class InnerFirst, class... InnerRest, template<class...> class Outer>
struct inject_type<R, Outer<InnerFirst, InnerRest...>>
{
  using type = Outer<typename inject_type<R, InnerFirst>::type, InnerRest...>;
};

// We will be inheriting either Redirect<...> or something 
// which derives from it (and overrides the functions).
// Use private inheritance, so that all polymorphic calls can
// only go through this class (which makes it impossible to 
// subvert redirect overrides using future user code).
template <class V>
struct Interface : private inject_type<Redirect, V>::type
{
  using impl = Interface;

  void foo()
  {
    std::cout << "Calling Interface::foo()\n";
    fooImpl();
  }

  void foo2()
  {
    std::cout << "Calling Interface::foo2()\n";
    fooImpl2();
  }

  void foo3()
  {
    std::cout << "Calling Interface::foo3()\n";
    fooImpl3();
  }

  void foo4()
  {
    std::cout << "Calling Interface::foo4()\n";
    fooImpl4();
  }

private:
  using R = typename inject_type<::Redirect, V>::type;

protected:
  using R::fooImpl;
  using R::fooImpl2;
  using R::fooImpl3;
  using R::fooImpl4;
};

template<class V>
struct DefaultImpl : Interface<V>
{
  template<class>
  friend struct Redirect;

protected:
  // Picking up typename impl from Interface, where all polymorphic calls must pass through
  using impl = typename DefaultImpl::impl;

  void fooImpl()
  {
    std::cout << "Default::fooImpl()\n";
  }

  void fooImpl2()
  {
    std::cout << "Default::fooImpl2()\n";
    std::cout << "Calling foo() from interface\n";
    impl::foo();
  }

  void fooImpl3()
  {
    std::cout << "Default::fooImpl3()\n";
    std::cout << "Calling highest level fooImpl2() from interface\n";
    impl::fooImpl2();
  }

  void fooImpl4()
  {
    std::cout << "Default::fooImpl4()\n";
    std::cout << "Calling highest level fooImpl3() from interface\n";
    impl::fooImpl3();
  }
};

template<class V>
struct AImpl : public DefaultImpl<V>
{
  template<class>
  friend struct Redirect;

protected:
  void fooImpl()
  {
    std::cout << "A::fooImpl()\n";
  }
};

struct A : AImpl<A>
{
};

template<class V>
struct BImpl : public AImpl<V>
{
  template<class>
  friend struct Redirect;

protected:
  BImpl()
    : i{1}
  {
  }

private:
  int i;
  void fooImpl2()
  {
    std::cout << "B::fooImpl2(): " << i << "\n";
  }
};

struct B : BImpl<B>
{
};

template<class V>
struct CImpl : public BImpl<V>
{
  template<class>
  friend struct Redirect;

  protected:
    CImpl(int x = 2)
      : i{x}
    {
    }

  private:
    int i;
    void fooImpl3()
    {
      std::cout << "C::fooImpl3(): " << i << "\n";
    }
};

struct C : CImpl<C>
{
  C(int i = 9)
    : CImpl(i)
  {
  }
};

// Make D::fooImpl4 final
template<class V>
struct DImplFinal : public V
{
protected:
  void fooImpl4()
  {
    std::cout << "DImplFinal::fooImpl4()\n";
  }
};


// Wrapping V with DImplFinal overrides the redirecting functions
template<class V>
struct DImpl : CImpl<DImplFinal<V>>
{
};

struct D : DImpl<D>
{
};

template<class V>
struct EImpl : DImpl<V>
{
  template<class>
  friend struct Redirect;

protected:
  void fooImpl()
  {
    std::cout << "E::fooImpl()\n";
  }

  void fooImpl3()
  {
    std::cout << "E::fooImpl3()\n";
  }

  // This will never be called, because fooImpl4 is final in DImpl
  void fooImpl4()
  {
    std::cout << "E::fooImpl4(): this should never be printed\n";
  }
};

struct E : EImpl<E>
{
};

// Make F::fooImpl3 final
template<class V, class Top>
struct FImplFinal : public V
{
protected:
  // This is implemented in FImpl, so redirect
  void fooImpl3()
  {
    top().fooImpl3();
  }

  // This will never be called, because fooImpl4 is final in DImpl
  void fooImpl4()
  {
    std::cout << "FImplFinal::fooImpl4() this should never be printed\n";
  }

  inline Top& top()
  {
    // GCC won't do a static_cast directly :( 
    static_assert(std::is_base_of<FImplFinal, Top>::value, "Invalid Top class specified");
    return (Top&)(*this);  
  }
};

// Wrapping V with FImplFinal overrides the redirecting functions, but only if they haven't been overridden already
template<class V>
struct FImpl : EImpl<FImplFinal<V, FImpl<V>>>
{
  template<class>
  friend struct Redirect;
  template<class, class>
  friend struct FImplFinal;

protected:
  FImpl() 
    : i{99} 
  {
  }

  // Picking up typename impl from DefaultImpl
  using impl = typename FImpl::impl;

private:
  int i;

  void fooImpl2()
  {
    std::cout << "F::fooImpl2()\n";
    // This will only call DFinal::fooImpl4();
    std::cout << "Calling fooImpl4() polymorphically. (Should not print FImplFinal::fooImpl4() or EImpl::fooImpl4())\n";
    impl::fooImpl4();
  }

  void fooImpl3()
  {
    std::cout << "FImpl::fooImpl3(), i = " << i << '\n';
  }
};

struct F : FImpl<F>
{
};

int main()
{
  std::cout << "### A ###\n";
  A a;
  a.foo();
  a.foo2();
  a.foo3();
  a.foo4();

  std::cout << "### B ###\n";
  B b;
  b.foo();
  b.foo2();
  b.foo3();
  b.foo4();

  std::cout << "### C ###\n";
  C c;
  c.foo();
  c.foo2();
  c.foo3();
  c.foo4();

  std::cout << "### D ###\n";
  D d;
  d.foo();
  d.foo2();
  d.foo3();
  d.foo4();

  std::cout << "### E ###\n";
  E e;
  e.foo();
  e.foo2();
  e.foo3();
  e.foo4();

  std::cout << "### F ###\n";
  F f;
  f.foo();
  f.foo2();
  f.foo3();
  f.foo4();
}

The code prints:

### A ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
### B ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
### C ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 9
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
C::fooImpl3(): 9
### D ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 2
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
### E ###
Calling CrtpInterface::foo()
E::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
E::fooImpl3()
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
### F ###
Calling CrtpInterface::foo()
E::fooImpl()
Calling CrtpInterface::foo2()
F::fooImpl2()
Attempting to call FFinal::fooImpl4() or E::fooImpl4()
DImplFinal::fooImpl4()
Calling CrtpInterface::foo3()
FImpl::fooImpl3(), i = 99
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
Community
  • 1
  • 1
Mark Mitchinson
  • 171
  • 1
  • 4
4
template<class Derived>
struct A {
  void foo() {
    static_cast<Derived*>(this)->foo();
  }
};

template<class Derived>
struct B: A <Derived> {
  void foo() {
    // do something
  }
};

struct C: B <C> {
  void foo(); // can be either present or absent
};

if foo() in C is absent the one in B will be invoked. Otherwise the one in B will be overridden.

Cassio Neri
  • 17,293
  • 5
  • 43
  • 66
jzl106
  • 307
  • 2
  • 9
  • 1
    Beautiful idea: making `B` derived from `A` instead of `A>` (as it's usual in CRTP) seems to save some boilerplate code because the call from `A` goes directly to the most derived class and the compiler goes up the hierarchy to find the deepest `foo`. (Contrast this with [my solution](https://stackoverflow.com/a/18174442/1137388) where we go down one level at time.) Having said that, there's an issue: `C c; B& b = c; b.foo();` calls `B::foo` even though the most derived type of `b` is `C` which is a diversion from what polymorphism would do. +1 anyway! – Cassio Neri Feb 07 '19 at 13:36
1

Multilevel inheritance is not the problem, but how CRTP creates polymorphism is.

template<typename Derived>
struct Base
{
    void f() { /* Basic case */ }

    // "Pure virtual" method
    void pure() { static_cast<Derived*>(this)->pure(); }
};

struct Overriding: Base<Overriding>
{
    void f() { /* Special case */ }

    // This method must exists to prevent endless recursion in Base::f
    void pure() { /* ... */ }
};

struct NonOverriding: Base<NonOverriding>
{
    void pure() { /* ... */ }
};

template<typename Derived>
void f(const Base<Derived>& base)
{
    base.f();    // Base::f
    base.pure(); // Base::pure, which eventually calls Derived::pure

    // Derived::f if an overriding method exists.
    // Base::f otherwise.
    static_cast<const Derived&>(base).f();
}

One can also introduce a derived method to avoid wordy type casting at each invocation.

template<typename Derived>
struct Base
{
    Derived& derived() { return *static_cast<Derived*>(this); }
    const Derived& derived() const { return *static_cast<const Derived*>(this); }
};
jdh8
  • 2,492
  • 1
  • 16
  • 18
0

Here is a possible implementation that can reduce the boilerplate code inside the class (but not the total amount of auxiliary code).

The idea of this solution is to use SFINAE and overloading to select the impl function.

(i) Class A

template <typename T> class A {
   void foo() const {
    static_cast<const T*>(this)->foo( Type2Type<T> );
   }
 }

where TypetoType is the template struct

template< typename T > struct Type2Type {
  typedef T OriginalType
}

which is useful for helping compiler to choose the foo() impl. by overloading.

(i) Class B

template <typename T = void>
class B : public A<B<T>> { 

 void foo(...) { 
   std::cout << "B::foo()\n";  
 }
 void foo( Type2Type< std::enable_if< is_base_of<T,B>::value == true, B>::type> ) { 
   static_cast<const T*>(this)->foo( Type2Type<T> );
 }
}

Here, the TypetoType argument is not necessary if the bottom of the hierarchy is given by C.

(ii) Class C

class C : public B<C> { 
 void foo(...) { 
   std::cout << "C::foo()\n";  
 }
}

I know that std::is_base_of returns true if T==B. Here, we use our own version of is_base_of that returns false_type when the two template arguments are the same. Something like

template<class B, class D>
struct is_base_of : public is_convertible<D *, B *> {};
template<class B, class B>
struct is_base_of<B, B> :  public false_type {};
template<class D>
struct is_base_of<void, D> : public false_type {};

Conclusion: if std::enable_if fails, then the variadic version of foo() will be the only one available (because of SFINAE) and compiler will implement B version of foo. However, if enable_if does not fail, compiler will choose the second version (because variadic is the last option when compiler try to figure out the best match between overloading functions).

Vivian Miranda
  • 2,276
  • 1
  • 13
  • 26
0

There is a lot of stuff going on in this thread which I do not find useful, so I share here my own solution to this problem.

CRTP is mainly a pattern for code reduction. In order to work properly, it is necessary that at each level of the inheritance hierachy, one is able to call all functions from the levels below -- just as in usual dynamic inheritance.

However, in CRTP, each stage must further be aware of the final type, which currently derives from it, because in the end this is the whole point of CRTP -- to call functions that always apply to the current (static) final type.

One can obtain this by adding a layer of indirection at each level of the static inheritance hierarchy, as in the following example:

template<typename derived_t>
struct level0_impl
{
    auto const& derived() const
    {
        return static_cast<derived_t const&>(*this);
    }
};
struct level0 : public level0_impl<level0>
{
    using level0_impl<level0>::level0_impl;
};


template<typename derived_t>
struct level1_impl : level0_impl<derived_t>
{
    auto only_for_level1_and_derived() const
    {
        return derived().foo;
    };
    auto do_something() const { std::cout<<"hi"<<std::endl; }
};
struct level1 : public level1_impl<level1>
{
    using level1_impl<level1>::level1_impl;
};


template<typename derived_t>
struct level2_impl : public level1_impl<derived_t>
{
    auto only_for_level2_and_derived() const
    {
        return derived().bar;
    };
};
struct level2 : public level2_impl<level2>
{
    using level2_impl<level2>::level2_impl;
};

// ... and so on ...

One can use it with a final type as in the following:

#include <iostream>
struct final : public level2_impl<final>
{
    int foo = 1;
    double bar = 2.0;
};

int main()
{
    final f{};
    std::cout<< f.only_for_level1_and_derived() <<std::endl;   //prints variable foo = 1
    std::cout<< f.only_for_level2_and_derived() <<std::endl;   //prints variable bar = 2.0
}

Alternatively, one can use each level for its own by just dropping the _impl suffix:

level1{}.do_something();   //prints "hi"

This is a nice thing, which notably does not work with the other approaches in this thread such as

template<typename T> class A { auto& derived() {return static_cast<T&>(*this);} };
template<typename T> class B : A<B<T> > {};
template<typename T> class C : B<C> {};    //here derived() in the base class does 
                                           //not return C, but B<C> -- which is
                                           //not what one usually wants in CRTP
davidhigh
  • 12,239
  • 1
  • 34
  • 64