Polimorfismo statico C ++ (CRTP) e utilizzo di typedef da classi derivate

Ho letto l’ articolo di Wikipedia sul modello di template curiosamente ricorrente in C ++ per fare il polimorfismo statico (leggi: compile-time). Volevo generalizzarlo in modo da poter modificare i tipi di ritorno delle funzioni in base al tipo derivato. (Sembra che dovrebbe essere ansible poiché il tipo base conosce il tipo derivato dal parametro template). Sfortunatamente, il codice seguente non verrà compilato utilizzando MSVC 2010 (non ho un facile accesso a gcc in questo momento, quindi non l’ho ancora provato). Qualcuno sa perché?

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

A proposito, ho una soluzione che utilizza parametri di template aggiuntivi, ma non mi piace – diventerà molto prolisso quando si passano molti tipi nella catena di ereditarietà.

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

MODIFICARE:

Il messaggio di errore che MSVC 2010 assegna in questa situazione è l’ error C2039: 'value_type' : is not a member of 'derived'

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

derived è incompleto quando lo si utilizza come argomento modello per base nel suo elenco di classi di base.

Una soluzione alternativa è usare un modello di class dei tratti. Ecco il tuo esempio, traitsificato. Questo mostra come è ansible utilizzare entrambi i tipi e le funzioni dalla class derivata attraverso i tratti.

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

Devi solo specializzare base_traits per tutti i tipi che usi per l’argomento template derived_t di base e assicurarti che ogni specializzazione fornisca tutti i membri che la base richiede.

Un piccolo inconveniente nell’usare i tratti è che devi dichiararne uno per ogni class derivata. Puoi scrivere una soluzione meno prolissa e ridondante come questa:

 template  

In C ++ 14 è ansible rimuovere il typedef e utilizzare la funzione auto deduzione del tipo di ritorno auto :

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

Questo funziona perché la deduzione del tipo restituito di base::foo è ritardata fino a quando il derived_t è completo.

Un’alternativa ai caratteri di tipo che richiede meno caratteri è quella di annidare la class derivata all’interno di una class wrapper che contiene i typedefs (o using’s) e passa il wrapper come argomento modello alla 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); } 

Puoi evitare di passare 2 argomenti nel template . In CRTP se si ha la certezza che la class base sarà accoppiata con class derived (e non con class derived_2 ), quindi utilizzare la tecnica seguente:

 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