Instanciation de modèles C ++: éviter les commutateurs longs

J’ai une classe qui dépend d’un paramètre de modèle entier. À un moment de mon programme, je souhaite utiliser une instanciation de ce modèle, en fonction d’une valeur de ce paramètre déterminée lors de l’exécution. Voici un exemple simple démontrant comment je procéderais actuellement, en utilisant une grosse instruction switch:

#include  #include  #include  template struct Wrapper { typedef typename std::conditional::type DataType; DataType content[A]; void foo() { std::cout << A << std::endl; }; }; int main(int argc, char *argv[]) { std::string arg = argv[1]; int arg_int = std::stoi(arg); switch (arg_int) { case 1: { Wrapper w; w.foo(); break; } case 2: { Wrapper w; w.foo(); break; } case 3: { Wrapper w; w.foo(); break; } default: return 1; }; return 0; } 

Cela deviendra rapidement fastidieux une fois que je n’ai pas seulement un paramètre A , mais plusieurs arguments de modèle dans diverses combinaisons. Supposons également qu’en réalité, il existe une très bonne raison pour implémenter A comme paramètre de modèle.

Existe-t-il un moyen de remplacer l’énorme déclaration switch par des déclarations case presque identiques, par exemple en utilisant une magie de métaprogrammation de Boost ou un piratage de préprocesseur?

Idéalement, j’aimerais pouvoir écrire quelque chose comme ceci:

 INSTANTIATE_DEPENDING(i, {1, 2, 3}, { Wrapper w; w.foo(); } ); 

Vous pouvez utiliser un modèle variadique, peut-être comme ceci:

 #include  #include  int main(int argc, char * argv[]) { if (argc != 2) { return EXIT_FAILURE; } handle_cases<1, 3, 4, 9, 11>(std::stoi(argv[1])); } 

La mise en oeuvre:

 template  struct IntList {}; void handle_cases(int, IntList<>) { /* "default case" */ } template  void handle_cases(int i, IntList) { if (I != i) { return handle_cases(i, IntList()); } Wrapper w; w.foo(); } template  void handle_cases(int i) { handle_cases(i, IntList()); } 

arg_int est un paramètre d’exécution, il est donc impossible de l’associer directement à un paramètre de modèle. Vous pouvez utiliser une sorte de table de gestionnaire permettant de supprimer l’instruction switch ici.

Vous utiliseriez quelque chose comme lookup_handler( int N ) renvoyant un handler type qui pourrait être un lambda invoquant l’une de ces fonctions de modèle.

L’enregistrement de tous vos lambdas sur la table peut être effectué de manière récursive en commençant par le nombre le plus élevé que vous autorisez.

 template< unsigned N > register_lambda() { table.add( Wrapper() ); register_lambda< N-1 >; } 

et se spécialiser pour register_lambda<0>

Ensuite, quelque part, vous appelez register_lambda<32> et vous avez enregistré tous les numéros de 0 à 32.

Une façon d’implémenter une telle table est:

 class lambda_table { typedef std::function lambda_type; public: void add( lambda_type ); bool lookup( size_t key, lambda_type & lambda ) const; }; 

Depuis main () ou où que vous souhaitiez l’invoquer, vous avez une référence à cette table (appelez-la table), puis appelez

 lambda_type lambda; if( table.find( arg_int, lambda ) ) lanbda(); else default_handler(); 

Vous pouvez changer ceci pour donner à la table elle-même un gestionnaire par défaut où aucun n’a été fourni pour ce nombre.

Bien que lambdas puisse englober toutes sortes de données membres, vous souhaiterez peut-être que vos modèles soient des classes dans une hiérarchie plutôt que des lambdas, étant donné le stockage de données qu’elles contiennent.

juste utiliser des macros!

 template struct Wrapper { int content[A]; void foo() { }; }; #define WRAPPER_SWITCH_CASE(i) case i: Wrapper().foo(); break; int main(int argc, char *argv[]) { std::ssortingng arg = argv[1]; int arg_int = std::stoi(arg); switch (arg_int) { WRAPPER_SWITCH_CASE(1) WRAPPER_SWITCH_CASE(2) WRAPPER_SWITCH_CASE(3) default: return 1; }; return 0; } 

(exemple live)

Mais comme vous le savez, les macros sont nuisibles. Je pense que Wrapper devrait atsortingbuer du content au moment de l’exécution, pas un modèle.

