Polimorfismo estático de C ++ (CRTP) e usando typedefs de classs derivadas

Eu li o artigo da Wikipedia sobre o padrão de modelo curiosamente recorrente em C ++ para fazer polymorphism estático (read: compile-time). Eu queria generalizá-lo para que eu pudesse alterar os tipos de retorno das funções com base no tipo derivado. (Isso parece ser possível, já que o tipo base conhece o tipo derivado do parâmetro template). Infelizmente, o código a seguir não será compilado usando o MSVC 2010 (não tenho access fácil ao gcc agora, então ainda não experimentei). Alguém 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; } 

BTW, eu tenho uma solução alternativa usando parâmetros de modelo extras, mas eu não gosto disso — ele vai ficar muito detalhado ao passar muitos tipos até a cadeia de inheritance.

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

EDITAR:

A mensagem de erro que o MSVC 2010 fornece nessa situação é o error C2039: 'value_type' : is not a member of 'derived'

g ++ 4.1.2 (via codepad.org ) diz error: no type named 'value_type' in 'class derived'

derived é incompleto quando você o usa como um argumento de modelo para base em sua lista de classs base.

Uma solução comum é usar um modelo de class de traços. Aqui está o seu exemplo, traitsified. Isso mostra como você pode usar os dois tipos e funções da class derivada através dos traços.

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

Você só precisa especializar base_traits para quaisquer tipos que você usa para o argumento de modelo derived_t de base e certificar-se de que cada especialização forneça todos os membros que a base requer.

Uma pequena desvantagem do uso de características é que você precisa declarar uma para cada class derivada. Você pode escrever uma solução alternativa menos verbosa e redundante assim:

 template  

Em C ++ 14, você pode remover o typedef e usar a dedução do tipo de retorno auto function:

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

Isso funciona porque a dedução do tipo de retorno de base::foo é atrasada até que derived_t seja concluído.

Uma alternativa para digitar características que exigem menos clichê é aninhar sua class derivada dentro de uma class de wrapper que contém seus typedefs (ou usando) e passar o wrapper como um argumento de modelo para sua class 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); } 

Você pode evitar passar 2 argumentos no template . No CRTP, se você tiver certeza de que a class base será emparelhada com a class derived (e não com a class derived_2 ), use a técnica abaixo:

 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