c++ inheritance c++11 crtp static-polymorphism

c++ - CRTP y herencia multinivel



inheritance c++11 (6)

Un amigo mío me preguntó "cómo usar CRTP para reemplazar el polimorfismo en una herencia multinivel" . Más precisamente, en una situación como esta:

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 */ } }

Mi amigo y yo entendemos que el CRTP no es un sustituto directo del polimorfismo, pero nos interesan los casos en los que se pueden utilizar ambos patrones. (Por el bien de esta pregunta, no estamos interesados ​​en los pros y los contras de cada patrón).

  1. Esta pregunta ya se ha formulado antes, pero resultó que el autor quería implementar el idioma del parámetro nombrado y su propia respuesta a este problema más que en el CRTP. Por otro lado, la respuesta más votada parece ser simplemente un método de clase derivado llamando a su homónimo en la clase base.

  2. Se me ocurrió una respuesta (publicada a continuación) que tiene un montón de código repetitivo y me pregunto si hay alternativas más simples.


Aquí hay una posible implementación que puede reducir el código repetitivo dentro de la clase (pero no la cantidad total de código auxiliar).

La idea de esta solución es usar SFINAE y sobrecargar para seleccionar la función impl.

(i) Clase A

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

donde TypetoType es la estructura de la plantilla

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

que es útil para ayudar al compilador a elegir el foo () impl. por sobrecargar

(i) Clase 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> ); } }

Aquí, el argumento TypetoType no es necesario si la parte inferior de la jerarquía está dada por C.

(ii) Clase C

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

Sé que std :: is_base_of devuelve true si T == B. Aquí, usamos nuestra propia versión de is_base_of que devuelve false_type cuando los dos argumentos de la plantilla son iguales. Algo como

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 {};

Conclusión: si std :: enable_if falla, entonces la versión variad de foo () será la única disponible (debido a SFINAE) y el compilador implementará la versión B de foo. Sin embargo, si enable_if no falla, el compilador elegirá la segunda versión (porque variadic es la última opción cuando el compilador trata de encontrar la mejor coincidencia entre las funciones de sobrecarga).


La herencia multinivel no es el problema, sino cómo CRTP crea el polimorfismo.

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(); }

También se puede introducir un método derived para evitar la conversión de tipo Wordy en cada invocación.

template<typename Derived> struct Base { Derived& derived() { return *static_cast<Derived*>(this); } const Derived& derived() const { return *static_cast<const Derived*>(this); } };


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 }

si foo () en C está ausente, se invocará el de B. De lo contrario, el que está en B será anulado.


Después de darme cuenta de que mi respuesta original en realidad no se refería a la última pregunta de anulación, pensé en agregar algo más. Quería llegar a una solución de "anulación final" de una manera similar a mi respuesta anterior.

El problema:

La clase de interfaz CRTP siempre redirige a través de un molde estático a la clase derivada más alta. Esto está en desacuerdo con el concepto de una función "final": si la función "final" deseada no se implementa en la clase derivada más alta, y es "anulada" por una clase más alta (ya que no se puede dar a una función un "final" propiedad a menos que sea virtual, lo cual estamos tratando de evitar en CRTP), la interfaz CRTP redirigirá no a la función "final" deseada sino a la "anulación".

La solución:

Divida la interfaz en tres conceptos:

  • La clase de interfaz abstracta sin ninguna de las funciones de redirección, que hereda:
  • una clase de redirección abstracta cuyas funciones de redirección redireccionan a la clase derivada más alta, a menos que una o más de las funciones de redirección sean anuladas por:
  • una clase concreta de "redirección", que anula las funciones de redirección con una implementación.

Al instanciar la clase de implementación concreta, en lugar de pasar la clase de implementación concreta como un parámetro de plantilla a través de todas las "clases de implementación heredables" en la interfaz, pasamos la clase de redireccionamiento de la que la interfaz heredará como el parámetro de la plantilla.

Cuando queremos hacer una función "final", simplemente creamos una "clase de anulación de redireccionamiento" que hereda de la clase de redirección abstracta, y anula la función de redireccionamiento que queremos hacer definitiva. Luego pasamos esta nueva "clase de anulación de redireccionamiento" como un parámetro a través de todas las clases de implementación heredables.

Con este enfoque:

  1. las funciones "finales" se llaman directamente en lugar de redirigirse a través de un molde (a menos que necesite que se implemente la función "final" en la clase de implementación heredable en lugar de la clase de anulación de redireccionamiento),
  2. las funciones "finales" no pueden ser anuladas por ningún código de usuario futuro,
  3. cada función "final" solo requiere una clase adicional ImplFinal por nivel de herencia, sin ningún texto adicional.

Todo esto suena muy complicado, así que aquí hay un diagrama de flujo que hice para intentar que todo sea más fácil de entender:

DImpl y EImpl tienen funciones finales que no pueden anularse cuando DImpl o EImpl se heredan de:

Código de ejemplo:

#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(); }

El código se imprime:

### 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()


Nota: Esto no es específicamente una solución al problema de "anulación final", sino al problema de herencia de niveles múltiples de CRTP en general (ya que no he encontrado una respuesta en ningún lugar sobre cómo hacerlo, y creo que mis hallazgos sé útil).

EDITAR : He publicado una solución para el problema de anulación final aquí

