Pack (seit C++11)
Ein Pack ist eine C++-Entität, die eines der folgenden definiert
- einen Parameter-Pack
- ein Template-Parameter-Pack
- einen Funktionsparameter-Pack
| (seit C++20) |
| (seit C++26) |
Ein Template-Parameter-Pack ist ein Template-Parameter, der null oder mehr Template-Argumente (Nicht-Typen, Typen oder Templates) akzeptiert. Ein Funktionsparameter-Pack ist ein Funktionsparameter, der null oder mehr Funktionsargumente akzeptiert.
|
Ein Lambda-Init-Capture-Pack ist ein Lambda-Capture, der für jedes Element der Pack-Expansion seines Initialisierers einen Init-Capture einführt. |
(seit C++20) |
|
Ein strukturiertes Bindungs-Pack ist ein Bezeichner in der Deklaration einer strukturierten Bindung, der null oder mehr strukturierte Bindungen einführt. |
(seit C++26) |
Die Anzahl der Elemente eines Packs ist gleich
- der Anzahl der für den Parameter-Pack bereitgestellten Argumente, wenn der Pack ein Template- oder Funktionsparameter-Pack ist,
|
(seit C++20) |
|
(seit C++26) |
Ein Template mit mindestens einem Parameter-Pack wird als variadic template (variadisches Template) bezeichnet.
[edit] Syntax
Template-Parameter-Pack (erscheint in Alias-Templates, Klassen-Templates, Variablen-Templates(seit C++14), Concepts(seit C++20) und Funktions-Templates-Parameterlisten)
type ... pack-name (optional) |
(1) | ||||||||
typename|class ... pack-name (optional) |
(2) | ||||||||
type-constraint ... pack-name (optional) |
(3) | (seit C++20) | |||||||
template < parameter-list > class ... pack-name (optional) |
(4) | (bis C++17) | |||||||
template < parameter-list > typename|class ... pack-name (optional) |
(4) | (seit C++17) | |||||||
Funktionsparameter-Pack (eine Form eines Deklarators, erscheint in der Funktionsparameterliste eines variadischen Funktions-Templates)
pack-name ... pack-param-name (optional) |
(5) | ||||||||
|
Für die Syntax von Nicht-Parameter-Packs siehe Lambda-Init-Capture-Pack und strukturiertes Bindungs-Pack(seit C++26). |
(seit C++20) |
Pack-Expansion (erscheint im Körper eines Templates)
pattern ... |
(6) | ||||||||
|
3) Ein eingeschränkter Typ-Template-Parameter-Pack mit optionalem Namen |
(seit C++20) |
patterns. Das Muster muss mindestens einen Pack enthalten.[edit] Erklärung
Ein variadisches Klassen-Template kann mit einer beliebigen Anzahl von Template-Argumenten instanziiert werden
template<class... Types> struct Tuple {}; Tuple<> t0; // Types contains no arguments Tuple<int> t1; // Types contains one argument: int Tuple<int, float> t2; // Types contains two arguments: int and float Tuple<0> t3; // error: 0 is not a type
Ein variadisches Funktions-Template kann mit einer beliebigen Anzahl von Funktionsargumenten aufgerufen werden (die Template-Argumente werden durch Template-Argument-Deduction abgeleitet)
template<class... Types> void f(Types... args); f(); // OK: args contains no arguments f(1); // OK: args contains one argument: int f(2, 1.0); // OK: args contains two arguments: int and double
In einem primären Klassen-Template muss der Template-Parameter-Pack der letzte Parameter in der Template-Parameterliste sein. In einem Funktions-Template darf der Template-Parameter-Pack früher in der Liste erscheinen, vorausgesetzt, alle nachfolgenden Parameter können aus den Funktionsargumenten abgeleitet werden oder haben Standardargumente.
template<typename U, typename... Ts> // OK: can deduce U struct valid; // template<typename... Ts, typename U> // Error: Ts... not at the end // struct Invalid; template<typename... Ts, typename U, typename=void> void valid(U, Ts...); // OK: can deduce U // void valid(Ts..., U); // Can't be used: Ts... is a non-deduced context in this position valid(1.0, 1, 2, 3); // OK: deduces U as double, Ts as {int, int, int}
Wenn jede gültige Spezialisierung eines variadischen Templates einen leeren Template-Parameter-Pack erfordert, ist das Programm fehlerhaft, keine Diagnose erforderlich.
[edit] Pack-Expansion
Ein Muster gefolgt von einer Ellipse, in dem der Name mindestens eines Packs mindestens einmal vorkommt, wird in null oder mehr Instanziierungen des Musters expandiert, wobei der Name des Packs durch jedes der Elemente aus dem Pack in Reihenfolge ersetzt wird. Instanziierungen von Ausrichtungs-Spezifizierern sind durch Leerzeichen getrennt, andere Instanziierungen sind durch Kommas getrennt.
template<class... Us> void f(Us... pargs) {} template<class... Ts> void g(Ts... args) { f(&args...); // “&args...” is a pack expansion // “&args” is its pattern } g(1, 0.2, "a"); // Ts... args expand to int E1, double E2, const char* E3 // &args... expands to &E1, &E2, &E3 // Us... pargs expand to int* E1, double* E2, const char** E3
Wenn die Namen zweier Packs im selben Muster vorkommen, werden sie gleichzeitig expandiert und müssen die gleiche Länge haben.
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>... is the pack expansion // Pair<Args1, Args2> is the pattern }; }; typedef zip<short, int>::with<unsigned short, unsigned>::type T1; // Pair<Args1, Args2>... expands to // Pair<short, unsigned short>, Pair<int, unsigned int> // T1 is Tuple<Pair<short, unsigned short>, Pair<int, unsigned>> // typedef zip<short>::with<unsigned short, unsigned>::type T2; // error: pack expansion contains packs of different lengths
Wenn eine Pack-Expansion innerhalb einer anderen Pack-Expansion verschachtelt ist, werden die Packs, die innerhalb der innersten Pack-Expansion vorkommen, von dieser expandiert, und es muss ein weiterer Pack in der umschließenden Pack-Expansion erwähnt werden, aber nicht in der innersten.
template<class... Args> void g(Args... args) { f(const_cast<const Args*>(&args)...); // const_cast<const Args*>(&args) is the pattern, it expands two packs // (Args and args) simultaneously f(h(args...) + args...); // Nested pack expansion: // inner pack expansion is "args...", it is expanded first // outer pack expansion is h(E1, E2, E3) + args..., it is expanded // second (as h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3) }
Wenn die Anzahl der Elemente in einem Pack null ist (leerer Pack), ändert die Instanziierung einer Pack-Expansion die syntaktische Interpretation des umschließenden Konstrukts nicht, selbst in Fällen, in denen das Weglassen der Pack-Expansion ansonsten fehlerhaft wäre oder zu einer syntaktischen Mehrdeutigkeit führen würde. Die Instanziierung erzeugt eine leere Liste.
template<class... Bases> struct X : Bases... { }; template<class... Args> void f(Args... args) { X<Args...> x(args...); } template void f<>(); // OK, X<> has no base classes // x is a variable of type X<> that is value-initialized
[edit] Expansionsorte
Je nachdem, wo die Expansion stattfindet, ist die resultierende durch Kommas getrennte (oder für Ausrichtungs-Spezifizierer durch Leerzeichen getrennte) Liste eine andere Art von Liste: Funktionsparameterliste, Member-Initialisiererliste, Attributliste usw. Die folgende Liste zeigt alle zulässigen Kontexte.
[edit] Funktionsargumentlisten
Eine Pack-Expansion kann innerhalb der Klammern eines Funktionsaufrufoperators erscheinen, in diesem Fall ist der größte Ausdruck oder die Klammer-eingeschlossene Initialisiererliste links von der Ellipse das Muster, das expandiert wird.
f(args...); // expands to f(E1, E2, E3) f(&args...); // expands to f(&E1, &E2, &E3) f(n, ++args...); // expands to f(n, ++E1, ++E2, ++E3); f(++args..., n); // expands to 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...); // expands to // f(h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
[edit] Klammer-initialisierte Initialisierer
Eine Pack-Expansion kann innerhalb der Klammern eines direkten Initialisierers, eines Funktionsstil-Casts und anderer Kontexte (Member-Initialisierer, new-Ausdruck usw.) erscheinen, in diesem Fall sind die Regeln identisch mit den Regeln für einen Funktionsaufrufausdruck.
Class c1(&args...); // calls Class::Class(&E1, &E2, &E3) Class c2 = Class(n, ++args...); // calls Class::Class(n, ++E1, ++E2, ++E3); ::new((void *)p) U(std::forward<Args>(args)...) // std::allocator::allocate
[edit] Klammer-eingeschlossene Initialisierer
In einer Klammer-eingeschlossenen Initialisiererliste kann auch eine Pack-Expansion erscheinen.
template<typename... Ts> void func(Ts... args) { const int size = sizeof...(args) + 2; int res[size] = {1, args..., 2}; // since initializer lists guarantee sequencing, this can be used to // call a function on each element of a pack, in order: int dummy[sizeof...(Ts)] = {(std::cout << args, 0)...}; }
[edit] Template-Argumentlisten
Pack-Expansionen können überall in einer Template-Argumentliste verwendet werden, vorausgesetzt, das Template hat die Parameter, die zur Erweiterung passen.
template<class A, class B, class... C> void func(A arg1, B arg2, C... arg3) { container<A, B, C...> t1; // expands to container<A, B, E1, E2, E3> container<C..., A, B> t2; // expands to container<E1, E2, E3, A, B> container<A, C..., B> t3; // expands to container<A, E1, E2, E3, B> }
[edit] Funktionsparameterliste
In einer Funktionsparameterliste, wenn eine Ellipse in einer Parameterdeklaration erscheint (egal ob sie einen Funktionsparameter-Pack benennt (wie in, Args... args) oder nicht), ist die Parameterdeklaration das Muster.
template<typename... Ts> void f(Ts...) {} f('a', 1); // Ts... expands to void f(char, int) f(0.1); // Ts... expands to 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] expands to // const char (&)[2], int(&)[1]
Hinweis: Im Muster Ts (&...arr)[N] ist die Ellipse das innerste Element, nicht das letzte Element wie in allen anderen Pack-Expansionen.
Hinweis: Ts (&...)[N] ist nicht erlaubt, da die C++11-Grammatik erfordert, dass die geklammerte Ellipse einen Namen hat: CWG-Problem 1488.
[edit] Template-Parameterliste
Pack-Expansion kann in einer Template-Parameterliste erscheinen.
template<typename... T> struct value_holder { template<T... Values> // expands to a non-type template parameter struct apply {}; // list, such as <int, char, int(&)[5]> };
[edit] Basis-Spezifizierer und Member-Initialisiererlisten
Eine Pack-Expansion kann die Liste der Basisklassen in einer Klassendeklaration bezeichnen. Typischerweise bedeutet dies auch, dass der Konstruktor eine Pack-Expansion in der Member-Initialisiererliste verwenden muss, um die Konstruktoren dieser Basen aufzurufen.
template<class... Mixins> class X : public Mixins... { public: X(const Mixins&... mixins) : Mixins(mixins)... {} };
[edit] Lambda-Captures
Pack-Expansion kann in der Capture-Klausel eines Lambda-Ausdrucks erscheinen.
template<class... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
[edit] Der sizeof... Operator
Der sizeof...-Operator wird ebenfalls als Pack-Expansion klassifiziert.
template<class... Types> struct count { static const std::size_t value = sizeof...(Types); };
Dynamische Ausnahme-SpezifikationenDie Liste der Ausnahmen in einer dynamischen Ausnahme-Spezifikation kann ebenfalls eine Pack-Expansion sein. template<class... X> void func(int arg) throw(X...) { // ... throw different Xs in different situations } |
(bis C++17) |
[edit] Ausrichtungs-Spezifizierer
Pack-Expansionen sind sowohl in den Typenlisten als auch in den Ausdrücken, die vom Schlüsselwort alignas verwendet werden, erlaubt. Die Instanziierungen sind durch Leerzeichen getrennt.
template<class... T> struct Align { alignas(T...) unsigned char buffer[128]; }; Align<int, short> a; // the alignment specifiers after expansion are // alignas(int) alignas(short) // (no comma in between)
[edit] Attributliste
Pack-Expansionen sind in den Listen von Attributen erlaubt, sofern die Spezifikation des Attributs dies zulässt. Zum Beispiel:
template<int... args> [[vendor::attr(args)...]] void* f();
Falt-AusdrückeIn Fold-Ausdrücken ist das Muster der gesamte Teilausdruck, der keinen unexpandierten Pack enthält. Using-DeklarationenIn Using-Deklarationen kann eine Ellipse in der Liste der Deklaratoren erscheinen. Dies ist nützlich, wenn von einem Template-Parameter-Pack abgeleitet wird. template<typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // OK: B::g and D::g introduced |
(seit C++17) |
PaketindizierungIn der Pack-Indizierung enthält die Pack-Expansion einen unexpandierten Pack, gefolgt von einer Ellipse und einem Index. Das Muster eines Pack-Indizierungs-Ausdrucks ist ein identifier, während das Muster eines Pack-Indizierungs-Spezifizierers ein typedef-name ist. 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); Friend-DeklarationenIn Klassen-Friend-Deklarationen kann jeder Typspezifizierer mit einer Ellipse gefolgt werden. 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>> Fold-erweiterte ConstraintsIn Fold-erweiterten Constraints ist das Muster der Constraint dieses Fold-erweiterten Constraints. Ein Fold-erweiterter Constraint wird nicht instanziiert. |
(seit C++26) |
[edit] Anmerkungen
| Dieser Abschnitt ist unvollständig Grund: ein paar Worte zu partiellen Spezialisierungen und anderen Möglichkeiten, auf einzelne Elemente zuzugreifen? Erwähnen Sie Rekursion vs. logarithmische vs. Abkürzungen wie Fold-Ausdrücke |
| Feature-Test-Makro | Wert | Std | Feature |
|---|---|---|---|
__cpp_variadic_templates |
200704L |
(C++11) | Variadische Templates |
__cpp_pack_indexing |
202311L |
(C++26) | Paketindizierung |
[edit] Beispiel
Das folgende Beispiel definiert eine Funktion ähnlich wie std::printf, die jedes Vorkommen des Zeichens % im Formatstring durch einen Wert ersetzt.
Die erste Überladung wird aufgerufen, wenn nur der Formatstring übergeben wird und keine Parametererweiterung stattfindet.
Die zweite Überladung enthält einen separaten Template-Parameter für den Kopf der Argumente und einen Parameter-Pack. Dies ermöglicht dem rekursiven Aufruf, nur den Schwanz der Parameter zu übergeben, bis er leer wird.
Targs ist der Template-Parameter-Pack und Fargs ist der Funktionsparameter-Pack.
#include <iostream> void tprintf(const char* format) // base function { std::cout << format; } template<typename T, typename... Targs> void tprintf(const char* format, T value, Targs... Fargs) // recursive variadic function { for (; *format != '\0'; format++) { if (*format == '%') { std::cout << value; tprintf(format + 1, Fargs...); // recursive call return; } std::cout << *format; } } int main() { tprintf("% world% %\n", "Hello", '!', 123); }
Ausgabe
Hello world! 123
[edit] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 1533 | C++11 | eine Pack-Expansion könnte in einem Member-Initialisierer für ein Mitglied auftreten | nicht erlaubt |
| CWG 2717 | C++11 | Instanziierungen von Ausrichtungs-Spezifizierern waren durch Kommas getrennt | sie sind durch Leerzeichen getrennt |
[edit] Siehe auch
| Funktionsschablone | Definiert eine Familie von Funktionen |
| Klassenschablone | Definiert eine Familie von Klassen |
sizeof...
|
Fragt die Anzahl der Elemente in einem Pack ab |
| C-style variadic function (C-artiges variadisches Funktion) | Nimmt eine variable Anzahl von Argumenten entgegen |
| Präprozessor-Makros | Können ebenfalls variadisch sein |
| Fold-Ausdruck | Reduziert einen Pack über einen binären Operator |
| Paketindizierung | Greift auf das Element eines Packs am angegebenen Index zu |