Chapitre 3
GENERIQUES A PARAMETRES VARIABLES
VARIADIC TEMPLATES
 
 
Précédent Suivant
 

Génériques à paramètres variables
Variadic Templates
Syntaxe (1/2)
* Variadic template = générique à paramètres variables
* Liste des paramètres templates non fixée
* A l'instar des arguments variables d'une fonction
* Permet de modéliser des collections hétérogènes
* template <typename... TYPES> class Tuple;
* Permet de renforcer le contrôle de types
* fprintf(const char * format,...);
* Impossible d'identifier les types des arguments variables
* template <typename... TYPES>
void printf(const char * format,TYPES... args);
* Possibilité d'identifier le type de chaque argument variable
Syntaxe (2/2)
* Paramètres variables = "pack" de paramètres
* Pack représenté par le symbole "..."
* Pack = 0 à n paramètres
* Pack de valeurs: template <int... VALEURS>
* Pack de types: template <typename... TYPES>
* Pack de génériques
* Rappel: template <template <typename> class T> class X;
* Le paramètre T de la classe générique X est un générique
* template <template <typename> class... GENERIQUES>
* Pack de génériques à un paramètre
* template <template <typename...> class... GENERIQUES>
* Pack de génériques à paramètres variables
Expansion de pack (1/4)
* template <typename... TYPES>
==> template <typename T1, ... ,typename Tn>
* Il s'agit d'une illustration: T1...Tn n'existent pas explicitement
* Accès direct à un paramètre d'un pack impossible
* Un paramètre n'a pas d'identifiant
* Aucun moyen d'obtenir le nom d'un paramètre
* Un paramètre n'a pas de numéro
* Aucun moyen direct d'obtenir le nième paramètre
* Parcours et identification possibles par récursivité
* Ecriture de templates récursifs ==> métaprogrammation
* Nombre d'éléments d'un pack: opérateur sizeof...(PACK)
* Remarque: un template récursif peut aussi faire l'affaire
Expansion de pack (2/4)
* Comment utiliser ces paramètres alors ?
* Grâce au mécanisme d'expansion
* Schéma d'expansion
* Expression contenant l'identifiant d'un pack
* Et terminée par "..."
* Expansion = réplication du schéma
* Pour chaque paramètre du pack
* Séparation par une virgule
Expansion de pack (3/4)
* Supposons un pack
* template <typename... TYPES>
* Exemples d'expansions possibles
* TYPES...
==> T1, ... ,Tn
* X<TYPES>...
==> X<T1>, ... ,X<Tn>
* X<TYPES>::a...
==> X<T1>::a, ... ,X<Tn>::a
* X<TYPES>::m(u,v)...
==> X<T1>::m(u,v), ... ,X<Tn>::m(u,v)
* const TYPES &... x
==> const T1 & x1, ... ,const Tn & xn
Expansion de pack (4/4)
* Expansion de plusieurs packs
* Expansions simultanées
* X<PACK1,PACK2>... ==> X<T1,U1>, ... ,X<Tn,Un>
* Expansions séparées
* X<PACK1...,PACK2...> ==> X<T1, ... ,Tn,U1, ... ,Un>
* La localisation de "..." est importante
* Exercice: trouver les expansions suivantes (Andrei Alexandrescu)
template <typename... TYPES> void f(TYPES... args) {
g(A<TYPES...>::m(args)...);
g(A<TYPES...>::m(args...));
g(A<TYPES>::m(args)...);
}
* Remarque: "args" représente un pack d'arguments
* Mécanisme d'expansion similaire au pack de paramètres
Modélisation d'un tuple (1/3)
* Aperçu de "std::tuple"
* Déclaration du template
* template <typename... TYPES> class Tuple;
* Cas d'un pack vide
* template <> class Tuple<> {};
* Cas général (récursion)
* Pack séparé en deux: le premier paramètre et le reste
* template <typename T,typename... RESTE>
class Tuple<T,RESTE...> : public Tuple<RESTE...> {
public:
T value;
Tuple(const T & val,const RESTE &... args)
: Tuple<RESTE...>(args...), value(val) {}
};
Modélisation d'un tuple (2/3)
* Accès au nème élément d'un tuple ==> métaprogrammation
* Déclaration du template
* template <unsigned N,typename TUPLE> struct TupleElement;
* Récursion
template <unsigned N,typename T,typename... RESTE>
struct TupleElement< N, Tuple<T,RESTE...> >
: TupleElement< N-1, Tuple<RESTE...> > {};
* Condition d'arrêt
template <typename T,typename... RESTE>
struct TupleElement< 0, Tuple<T,RESTE...> > {
typedef T type;
static T & get(Tuple<T,RESTE...> & t) { return t.value; }
};
Modélisation d'un tuple (3/3)
* Utilisation du template
* typedef Tuple<int,double> MyTuple;
* MyTuple tuple(13,2.7);
* TupleElement<1,MyTuple>::type ==> double
* TupleElement<1,decltype(tuple)>::get(tuple) ==> 2.7
* Méthode "get" peu pratique ==> fonction d'assistance
template <unsigned N,typename... TYPES>
typename TupleElement< N, Tuple<TYPES...> >::type &
get_value(Tuple<TYPES...> & t) {
return TupleElement< N, Tuple<TYPES...> >::get(t);
}
* Utilisation simplifiée
* get_value<1>(tuple) ==> 2.7
Parcours d'un pack d'arguments (1/4)
* Comment parcourir un pack d'arguments ?
* Solution 1: approche récursive
* template <typename T>
void display(const T & valeur) {
cout << valeur << endl;
}
* template <typename T,typename... RESTE>
void display(const T & v1,const RESTE &... reste) {
cout << v1 << " ; ";
display(reste...);
}
* display(13,2.7,"Hello World !"); ==> 13 ; 2.7 ; Hello World !
Parcours d'un pack d'arguments (2/4)
* Solution 2: approche par expansion
* template <typename T> void display(const T & valeur)
{ cout << valeur << " ; "; }
* template <typename... TYPES>
void display(const TYPES &... valeurs) {
display(valeurs)...; ==> expansion interdite ici !
cout << endl;
}
* Pourtant l'expansion semble valide
* display(valeurs)... ==> display(v1), ... ,display(vn)
* Mais l'expansion n'est autorisée qu'à certains endroits
* Notamment ici: paramètre de template, argument de fonction,
base d'héritage, liste d'initialisation d'un constructeur
* http://en.cppreference.com/w/cpp/language/parameter_pack
Parcours d'un pack d'arguments (3/4)
* Solution 2 (suite)
* On peut utiliser une fonction pour recevoir l'expansion
template <typename... TYPES>
inline void variadic_call(const TYPES &...) {}
* 2ème tentative (presque bonne)
template <typename... TYPES>
void display(const TYPES &... valeurs) {
variadic_call(display(valeurs)...);
}
* Ne fonctionne que si "display" retourne une valeur
* 3ème tentative (acceptable)
template <typename... TYPES>
void display(const TYPES &... valeurs) {
variadic_call((display(valeurs),0)...);
}
Parcours d'un pack d'arguments (4/4)
* Solution 2 (suite et fin)
* Mais l'ordre d'évaluation n'est pas garanti
* display(13,2.7,"Hello World !"); ==> Hello World ! ; 2.7 ; 13 ;
* L'ordre peut être garanti par une liste d'initialisation
* Version finale (ouf !)
* struct variadic_call {
variadic_call(const std::initializer_list<int>) {}
};
* template <typename... TYPES>
void display(const TYPES &... valeurs) {
variadic_call{(display(valeurs),0)...};
}

Voici le code source des exemples présentés: