Polimorfismo estático C ++ (CRTP) y uso de typedefs de clases derivadas

Leí el artículo de Wikipedia sobre el patrón de plantilla curiosamente recurrente en C ++ para hacer un polymorphism estático (leer: tiempo de comstackción). Quería generalizarlo para poder cambiar los tipos de devolución de las funciones en función del tipo derivado. (Parece que debería ser posible ya que el tipo de base conoce el tipo derivado del parámetro de la plantilla). Desafortunadamente, el siguiente código no se comstackrá utilizando MSVC 2010 (no tengo acceso fácil a gcc en este momento, así que aún no lo he probado). Alguien sabe por qué?

template  class base { public: typedef typename derived_t::value_type value_type; value_type foo() { return static_cast(this)->foo(); } }; template  class derived : public base<derived > { public: typedef T value_type; value_type foo() { return T(); //return some T object (assumes T is default constructable) } }; int main() { derived a; } 

Por cierto, tengo una solución alternativa que utiliza parámetros de plantilla adicionales, pero no me gusta, se volverá muy detallado al pasar muchos tipos en la cadena de herencia.

 template  class base { ... }; template  class derived : public base<derived,T> { ... }; 

EDITAR:

El mensaje de error que da MSVC 2010 en esta situación es el error C2039: 'value_type' : is not a member of 'derived'

g ++ 4.1.2 (a través de codepad.org ) dice error: no type named 'value_type' in 'class derived'

derived está incompleta cuando la usa como un argumento de plantilla para base en su lista de clases base.

Una solución común es usar una plantilla de clase de rasgos. Aquí está tu ejemplo, traitsificado. Esto muestra cómo puede usar los tipos y funciones de la clase derivada a través de los rasgos.

 // Declare a base_traits traits class template: template  struct base_traits; // Define the base class that uses the traits: template  struct base { typedef typename base_traits::value_type value_type; value_type base_foo() { return base_traits::call_foo(static_cast(this)); } }; // Define the derived class; it can use the traits too: template  struct derived : base > { typedef typename base_traits::value_type value_type; value_type derived_foo() { return value_type(); } }; // Declare and define a base_traits specialization for derived: template  struct base_traits > { typedef T value_type; static value_type call_foo(derived* x) { return x->derived_foo(); } }; 

Solo necesita especializar los base_traits para cualquier tipo que use para el argumento de la plantilla derived_t de la base y asegúrese de que cada especialización proporcione todos los miembros que la base requiera.

Un pequeño inconveniente de usar rasgos es que debe declarar uno para cada clase derivada. Puede escribir una solución menos detallada y continua como esta:

 template  

En C ++ 14, puede eliminar el typedef y usar la deducción de tipo auto return return:

 template  class base { public: auto foo() { return static_cast(this)->foo(); } }; 

Esto funciona porque la deducción del tipo de retorno de base::foo se retrasa hasta que se complete derived_t .

Una alternativa a los rasgos de tipo que requiere menos repetitivo es anidar su clase derivada dentro de una clase contenedora que contiene su typedefs (o use’s) y pasar el contenedor como un argumento de plantilla a su clase base.

 template  struct base { using derived = typename Outer::derived; using value_type = typename Outer::value_type; value_type base_func(int x) { return static_cast(this)->derived_func(x); } }; // outer holds our typedefs, derived does the rest template  struct outer { using value_type = T; struct derived : public base { // outer is now complete value_type derived_func(int x) { return 5 * x; } }; }; // If you want you can give it a better name template  using NicerName = typename outer::derived; int main() { NicerName obj; return obj.base_func(5); } 

Puede evitar pasar 2 argumentos en la template . En CRTP, si tiene la seguridad de que class base se emparejará con la class derived (y no con la class derived_2 ), utilice la técnica siguiente:

 template  class derived; // forward declare template  > class base { // make class derived as default argument value_type foo(); }; 

Uso:

 template  class derived : public base // directly use  for base