Pack (since C++11)
パックは、以下のいずれかを定義するC++エンティティです:
- パラメータパック
-
- template parameter pack
- function parameter pack
| (C++20以降) |
| (C++26以降) |
テンプレートパラメータパックは、ゼロ個以上のテンプレート引数(定数、型、またはテンプレート)を受け入れるテンプレートパラメータです。関数パラメータパックは、ゼロ個以上の関数引数を受け入れる関数パラメータです。
|
ラムダ初期化キャプチャパックは、その初期化子のパック展開における各要素に対して初期化キャプチャを導入するラムダキャプチャです。 |
(since C++20) |
|
構造化バインディングパックは、ゼロ個以上の構造化バインディングを導入する構造化バインディング宣言内の識別子です。 |
(C++26以降) |
パックの要素数は以下と等しい:
- パラメータパックに提供された引数の数(パックがテンプレートまたは関数パラメータパックである場合)
|
(C++20 以降) |
|
(C++26 以降) |
少なくとも1つのパラメータパックを持つテンプレートは、 可変引数テンプレート と呼ばれます。
目次 |
構文
テンプレートパラメータパック( エイリアステンプレート 、 クラステンプレート 、 変数テンプレート (C++14以降) 、 コンセプト (C++20以降) および 関数テンプレート のパラメータリストに現れる)
型
...
パック名
(オプション)
|
(1) | ||||||||
typename
|
class
...
パック名
(オプション)
|
(2) | ||||||||
型制約
...
パック名
(オプション)
|
(3) | (C++20以降) | |||||||
template
<
パラメータリスト
>
class
...
パック名
(オプション)
|
(4) | (C++17まで) | |||||||
template
<
パラメータリスト
>
typename
|
class
...
パック名
(オプション)
|
(4) | (C++17以降) | |||||||
関数パラメータパック( declarator の一種で、可変引数関数テンプレートの関数パラメータリストに現れる)
pack-name
...
pack-param-name
(オプション)
|
(5) | ||||||||
|
非パラメータパックの構文については、 lambda init-capture pack および structured binding pack (C++26以降) を参照してください。 |
(C++20以降) |
パック展開(テンプレートの本体に現れる)
pattern
...
|
(6) | ||||||||
|
3)
オプションの名前付き
制約付き
型テンプレートパラメータパック
|
(C++20以降) |
pattern
のリストに展開されます。パターンには少なくとも1つのパックが含まれている必要があります。
説明
可変引数クラステンプレートは任意の数のテンプレート引数でインスタンス化できます:
template<class... Types> struct Tuple {}; Tuple<> t0; // Typesには引数が含まれない Tuple<int> t1; // Typesには1つの引数が含まれる: int Tuple<int, float> t2; // Typesには2つの引数が含まれる: intとfloat Tuple<0> t3; // エラー: 0は型ではない
可変引数関数テンプレートは任意の数の関数引数で呼び出すことができます(テンプレート引数は template argument deduction によって推論されます):
template<class... Types> void f(Types... args); f(); // OK: args には引数が含まれない f(1); // OK: args には1つの引数が含まれる: int f(2, 1.0); // OK: args には2つの引数が含まれる: int と double
プライマリクラステンプレートでは、テンプレートパラメータパックはテンプレートパラメータリストの最終パラメータでなければなりません。関数テンプレートでは、後続のすべてのパラメータが関数引数から推論可能であるか、デフォルト引数を持つ場合、テンプレートパラメータパックはリスト内でより早く現れることができます:
template<typename U, typename... Ts> // OK: Uの推論が可能 struct valid; // template<typename... Ts, typename U> // エラー: Ts...が末尾にない // struct Invalid; template<typename... Ts, typename U, typename=void> void valid(U, Ts...); // OK: Uの推論が可能 // void valid(Ts..., U); // 使用不可: この位置ではTs...は非推論コンテキスト valid(1.0, 1, 2, 3); // OK: Uをdouble、Tsを{int, int, int}として推論
可変個引数テンプレートのすべての有効な特殊化が空のテンプレートパラメータパックを必要とする場合、プログラムは不適格(診断不要)です。
パック展開
省略記号に続くパターンで、少なくとも1つのパックの名前が少なくとも1回現れるものは、 展開 され、パックの名前がパック内の各要素で順次置き換えられた、ゼロ個以上のパターンのインスタンス化になります。 alignment specifiers のインスタンス化はスペースで区切られ、その他のインスタンス化はカンマで区切られます。
template<class... Us> void f(Us... pargs) {} template<class... Ts> void g(Ts... args) { f(&args...); // 「&args...」はパック展開 // 「&args」はそのパターン } g(1, 0.2, "a"); // Ts... args は int E1, double E2, const char* E3 に展開される // &args... は &E1, &E2, &E3 に展開される // Us... pargs は int* E1, double* E2, const char** E3 に展開される
2つのパックの名前が同じパターンに現れる場合、それらは同時に展開され、同じ長さでなければなりません:
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>... はパック展開 // Pair<Args1, Args2> はパターン }; }; typedef zip<short, int>::with<unsigned short, unsigned>::type T1; // Pair<Args1, Args2>... は以下のように展開される // Pair<short, unsigned short>, Pair<int, unsigned int> // T1 は Tuple<Pair<short, unsigned short>, Pair<int, unsigned>> // typedef zip<short>::with<unsigned short, unsigned>::type T2; // エラー: パック展開に異なる長さのパックが含まれています
パック展開が別のパック展開内にネストされている場合、最も内側のパック展開内に現れるパックはそれによって展開され、外側のパック展開内で言及されているが最も内側のものには含まれていない別のパックが存在しなければなりません:
template<class... Args> void g(Args... args) { f(const_cast<const Args*>(&args)...); // const_cast<const Args*>(&args) はパターンであり、2つのパック // (Args と args) を同時に展開する f(h(args...) + args...); // ネストされたパック展開: // 内側のパック展開は "args..." であり、最初に展開される // 外側のパック展開は h(E1, E2, E3) + args... であり、次に展開される // (h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3 として) }
パック内の要素数がゼロ(空パック)の場合、パック展開のインスタンス化は、たとえパック展開を完全に省略した場合に不正な形式となるか構文の曖昧性が生じるような状況であっても、包含する構文の解釈を変更しません。インスタンス化は空のリストを生成します。
template<class... Bases> struct X : Bases... { }; template<class... Args> void f(Args... args) { X<Args...> x(args...); } template void f<>(); // OK、X<>は基底クラスを持たない // xは値初期化されたX<>型の変数
展開軌跡
展開が行われる場所に応じて、結果のカンマ区切り(または アライメント指定子 ではスペース区切り)のリストは、異なる種類のリストとなります:関数引数リスト、メンバ初期化子リスト、属性リストなど。以下はすべての許可されたコンテキストのリストです:
関数引数リスト
パック展開は関数呼び出し演算子の括弧内に現れることがあり、その場合、省略記号の左側にある最大の式または 波括弧で囲まれた初期化子リスト が展開されるパターンとなります:
f(args...); // f(E1, E2, E3) に展開される f(&args...); // f(&E1, &E2, &E3) に展開される f(n, ++args...); // f(n, ++E1, ++E2, ++E3) に展開される f(++args..., n); // 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...); // 以下のように展開される // f(h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
括弧付き初期化子
パック展開は、 direct initializer の括弧内、 function-style cast およびその他の文脈( member initializer 、 new-expression など)内に現れることがあります。その場合のルールは、前述の関数呼び出し式のルールと同一です:
Class c1(&args...); // Class::Class(&E1, &E2, &E3) を呼び出す Class c2 = Class(n, ++args...); // Class::Class(n, ++E1, ++E2, ++E3) を呼び出す ::new((void *)p) U(std::forward<Args>(args)...) // std::allocator::allocate
波括弧で囲まれた初期化子
波括弧で囲まれた初期化子リストでは、パック展開も同様に現れます:
template<typename... Ts> void func(Ts... args) { const int size = sizeof...(args) + 2; int res[size] = {1, args..., 2}; // 初期化リストは順序を保証するため、パックの各要素に対して順序通りに関数を呼び出すために使用できる: int dummy[sizeof...(Ts)] = {(std::cout << args, 0)...}; }
テンプレート引数リスト
パック展開は、テンプレートが展開に一致するパラメータを持つ限り、テンプレート引数リストの任意の場所で使用できます:
template<class A, class B, class... C> void func(A arg1, B arg2, C... arg3) { container<A, B, C...> t1; // container<A, B, E1, E2, E3> に展開される container<C..., A, B> t2; // container<E1, E2, E3, A, B> に展開される container<A, C..., B> t3; // container<A, E1, E2, E3, B> に展開される }
関数パラメータリスト
関数のパラメータリストにおいて、省略記号がパラメータ宣言内に現れる場合(それが関数パラメータパックを命名するかどうかに関わらず(例:
Args
...
args
))、そのパラメータ宣言はパターンとなります:
template<typename... Ts> void f(Ts...) {} f('a', 1); // Ts... は void f(char, int) に展開される f(0.1); // Ts... は 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] は // const char (&)[2], int(&)[1] に展開される
注意: パターン
Ts (&...arr)[N]
では、他のすべてのパック展開とは異なり、省略記号は最後の要素ではなく最も内側の要素です。
注記:
Ts (&...)[N]
は許可されていません。C++11の文法では括弧で囲まれた省略記号に名前が必要なためです:
CWG issue 1488
。
テンプレートパラメータリスト
パック展開はテンプレートパラメータリスト内に現れることがあります:
template<typename... T> struct value_holder { template<T... Values> // 定数テンプレートパラメータリストに展開されます struct apply {}; // 例: <int, char, int(&)[5]> };
基底指定子とメンバ初期化子リスト
パック展開は、 クラス宣言 における基底クラスのリストを指定することができます。 通常、これはコンストラクタがこれらの基底クラスのコンストラクタを呼び出すために、 メンバ初期化子リスト で パック展開を使用する必要があることも意味します:
template<class... Mixins> class X : public Mixins... { public: X(const Mixins&... mixins) : Mixins(mixins)... {} };
Lambdaキャプチャ
パック展開は lambda 式のキャプチャ節に現れることがあります:
template<class... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
sizeof... 演算子
sizeof...
演算子もパック展開として分類されます:
template<class... Types> struct count { static const std::size_t value = sizeof...(Types); };
動的例外仕様動的例外仕様 における例外のリストは、パック展開も可能です: template<class... X> void func(int arg) throw(X...) { // ... throw different Xs in different situations } |
(C++17まで) |
アライメント指定子
pack展開は、キーワード
alignas
で使用される型リストと式リストの両方で許可されます。インスタンス化はスペースで区切られます:
template<class... T> struct Align { alignas(T...) unsigned char buffer[128]; }; Align<int, short> a; // 展開後のアライメント指定子は // alignas(int) alignas(short) // (間にカンマはない)
属性リスト
pack展開は、属性の仕様で許可されている場合、 attributes のリスト内で許可されます。例:
template<int... args> [[vendor::attr(args)...]] void* f();
Fold式fold式 において、パターンは未展開のパックを含まない完全な部分式です。 using宣言using宣言 では、宣言子のリストに省略記号が現れることがあります。これはテンプレートパラメータパックから派生する際に有用です: template<typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // OK: B::g and D::g introduced |
(C++17以降) |
Pack indexingパックインデックス では、パック展開には展開されていないパックの後に省略記号と添字が続きます。パックインデックス式のパターンは identifier であり、パックインデックス指定子のパターンは 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); Friend declarationsクラスの フレンド宣言 では、各型指定子の後に省略記号を付けることができます: 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 expanded constraints畳み込み展開された制約 では、パターンはその畳み込み展開された制約の制約です。 畳み込み展開された制約はインスタンス化されません。 |
(C++26以降) |
注記
|
このセクションは不完全です
理由:部分特殊化や個々の要素にアクセスする他の方法についての簡単な説明? 再帰対数対フォールド式などのショートカットについて言及 |
| 機能テスト マクロ | 値 | 標準 | 機能 |
|---|---|---|---|
__cpp_variadic_templates
|
200704L
|
(C++11) | 可変引数テンプレート |
__cpp_pack_indexing
|
202311L
|
(C++26) | パックインデックス |
例
以下の例は、
std::printf
と同様の関数を定義し、フォーマット文字列内の
%
文字の各出現を値で置換します。
最初のオーバーロードは、フォーマット文字列のみが渡され、パラメータ展開がない場合に呼び出されます。
2番目のオーバーロードは、引数の先頭用に個別のテンプレートパラメータとパラメータパックを含みます。これにより、再帰呼び出しがパラメータの末尾のみを渡し、空になるまで継続することが可能になります。
Targs
はテンプレートパラメータパックであり、
Fargs
は関数パラメータパックです。
#include <iostream> void tprintf(const char* format) // 基底関数 { std::cout << format; { template<typename T, typename... Targs> void tprintf(const char* format, T value, Targs... Fargs) // 再帰的可変引数関数 { for (; *format != '\0'; format++) { if (*format == '%') { std::cout << value; tprintf(format + 1, Fargs...); // 再帰呼び出し return; } std::cout << *format; } } int main() { tprintf("% world% %\n", "Hello", '!', 123); }
出力:
Hello world! 123
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用バージョン | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 1533 | C++11 | メンバーのメンバー初期化子でパック展開が発生する可能性があった | 許可されない |
| CWG 2717 | C++11 | アライメント指定子のインスタンス化はカンマ区切りであった | スペース区切りである |
関連項目
| Function template | 関数のファミリーを定義する |
| Class template | クラスのファミリーを定義する |
sizeof...
|
パック内の要素数を問い合わせる |
| C-style variadic function | 可変個の引数を受け取る |
| Preprocessor macros | 同様に可変個引数を取ることができる |
| Fold expression | 二項演算子を使用してパックを畳み込む |
| Pack indexing | 指定されたインデックスのパック要素にアクセスする |