Recientemente me enteré de CRTP y su potencial como un reemplazo estático para el polimorfismo en tiempo de ejecución. Después de buscar por un tiempo para ver si el CRTP podría ser usado como un reemplazo de polimorfismo "similar al igual", de manera que se pudiera usar herencia de varios niveles y cosas por el estilo, tengo que decir que estaba bastante sorprendido. que no podía encontrar una solución genérica adecuada en ningún lado sin una repetición que pudiera escalar indefinidamente. Después de todo, ¿por qué no intentar hacer de CRTP un reemplazo directo para el polimorfismo, dados todos sus beneficios de rendimiento? Se produjo una investigación, y esto es lo que se me ocurrió:

El problema:

El patrón clásico de CRTP crea un "bucle" de accesibilidad entre la interfaz CRTP y la clase de implementación. (La clase de interfaz CRTP tiene acceso a la clase de implementación "base" a través de un molde estático de sí mismo para el tipo de parámetro de plantilla, y la clase de implementación hereda la interfaz pública de la clase de interfaz CRTP.) Cuando crea una implementación concreta, usted cerrando el ciclo, lo que hace que sea muy difícil heredar de la clase de implementación concreta, de modo que todo lo que se deriva de él también se comporta de forma polimórfica.

Herencia clásica de CRTP de un solo nivel

La solución:

Separe el patrón en tres conceptos:

  • La "clase de interfaz abstracta", es decir, la interfaz CRTP.
  • La "clase de implementación heredable", que puede heredarse de forma indefinida por otras clases de implementación heredables.
  • La "clase concreta", que combina la interfaz abstracta con la clase de implementación heredable deseada, y cierra el ciclo.

Aquí hay un diagrama para ayudar a ilustrar:

Herencia de múltiples niveles con CRTP

El truco es pasar la clase de implementación concreta como el parámetro de la plantilla a través de todas las clases de implementación heredables en la clase de interfaz abstracta.

Con este enfoque, puedes:

  1. heredar implementaciones indefinidamente,
  2. llamar al método implementado más alto en una cadena de herencia multinivel CRTP desde cualquier nivel,
  3. diseñar cada implementación de una manera jerárquica, agnóstica,
  4. Olvidé tener que usar código repetitivo (bueno, no más que con el clásico CRTP de un solo nivel)

que refleja perfectamente el polimorfismo virtual / de tiempo de ejecución.

Código de ejemplo:

#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(); }

Que impresiones:

### 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()

Utilizando este enfoque, y un envoltorio de "estilo variante" (construido usando plantillas y macros variados sediciosos, tal vez lo publique más adelante), que actuó como un puntero a una clase base abstracta virtual, pude crear efectivamente un vector de clases CRTP heredadas de la misma interfaz.

Medí el rendimiento en comparación con un vector de clases virtuales parecidas, todas basadas en una interfaz virtual equivalente, y encontré que con este enfoque, dependiendo del escenario, ¡podría lograr un aumento de rendimiento de hasta 8x! Esto es muy alentador, dada la relativamente poca sobrecarga necesaria para generar una jerarquía de clases de CRTP funcionalmente polimórficas.


(1) La clase más alta en la jerarquía se ve así:

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() comporta de manera similar a un método virtual puro en el sentido de que no tiene una " implementación predeterminada " y las llamadas se dirigen a las clases derivadas. Sin embargo, esto no impide que A<T> se instanciará como una clase no base. Para obtener este comportamiento, A<T>::~A() se hace protected .

Observación: Desafortunadamente, un error de GCC convierte las funciones de miembros especiales en públicas cuando = default; es usado. En este caso, uno debería usar

protected: ~A() {}

Aún así, proteger el destructor no es suficiente para los casos en que una llamada al constructor no se corresponde con una llamada al destructor (esto puede suceder a través del operator new ). Por lo tanto, es aconsejable proteger todos los constructores (incluido el constructor de copiar y mover) también.

Cuando se deben permitir instancias de A<T> y A<T>::foo() debe comportarse como un método virtual no puro, A debe ser similar a la clase de plantilla B continuación.

(2) Las clases en el medio de la jerarquía (o la más alta, como se explica en el párrafo anterior) se ven así:

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(); } };

Los constructores y los destructores son públicos y T convierte en void . Esto permite que los objetos de tipo B<> sean los más derivados en la jerarquía y lo hace legal:

B<> b; b.foo();

Observe también que B<T>::foo() comporta como un método virtual no puro en el sentido de que, si B<T> es la clase más derivada (o, más precisamente, si T es void ), entonces b.foo(); llama a la " implementación predeterminada de foo() " (que emite B::foo() ). Si T no es void , la llamada se dirige a la clase derivada. Esto se logra mediante el despacho de etiquetas.

La prueba &B::foo == &T::foo es necesaria para evitar una llamada infinita recursiva. De hecho, si la clase derivada, T , no vuelve a implementar foo() , la llamada static_cast<const T*>(this)->foo(); se resolverá con B::foo() que llama a B::foo_impl(std::false_type) nuevamente. Además, esta prueba se puede resolver en tiempo de compilación y el código es if (true) o if (false) y el optimizador puede eliminar la prueba por completo (por ejemplo, GCC con -O3).

(3) Finalmente, la parte inferior de la jerarquía se ve así:

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

Alternativamente, uno puede eliminar C::foo() completo si la implementación heredada ( B<C>::foo() ) es adecuada.

Tenga en cuenta que C::foo() es similar a un método final en el sentido de que llamarlo no redirige la llamada a una clase derivada (si corresponde). (Para que no sea final, se debe usar una clase de plantilla como B ).

(4) Ver también:

¿Cómo evitar errores al usar CRTP?