Polymorphisme statique C ++ (CRTP) et utilisation de typedefs à partir de classes dérivées

J’ai lu l’article de Wikipedia sur le modèle de modèle curieusement récurrent en C ++ pour faire du polymorphism statique (lecture: compilation). Je voulais le généraliser pour pouvoir changer les types de retour des fonctions en fonction du type dérivé. (Cela semble être possible car le type de base connaît le type dérivé du paramètre template). Malheureusement, le code suivant ne comstackra pas en utilisant MSVC 2010 (je n’ai pas facilement access à gcc en ce moment, donc je ne l’ai pas encore essayé). Quelqu’un sait pourquoi?

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, j’ai un travail en utilisant des parameters de modèle supplémentaires, mais je ne l’aime pas — il deviendra très verbeux lors du passage de nombreux types dans la chaîne d’inheritance.

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

MODIFIER:

Le message d’erreur que MSVC 2010 émet dans cette situation est l’ error C2039: 'value_type' : is not a member of 'derived'

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

derived est incomplète lorsque vous l’utilisez comme argument de modèle pour la base dans sa liste de classes de base.

Une solution courante consiste à utiliser un modèle de classe de traits. Voici votre exemple, caractéristique. Cela montre comment vous pouvez utiliser les deux types et fonctions de la classe dérivée à travers les traits.

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

Il vous suffit de spécialiser base_traits pour tous les types que vous utilisez pour l’argument derived_t de base et assurez-vous que chaque spécialisation fournit tous les membres derived_t par la base .

Un petit inconvénient de l’utilisation de traits est que vous devez en déclarer un pour chaque classe dérivée. Vous pouvez écrire une solution de contournement moins verbeuse et redondante comme celle-ci:

 template  

En C ++ 14, vous pouvez supprimer la déduction de type retour auto fonction typedef et utiliser la fonction:

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

Cela fonctionne car la déduction du type de retour de base::foo est retardée jusqu’à derived_t que derived_t soit terminé.

Une alternative à la saisie de traits nécessitant moins de règles est de placer votre classe dérivée dans une classe wrapper qui contient vos typedefs (ou les utiliser) et de passer le wrapper en tant qu’argument de modèle à votre classe de 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); } 

Vous pouvez éviter de passer 2 arguments dans le template . Dans CRTP, si vous avez l’assurance que class base sera associée à la class derived (et non à la class derived_2 ), utilisez la technique ci-dessous:

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

Usage:

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