Pack (since C++11)
Un pack est une entité C++ qui définit l'un des éléments suivants :
- un paramètre pack
-
- template parameter pack
- function parameter pack
| (depuis C++20) |
| (depuis C++26) |
Un paramètre de modèle variadique est un paramètre de modèle qui accepte zéro ou plusieurs arguments de modèle (constantes, types ou modèles). Un paramètre de fonction variadique est un paramètre de fonction qui accepte zéro ou plusieurs arguments de fonction.
|
Une capture d'initialisation de pack lambda est une capture lambda qui introduit une capture d'initialisation pour chaque élément dans l'expansion du pack de son initialiseur. |
(depuis C++20) |
|
Une liaison structurée pack est un identifiant dans la déclaration de liaison structurée qui introduit zéro ou plusieurs liaisons structurées. |
(since C++26) |
Le nombre d'éléments d'un pack est égal à :
- le nombre d'arguments fournis pour le pack de paramètres, si le pack est un pack de paramètres de modèle ou de fonction,
|
(depuis C++20) |
|
(depuis C++26) |
Un modèle avec au moins un paquet de paramètres est appelé un modèle variadique .
Syntaxe
Pack de paramètres de modèle (apparaît dans les listes de paramètres des alias template , class template , variable template (depuis C++14) , concept (depuis C++20) et function template )
type
...
pack-name
(optionnel)
|
(1) | ||||||||
typename
|
class
...
pack-name
(optionnel)
|
(2) | ||||||||
type-constraint
...
pack-name
(optionnel)
|
(3) | (depuis C++20) | |||||||
template
<
parameter-list
>
class
...
pack-name
(optionnel)
|
(4) | (jusqu'à C++17) | |||||||
template
<
parameter-list
>
typename
|
class
...
pack-name
(optionnel)
|
(4) | (depuis C++17) | |||||||
Fonction parameter pack (une forme de declarator , apparaît dans une liste de paramètres de fonction d'un modèle de fonction variadique)
pack-name
...
pack-param-name
(facultatif)
|
(5) | ||||||||
|
Pour la syntaxe des packs non-paramètres, voir lambda init-capture pack et structured binding pack (depuis C++26) . |
(depuis C++20) |
Expansion de pack (apparaît dans le corps d'un template)
pattern
...
|
(6) | ||||||||
|
3)
Un
pack de paramètres de template de type contraint
avec un nom optionnel
|
(depuis C++20) |
pattern
s. Le motif doit inclure au moins un paquet.
Explication
Un modèle de classe variadique peut être instancié avec n'importe quel nombre d'arguments de modèle :
template<class... Types> struct Tuple {}; Tuple<> t0; // Types ne contient aucun argument Tuple<int> t1; // Types contient un argument : int Tuple<int, float> t2; // Types contient deux arguments : int et float Tuple<0> t3; // erreur : 0 n'est pas un type
Une fonction template variadique peut être appelée avec n'importe quel nombre d'arguments de fonction (les arguments template sont déduits via template argument deduction ):
template<class... Types> void f(Types... args); f(); // OK : args ne contient aucun argument f(1); // OK : args contient un argument : int f(2, 1.0); // OK : args contient deux arguments : int et double
Dans un modèle de classe primaire, le paquet de paramètres de modèle doit être le dernier paramètre dans la liste des paramètres de modèle. Dans un modèle de fonction, le paquet de paramètres de modèle peut apparaître plus tôt dans la liste à condition que tous les paramètres suivants puissent être déduits des arguments de la fonction, ou aient des arguments par défaut :
template<typename U, typename... Ts> // OK : peut déduire U struct valid; // template<typename... Ts, typename U> // Erreur : Ts... pas à la fin // struct Invalid; template<typename... Ts, typename U, typename=void> void valid(U, Ts...); // OK : peut déduire U // void valid(Ts..., U); // Ne peut pas être utilisé : Ts... est un contexte non déduit dans cette position valid(1.0, 1, 2, 3); // OK : déduit U comme double, Ts comme {int, int, int}
Si toute spécialisation valide d'un modèle variadique nécessite un pack de paramètres de modèle vide, le programme est mal formé, aucun diagnostic requis.
Expansion de pack
Un motif suivi de points de suspension, dans lequel le nom d'au moins un pack apparaît au moins une fois, est développé en zéro ou plusieurs instanciations du motif, où le nom du pack est remplacé par chacun des éléments du pack, dans l'ordre. Les instanciations des spécificateurs d'alignement sont séparées par des espaces, les autres instanciations sont séparées par des virgules.
template<class... Us> void f(Us... pargs) {} template<class... Ts> void g(Ts... args) { f(&args...); // « &args... » est une expansion de pack // « &args » est son motif } g(1, 0.2, "a"); // Ts... args se développe en int E1, double E2, const char* E3 // &args... se développe en &E1, &E2, &E3 // Us... pargs se développe en int* E1, double* E2, const char** E3
Si les noms de deux paquets apparaissent dans le même motif, ils sont développés simultanément et doivent avoir la même longueur :
template<typename...> struct Tuple {}; template<typename T1, typename T2> struct Pair {}; template<class... Args1> struct zip { template<class... Args2> struct with { typedef Tuple<Pair<Args1, Args2>...> type; // Pair<Args1, Args2>... est l'expansion de pack // Pair<Args1, Args2> est le motif }; }; typedef zip<short, int>::with<unsigned short, unsigned>::type T1; // Pair<Args1, Args2>... se développe en // Pair<short, unsigned short>, Pair<int, unsigned int> // T1 est Tuple<Pair<short, unsigned short>, Pair<int, unsigned>> // typedef zip<short>::with<unsigned short, unsigned>::type T2; // erreur : l'expansion de pack contient des packs de longueurs différentes
Si une expansion de paquet est imbriquée dans une autre expansion de paquet, les paquets qui apparaissent à l'intérieur de l'expansion de paquet la plus interne sont développés par celle-ci, et il doit y avoir un autre paquet mentionné dans l'expansion de paquet englobante, mais pas dans la plus interne :
template<class... Args> void g(Args... args) { f(const_cast<const Args*>(&args)...); // const_cast<const Args*>(&args) est le motif, il développe deux packs // (Args et args) simultanément f(h(args...) + args...); // Expansion de pack imbriquée : // l'expansion du pack interne est "args...", elle est développée en premier // l'expansion du pack externe est h(E1, E2, E3) + args..., elle est développée // ensuite (comme h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3) }
Lorsque le nombre d'éléments dans un pack est nul (pack vide), l'instanciation d'une expansion de pack ne modifie pas l'interprétation syntaxique de la construction englobante, même dans les cas où omettre complètement l'expansion de pack serait autrement mal formé ou entraînerait une ambiguïté syntaxique. L'instanciation produit une liste vide.
template<class... Bases> struct X : Bases... { }; template<class... Args> void f(Args... args) { X<Args...> x(args...); } template void f<>(); // OK, X<> n'a pas de classes de base // x est une variable de type X<> qui est initialisée par valeur
Foyers d'expansion
Selon l'endroit où l'expansion a lieu, la liste résultante séparée par des virgules (ou séparée par des espaces pour les spécificateurs d'alignement ) est un type différent de liste : liste de paramètres de fonction, liste d'initialisation de membres, liste d'attributs, etc. Voici la liste de tous les contextes autorisés :
Listes d'arguments de fonction
Une expansion de paquet peut apparaître à l'intérieur des parenthèses d'un opérateur d'appel de fonction, auquel cas la plus grande expression ou liste d'initialisation entre accolades à gauche des points de suspension est le motif qui est développé :
f(args...); // se développe en f(E1, E2, E3) f(&args...); // se développe en f(&E1, &E2, &E3) f(n, ++args...); // se développe en f(n, ++E1, ++E2, ++E3); f(++args..., n); // se développe en f(++E1, ++E2, ++E3, n); f(const_cast<const Args*>(&args)...); // f(const_cast<const E1*>(&X1), const_cast<const E2*>(&X2), const_cast<const E3*>(&X3)) f(h(args...) + args...); // se développe en // f(h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
Initialiseurs entre parenthèses
Une expansion de paquet peut apparaître à l'intérieur des parenthèses d'un initialiseur direct , d'une conversion de type fonctionnelle , et d'autres contextes ( initialiseur de membre , nouvelle-expression , etc.) auquel cas les règles sont identiques aux règles pour une expression d'appel de fonction ci-dessus :
Class c1(&args...); // appelle Class::Class(&E1, &E2, &E3) Class c2 = Class(n, ++args...); // appelle Class::Class(n, ++E1, ++E2, ++E3); ::new((void *)p) U(std::forward<Args>(args)...) // std::allocator::allocate
Initialiseurs entre accolades
Dans une liste d'initialisation entre accolades, une expansion de paquet peut également apparaître :
template<typename... Ts> void func(Ts... args) { const int size = sizeof...(args) + 2; int res[size] = {1, args..., 2}; // puisque les listes d'initialisation garantissent le séquencement, cela peut être utilisé pour // appeler une fonction sur chaque élément d'un pack, dans l'ordre : int dummy[sizeof...(Ts)] = {(std::cout << args, 0)...}; }
Listes d'arguments de template
Les expansions de paquets peuvent être utilisées n'importe où dans une liste d'arguments de template, à condition que le template ait les paramètres correspondant à l'expansion :
template<class A, class B, class... C> void func(A arg1, B arg2, C... arg3) { container<A, B, C...> t1; // se développe en container<A, B, E1, E2, E3> container<C..., A, B> t2; // se développe en container<E1, E2, E3, A, B> container<A, C..., B> t3; // se développe en container<A, E1, E2, E3, B> }
Liste des paramètres de fonction
Dans une liste de paramètres de fonction, si une ellipse apparaît dans une déclaration de paramètre (qu'elle nomme un pack de paramètres de fonction (comme dans,
Args
...
args
) ou non) la déclaration de paramètre est le motif :
template<typename... Ts> void f(Ts...) {} f('a', 1); // Ts... se développe en void f(char, int) f(0.1); // Ts... se développe en void f(double) template<typename... Ts, int... N> void g(Ts (&...arr)[N]) {} int n[1]; g<const char, int>("a", n); // Ts (&...arr)[N] se développe en // const char (&)[2], int(&)[1]
Note : Dans le motif
Ts (&...arr)[N]
, l'ellipse est l'élément le plus interne, et non le dernier élément comme dans toutes les autres expansions de paquets.
Note :
Ts (&...)[N]
n'est pas autorisé car la grammaire C++11 exige que l'ellipse entre parenthèses ait un nom :
CWG issue 1488
.
Liste des paramètres de modèle
L'expansion de paquet peut apparaître dans une liste de paramètres de modèle :
template<typename... T> struct value_holder { template<T... Values> // se développe en une liste de paramètres de template constants struct apply {}; // telle que <int, char, int(&)[5]> };
Spécificateurs de base et listes d'initialisation des membres
Une expansion de paquet peut désigner la liste des classes de base dans une déclaration de classe . Typiquement, cela signifie également que le constructeur doit utiliser une expansion de paquet dans la liste d'initialisation des membres pour appeler les constructeurs de ces bases :
template<class... Mixins> class X : public Mixins... { public: X(const Mixins&... mixins) : Mixins(mixins)... {} };
Captures de lambda
L'expansion de paquet peut apparaître dans la clause de capture d'une expression lambda :
template<class... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
L'opérateur sizeof...
L'opérateur
sizeof...
est également classifié comme une expansion de paquet :
template<class... Types> struct count { static const std::size_t value = sizeof...(Types); };
` et contient des termes spécifiques au C++ qui doivent être préservés. Seul le texte environnant (s'il y en avait) aurait été traduit en français.
Spécifications d'exceptions dynamiquesLa liste des exceptions dans une spécification d'exception dynamique peut également être une expansion de pack : template<class... X> void func(int arg) throw(X...) { // ... throw different Xs in different situations } |
(jusqu'à C++17) |
Spécificateur d'alignement
Les expansions de paquets sont autorisées à la fois dans les listes de types et les listes d'expressions utilisées par le mot-clé
alignas
. Les instanciations sont séparées par des espaces :
template<class... T> struct Align { alignas(T...) unsigned char buffer[128]; }; Align<int, short> a; // les spécificateurs d'alignement après expansion sont // alignas(int) alignas(short) // (aucune virgule entre les deux)
Liste des attributs
Les expansions de paquets sont autorisées dans les listes d' attributs , si cela est permis par la spécification de l'attribut. Par exemple :
template<int... args> [[vendor::attr(args)...]] void* f();
` et contient des termes spécifiques au C++ qui doivent être préservés. Seul le texte environnant (comme ce commentaire) a été traduit en français.
Expressions de repliDans les expressions de repli , le motif est la sous-expression entière qui ne contient pas de paquet non développé. Déclarations usingDans les déclarations using , des points de suspension peuvent apparaître dans la liste des déclarateurs, ce qui est utile lors de la dérivation d'un paquet de paramètres de modèle : template<typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // OK: B::g and D::g introduced |
(depuis C++17) |
Indexation de paquetsDans l' indexation de paquets , l'expansion de paquet contient un paquet non développé suivi de points de suspension et d'un indice. Le motif de l'expression d'indexation de paquet est un identifiant , tandis que le motif du spécificateur d'indexation de paquet est un typedef-name . consteval auto first_plus_last(auto... args) { return args...[0] + args...[sizeof...(args) - 1]; } static_assert(first_plus_last(5) == 10); static_assert(first_plus_last(5, 4) == 9); static_assert(first_plus_last(5, 6, 2) == 7); Déclarations friendDans les déclarations friend de classe, chaque spécificateur de type peut être suivi de points de suspension : struct C {}; struct E { struct Nested; }; template<class... Ts> class R { friend Ts...; }; template<class... Ts, class... Us> class R<R<Ts...>, R<Us...>> { friend Ts::Nested..., Us...; }; R<C, E> rce; // classes C and E are friends of R<C, E> R<R<E>, R<C, int>> rr; // E::Nested and C are friends of R<R<E>, R<C, int>> Contraintes développées par pliageDans les contraintes développées par pliage , le motif est la contrainte de cette contrainte développée par pliage. Une contrainte développée par pliage n'est pas instanciée. |
(depuis C++26) |
Notes
|
Cette section est incomplète
Raison : quelques mots sur les spécialisations partielles et autres méthodes pour accéder aux éléments individuels ? Mentionner la récursion vs logarithmique vs raccourcis tels que les expressions de pliage |
| Macro de test de fonctionnalité | Valeur | Std | Fonctionnalité |
|---|---|---|---|
__cpp_variadic_templates
|
200704L
|
(C++11) | Modèles variadiques |
__cpp_pack_indexing
|
202311L
|
(C++26) | Indexation de paquets |
Exemple
L'exemple ci-dessous définit une fonction similaire à
std::printf
, qui remplace chaque occurrence du caractère
%
dans la chaîne de format par une valeur.
La première surcharge est appelée lorsque seul le format de chaîne est passé et qu'il n'y a pas d'expansion de paramètre.
La deuxième surcharge contient un paramètre de modèle distinct pour la tête des arguments et un paquet de paramètres, cela permet à l'appel récursif de ne passer que la queue des paramètres jusqu'à ce qu'elle devienne vide.
Targs
est le paramètre de modèle pack et
Fargs
est le paramètre de fonction pack.
#include <iostream> void tprintf(const char* format) // fonction de base { std::cout << format; } template<typename T, typename... Targs> void tprintf(const char* format, T value, Targs... Fargs) // fonction variadique récursive { for (; *format != '\0'; format++) { if (*format == '%') { std::cout << value; tprintf(format + 1, Fargs...); // appel récursif return; } std::cout << *format; } } int main() { tprintf("% world% %\n", "Hello", '!', 123); }
Sortie :
Hello world! 123
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Applicable à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 1533 | C++11 | une expansion de pack pouvait survenir dans un initialiseur de membre | non autorisé |
| CWG 2717 | C++11 | les instanciations de spécificateurs d'alignement étaient séparées par des virgules | elles sont séparées par des espaces |
Voir aussi
| Function template | Définit une famille de fonctions |
| Class template | Définit une famille de classes |
sizeof...
|
Interroge le nombre d'éléments dans un pack |
| C-style variadic function | Accepte un nombre variable d'arguments |
| Preprocessor macros | Peut également être variadique |
| Fold expression | Réduit un pack avec un opérateur binaire |
| Pack indexing | Accède à l'élément d'un pack à un index spécifié |