Pack (since C++11)
Ein Pack ist eine C++-Entität, die eines der folgenden Elemente definiert:
- ein Parameterpaket
-
- Template-Parameterpaket
- Funktionsparameterpaket
| (seit C++20) |
| (seit C++26) |
Ein Template-Parameter-Pack ist ein Template-Parameter, der null oder mehr Template-Argumente (Konstanten, Typen oder Templates) akzeptiert. Ein Funktionsparameter-Pack ist ein Funktionsparameter, der null oder mehr Funktionsargumente akzeptiert.
|
Ein Lambda-Init-Capture-Pack ist eine Lambda-Capture, die für jedes Element in der Pack-Expansion ihres Initialisierers eine Init-Capture einführt. |
(since C++20) |
|
Ein strukturierter Bindungs-Pack ist ein Bezeichner in der strukturierten Bindungsdeklaration, der null oder mehr strukturierte Bindungen einführt. |
(since C++26) |
Die Anzahl der Elemente eines Packs ist gleich:
- die Anzahl der für den Parameter-Pack bereitgestellten Argumente, falls es sich um einen Template- oder Funktionsparameter-Pack handelt,
|
(seit C++20) |
|
(seit C++26) |
Eine Vorlage mit mindestens einem Parameterpaket wird als variadic template bezeichnet.
Syntax
Template-Parameter-Paket (erscheint in Alias-Template , Klassentemplate , Variablen-Template (seit C++14) , Concept (seit C++20) und Funktionstemplate Parameterlisten)
Typ
...
Paketname
(optional)
|
(1) | ||||||||
typename
|
class
...
Paketname
(optional)
|
(2) | ||||||||
Typ-Einschränkung
...
Paketname
(optional)
|
(3) | (seit C++20) | |||||||
template
<
Parameterliste
>
class
...
Paketname
(optional)
|
(4) | (bis C++17) | |||||||
template
<
Parameterliste
>
typename
|
class
...
Paketname
(optional)
|
(4) | (seit C++17) | |||||||
Funktionsparameter-Pack (eine Form des Deklarators , erscheint in einer Funktionsparameterliste einer variadischen Funktionsvorlage)
pack-name
...
pack-param-name
(optional)
|
(5) | ||||||||
|
Für die Syntax von Nicht-Parameter-Packs, siehe Lambda-Init-Capture-Pack und Structured-Binding-Pack (seit C++26) . |
(seit C++20) |
Pack-Erweiterung (erscheint im Rumpf eines Templates)
pattern
...
|
(6) | ||||||||
|
3)
Ein
constrained
Typ-Template-Parameterpaket mit einem optionalen Namen
|
(since C++20) |
pattern
s. Das Pattern muss mindestens einen Pack enthalten.
Erklärung
Ein variadisches Klassentemplate kann mit beliebig vielen Template-Argumenten instanziiert werden:
template<class... Types> struct Tuple {}; Tuple<> t0; // Types enthält keine Argumente Tuple<int> t1; // Types enthält ein Argument: int Tuple<int, float> t2; // Types enthält zwei Argumente: int und float Tuple<0> t3; // Fehler: 0 ist kein Typ
Eine variadische Funktionsvorlage kann mit beliebig vielen Funktionsargumenten aufgerufen werden (die Vorlagenargumente werden durch Template-Argument-Deduktion abgeleitet):
template<class... Types> void f(Types... args); f(); // OK: args enthält keine Argumente f(1); // OK: args enthält ein Argument: int f(2, 1.0); // OK: args enthält zwei Argumente: int und double
In einer primären Klassenvorlage muss der Template-Parameterpack der letzte Parameter in der Template-Parameterliste sein. In einer Funktionsvorlage kann der Template-Parameterpack früher in der Liste erscheinen, vorausgesetzt, dass alle folgenden Parameter von den Funktionsargumenten abgeleitet werden können oder Standardargumente haben:
template<typename U, typename... Ts> // OK: kann U ableiten struct valid; // template<typename... Ts, typename U> // Fehler: Ts... nicht am Ende // struct Invalid; template<typename... Ts, typename U, typename=void> void valid(U, Ts...); // OK: kann U ableiten // void valid(Ts..., U); // Kann nicht verwendet werden: Ts... ist in dieser Position ein nicht ableitbarer Kontext valid(1.0, 1, 2, 3); // OK: leitet U als double ab, Ts als {int, int, int}
Wenn jede gültige Spezialisierung eines variadischen Templates ein leeres Template-Parameterpaket erfordert, ist das Programm ill-formed, keine Diagnose erforderlich.
Paketerweiterung
Einem Muster, dem eine Auslassung folgt, in dem der Name mindestens eines Packs mindestens einmal erscheint, wird erweitert in null oder mehr Instanzen des Musters, wobei der Name des Packs durch jedes der Elemente aus dem Pack in der Reihenfolge ersetzt wird. Instanzen von alignment specifiers werden durch Leerzeichen getrennt, andere Instanzen werden durch Kommas getrennt.
template<class... Us> void f(Us... pargs) {} template<class... Ts> void g(Ts... args) { f(&args...); // „&args...“ ist eine Pack-Erweiterung // „&args“ ist ihr Muster } g(1, 0.2, "a"); // Ts... args expandiert zu int E1, double E2, const char* E3 // &args... expandiert zu &E1, &E2, &E3 // Us... pargs expandiert zu int* E1, double* E2, const char** E3
Wenn die Namen zweier Pakete im selben Muster erscheinen, werden sie gleichzeitig erweitert und müssen dieselbe 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>... ist die Paketentfaltung // Pair<Args1, Args2> ist das Muster }; }; typedef zip<short, int>::with<unsigned short, unsigned>::type T1; // Pair<Args1, Args2>... entfaltet sich zu // Pair<short, unsigned short>, Pair<int, unsigned int> // T1 ist Tuple<Pair<short, unsigned short>, Pair<int, unsigned>> // typedef zip<short>::with<unsigned short, unsigned>::type T2; // Fehler: Paketentfaltung enthält Pakete unterschiedlicher Längen
Wenn eine Paketentfaltung innerhalb einer anderen Paketentfaltung verschachtelt ist, werden die Pakete, die in der innersten Paketentfaltung erscheinen, von dieser entfaltet, und es muss ein weiteres Paket in der umschließenden Paketentfaltung erwähnt werden, jedoch nicht in der innersten:
template<class... Args> void g(Args... args) { f(const_cast<const Args*>(&args)...); // const_cast<const Args*>(&args) ist das Muster, es erweitert zwei Packs // (Args und args) gleichzeitig f(h(args...) + args...); // Verschachtelte Pack-Erweiterung: // innere Pack-Erweiterung ist "args...", sie wird zuerst erweitert // äußere Pack-Erweiterung ist h(E1, E2, E3) + args..., sie wird // danach erweitert (als 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 (leeres Pack), ändert die Instanziierung einer Pack-Expansion die syntaktische Interpretation des umschließenden Konstrukts nicht, selbst in Fällen, in denen das vollständige Weglassen der Pack-Expansion andernfalls fehlerhaft wäre oder zu einer Syntax-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<> hat keine Basisklassen // x ist eine Variable vom Typ X<>, die wertinitialisiert wird
Expansionsorte
Je nachdem, wo die Erweiterung stattfindet, ist die resultierende durch Kommas getrennte (oder für Ausrichtungsbezeichner durch Leerzeichen getrennte) Liste eine unterschiedliche Art von Liste: Funktionsparameterliste, Member-Initialisierungsliste, Attributliste usw. Folgendes ist die Liste aller zulässigen Kontexte:
Funktionsargumentlisten
Eine Paketentfaltung kann innerhalb der Klammern eines Funktionsaufrufoperators erscheinen, wobei der größte Ausdruck oder die geschweifte Initialisierungsliste links von den Auslassungspunkten das Muster ist, das entfaltet wird:
f(args...); // expandiert zu f(E1, E2, E3) f(&args...); // expandiert zu f(&E1, &E2, &E3) f(n, ++args...); // expandiert zu f(n, ++E1, ++E2, ++E3); f(++args..., n); // expandiert zu 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...); // expandiert zu // f(h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
Initialisierungen in Klammern
Eine Paketentfaltung kann innerhalb der Klammern eines direkten Initialisierers , eines funktionsstil-Casts und in anderen Kontexten auftreten ( Member-Initialisierer , new-Ausdruck , etc.), wobei die Regeln identisch zu den Regeln für einen Funktionsaufrufausdruck oben sind:
Class c1(&args...); // ruft Class::Class(&E1, &E2, &E3) auf Class c2 = Class(n, ++args...); // ruft Class::Class(n, ++E1, ++E2, ++E3) auf ::new((void *)p) U(std::forward<Args>(args)...) // std::allocator::allocate
Geschweifte Klammern-Initialisierer
In einer von geschweiften Klammern umschlossenen Initialisierungsliste kann ebenfalls eine Paketentwicklung auftreten:
template<typename... Ts> void func(Ts... args) { const int size = sizeof...(args) + 2; int res[size] = {1, args..., 2}; // Da Initialisierungslisten eine Reihenfolge garantieren, kann dies verwendet werden, // um eine Funktion für jedes Element eines Packs in Reihenfolge aufzurufen: int dummy[sizeof...(Ts)] = {(std::cout << args, 0)...}; }
Template-Argumentenlisten
Pack-Erweiterungen können überall in einer Template-Argumentliste verwendet werden, sofern das Template über die entsprechenden Parameter verfügt, um die Erweiterung aufzunehmen:
template<class A, class B, class... C> void func(A arg1, B arg2, C... arg3) { container<A, B, C...> t1; // erweitert zu container<A, B, E1, E2, E3> container<C..., A, B> t2; // erweitert zu container<E1, E2, E3, A, B> container<A, C..., B> t3; // erweitert zu container<A, E1, E2, E3, B> }
Funktionsparameterliste
In einer Funktionsparameterliste, wenn eine Ellipse in einer Parameterdeklaration erscheint (ob sie einen Funktionsparameterpack benennt (wie in,
Args
...
args
) oder nicht) ist die Parameterdeklaration das Muster:
template<typename... Ts> void f(Ts...) {} f('a', 1); // Ts... expandiert zu void f(char, int) f(0.1); // Ts... expandiert zu 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] expandiert zu // const char (&)[2], int(&)[1]
Hinweis: Im Muster
Ts (&...arr)[N]
ist die Ellipse das innerste Element, nicht das letzte Element wie bei allen anderen Pack-Erweiterungen.
Hinweis:
Ts (&...)[N]
ist nicht erlaubt, da die C++11-Grammatik erfordert, dass die eingeklammerte Ellipse einen Namen hat:
CWG issue 1488
.
Template-Parameterliste
Paketentfaltung kann in einer Template-Parameterliste erscheinen:
template<typename... T> struct value_holder { template<T... Values> // erweitert sich zu einer konstanten Template-Parameter struct apply {}; // Liste, wie z.B. <int, char, int(&)[5]> };
Basis-Spezifizierer und Member-Initialisierungslisten
Eine Pack-Erweiterung kann die Liste der Basisklassen in einer Klassendeklaration bezeichnen. Typischerweise bedeutet dies auch, dass der Konstruktor eine Pack-Erweiterung in der Member-Initialisierungsliste verwenden muss, um die Konstruktoren dieser Basen aufzurufen:
template<class... Mixins> class X : public Mixins... { public: X(const Mixins&... mixins) : Mixins(mixins)... {} };
Lambda-Ausdrücke Erfassungen
Paketentfaltung kann in der Erfassungsklausel eines Lambda -Ausdrucks erscheinen:
template<class... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
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 Exception-SpezifikationenDie Liste der Exceptions in einer dynamischen Exception-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) |
Ausrichtungsspezifizierer
Pack-Erweiterungen sind sowohl in den Typenlisten als auch in den Ausdruckslisten erlaubt, die vom Schlüsselwort
alignas
verwendet werden. Die Instanziierungen werden durch Leerzeichen getrennt:
template<class... T> struct Align { alignas(T...) unsigned char buffer[128]; }; Align<int, short> a; // die Ausrichtungsspezifizierer nach der Erweiterung sind // alignas(int) alignas(short) // (kein Komma dazwischen)
Attributliste
Pack-Erweiterungen sind in den Listen von Attributen erlaubt, sofern durch die Spezifikation des Attributs zugelassen. Zum Beispiel:
template<int... args> [[vendor::attr(args)...]] void* f();
Fold-AusdrückeIn Fold-Ausdrücken ist das Muster der gesamte Teilausdruck, der kein unentpacktes Pack enthält. Using-DeklarationenIn Using-Deklarationen können Auslassungspunkte in der Liste der Deklaratoren erscheinen, dies ist nützlich bei Ableitung von einem Template-Parameter-Pack: template<typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // OK: B::g and D::g introduced |
(seit C++17) |
Pack-IndexierungBei der Pack-Indexierung enthält die Pack-Erweiterung ein nicht expandiertes Pack gefolgt von einer Ellipse und einem Index. Das Muster des Pack-Indexierungsausdrucks ist ein Bezeichner , während das Muster des Pack-Indexierungs-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 von 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; // Klassen C und E sind Friends von R<C, E> R<R<E>, R<C, int>> rr; // E::Nested und C sind Friends von R<R<E>, R<C, int>> Falt-erweiterte ConstraintsBei falt-erweiterten Constraints ist das Muster das Constraint dieses falt-erweiterten Constraints. Ein falt-erweitertes Constraint wird nicht instanziiert. |
(seit C++26) |
Hinweise
|
Dieser Abschnitt ist unvollständig
Grund: Einige Worte zu partiellen Spezialisierungen und anderen Möglichkeiten, auf individuelle Elemente zuzugreifen? Rekursion vs. logarithmisch vs. Abkürzungen wie Fold-Ausdrücke erwähnen |
| Feature-Test Makro | Wert | Std | Feature |
|---|---|---|---|
__cpp_variadic_templates
|
200704L
|
(C++11) | Variadic templates |
__cpp_pack_indexing
|
202311L
|
(C++26) | Pack indexing |
Beispiel
Das folgende Beispiel definiert eine Funktion ähnlich
std::printf
, die jedes Vorkommen des Zeichens
%
im Formatstring durch einen Wert ersetzt.
Die erste Überladung wird aufgerufen, wenn nur die Formatzeichenkette übergeben wird und keine Parameterersetzung stattfindet.
Die zweite Überladung enthält einen separaten Template-Parameter für den Kopf der Argumente und einen Parameter-Pack, dies ermöglicht den rekursiven Aufruf, nur den Rest 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) // Basisfunktion { std::cout << format; } template<typename T, typename... Targs> void tprintf(const char* format, T value, Targs... Fargs) // rekursive variadische Funktion { for (; *format != '\0'; format++) { if (*format == '%') { std::cout << value; tprintf(format + 1, Fargs...); // rekursiver Aufruf return; } std::cout << *format; } } int main() { tprintf("% world% %\n", "Hello", '!', 123); }
Ausgabe:
Hello world! 123
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrektes Verhalten |
|---|---|---|---|
| CWG 1533 | C++11 | eine Pack-Expansion konnte in einem Member-Initialisierer für ein Member auftreten | nicht erlaubt |
| CWG 2717 | C++11 | Instanziierungen von Ausrichtungs-Spezifizierern waren kommagetrennt | sie sind leerzeichengetrennt |
Siehe auch
| Function template | Definiert eine Familie von Funktionen |
| Class template | Definiert eine Familie von Klassen |
sizeof...
|
Ermittelt die Anzahl der Elemente in einem Pack |
| C-style variadic function | Akzeptiert eine variable Anzahl von Argumenten |
| Preprocessor macros | Können ebenfalls variadisch sein |
| Fold expression | Reduziert ein Pack über einen binären Operator |
| Pack indexing | Greift auf das Element eines Packs an einem bestimmten Index zu |