un petit prof d’application de concept utilisant un générateur récursif pour les wrappers:

 #include  #include  struct FooProvider { virtual void foo() = 0; }; template struct Wrapper : public FooProvider { Wrapper() {std::cout << A << std::endl;} int content[A]; virtual void foo() { std::cout << "call:" << A << std::endl;}; }; static std::vector providers; template  struct Instantiator { Instantiator() { providers.insert(providers.begin(), new Wrapper); Instantiator(); } }; template <> struct Instantiator<0> { Instantiator() {} }; int main() { Instantiator<100>(); providers[4]->foo(); // do not forget to delete the providers } 

Vous pouvez simplement utiliser une macro de bouclage d’ordre supérieur qui transmet l’implémentation du bloc à un expandeur de boucle générique:

 #define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N #define M_CONC(A, B) M_CONC_(A, B) #define M_CONC_(A, B) A##B #define M_ID(...) __VA_ARGS__ #define M_FOR_EACH(ACTN, ...) M_CONC(M_FOR_EACH_, M_NARGS(__VA_ARGS__)) (ACTN, __VA_ARGS__) #define M_FOR_EACH_0(ACTN, E) E #define M_FOR_EACH_1(ACTN, E) ACTN(E) #define M_FOR_EACH_2(ACTN, E, ...) ACTN(E) M_FOR_EACH_1(ACTN, __VA_ARGS__) #define M_FOR_EACH_3(ACTN, E, ...) ACTN(E) M_FOR_EACH_2(ACTN, __VA_ARGS__) #define M_FOR_EACH_4(ACTN, E, ...) ACTN(E) M_FOR_EACH_3(ACTN, __VA_ARGS__) #define M_FOR_EACH_5(ACTN, E, ...) ACTN(E) M_FOR_EACH_4(ACTN, __VA_ARGS__) //...etc #define INSTANTIATE_DEPENDING(L, C) M_FOR_EACH(C, M_ID L) //... #define CASE_BLOCK(n) case n: { Wrapper w; w.foo(); break; } INSTANTIATE_DEPENDING((1, 2, 3), CASE_BLOCK) #undef CASE_BLOCK //if you like, not essential to the concept 

Pas grand chose à dire à ce sujet: la boucle répète le bloc pour la longueur de la liste transmise, en passant les éléments de la liste à la macro à développer. Vous mettez donc votre implémentation dans cette macro (et #undef si vous voulez qu’elle soit locale).

Plus élégamment (en vous permettant d’imbriquer le code paramétré pour qu’il se développe dans l’expression à laquelle il appartient, au lieu d’une seconde définition), vous pouvez utiliser la bibliothèque de métaprogrammation Order, plutôt haut de gamme:

 #include  ORDER_PP( // runs Order code 8for_each_in_range(8fn(8I, 8print( (case) 8I (: { ) (Wrapper<) 8I (> w; w.foo(); break; }) )), 1, 4) ) 

(Utilisez 8for-each au lieu de 8for_each_in_range pour des listes non contiguës. Order possède une sémantique de functional programming complète, ce qui en fait des problèmes mineurs.)

En guise d’alternative générale aux commutateurs, vous pouvez utiliser un vecteur ou une carte de pointeurs de fonction pour supprimer le commutateur:

 template  int foo() { Wrapper w; w.foo(); return i; } static std::vector m; void init() { m.push_back(&foo<0>); m.push_back(&foo<1>); } void bar(int i) { m[i](); } 

En C ++ 11, vous pouvez utiliser une liste d’initialiseur pour initialiser le vecteur ou la carte.

Voici une autre approche:

 template void do_foo() { Wrapper w; w.foo(); } template struct fn_table : fn_table { }; template struct fn_table<0, Ns...> { static constexpr void (*fns[])() = {do_foo...}; }; template constexpr void (*fn_table<0, Ns...>::fns[sizeof...(Ns)])(); int main(int argc, char *argv[]) { std::ssortingng arg = argv[1]; int arg_int = std::stoi(arg); // 4 if you have Wrapper<0> to Wrapper<3>. fn_table<4>::fns[arg_int](); } 

Inspiré par la réponse de Kerrek SB avec des modèles variadiques, voici une solution qui peut facilement être étendue à plusieurs parameters de tout type:

 template  struct Params { const static int kParam1 = param1_; // Add other parameters here if needed }; // Default case: list is empty template  void handle_cases(const T param1) { } // Regular case: list is not-empty template  void handle_cases(const T param1) { if (head::kParam1 == param1) { Wrapper w; w.foo(); } else { handle_cases(param1); } } 

Notez que typename T est juste un exemple de paramètre de modèle supplémentaire qui ne fait pas partie de la liste head / tail.

Et voici comment l’utiliser:

 int main(int argc, char * argv[]) { if (argc != 2) { return EXIT_FAILURE; } handle_cases, Params<3>, Params<4>, Params<9>, Params<11>>(std::stoi(argv[1])); }