パック (C++11 以降)
パックは、以下のいずれかを定義する C++ エンティティです。
- パラメーターパック
- テンプレートパラメーターパック
- 関数パラメーターパック
| (C++20以降) |
| (C++26以降) |
テンプレートパラメーターパックは、0 個以上のテンプレート引数 (非型、型、またはテンプレート) を受け入れるテンプレートパラメーターです。関数パラメーターパックは、0 個以上の関数引数を受け入れる関数パラメーターです。
|
ラムダの init-capture パックは、その初期化子のパック展開の各要素に対して init-capture を導入するラムダキャプチャです。 |
(C++20以降) |
|
構造化バインディングパックは、構造化バインディング宣言において、0 個以上の構造化バインディングを導入する識別子です。 |
(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以降) | |||||||
関数パラメーターパック (宣言子の一種で、可変引数関数テンプレートの関数パラメーターリストに現れる)
パック名 ... パックパラメーター名 (任意) |
(5) | ||||||||
|
非パラメーターパックの構文については、ラムダの init-capture パック および 構造化バインディングパック(C++26 以降) を参照してください。 |
(C++20以降) |
パック展開 (テンプレートの本体に現れる)
パターン ... |
(6) | ||||||||
|
3) オプションの識別子を持つ制約付き型テンプレートパラメーターパック |
(C++20以降) |
pattern のリストに展開されます。パターンには少なくとも1つのパックが含まれている必要があります。[編集] 説明
可変引数クラステンプレートは、任意の数のテンプレート引数でインスタンス化できます。
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
可変引数関数テンプレートは、任意の数の関数引数で呼び出すことができます (テンプレート引数はテンプレート引数推論によって推論されます)。
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
プライマリークラステンプレートでは、テンプレートパラメーターパックはテンプレートパラメーターリストの最後のパラメーターでなければなりません。関数テンプレートでは、後続のすべてのパラメーターが関数引数から推論できるか、またはデフォルト引数を持つ場合に限り、テンプレートパラメーターパックはリストのより早い位置に現れてもよいです。
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}
可変引数テンプレートの有効なすべての特殊化が空のテンプレートパラメーターパックを要求する場合、プログラムは整形式ではありません (診断は不要です)。
[編集] パック展開
省略記号が続くパターンで、少なくとも1つのパックの名前が少なくとも1回現れるものは、パターンの0個以上のインスタンスに展開されます。このとき、パックの名前はパックの各要素によって順に置き換えられます。アライメント指定子のインスタンス化はスペース区切り、その他のインスタンス化はコンマ区切りです。
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
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>... 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
パック展開が別のパック展開の内部にネストされている場合、最も内側のパック展開内のパックはそれによって展開され、囲むパック展開には別のパックが記述されている必要がありますが、最も内側のパック展開には記述されていません。
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) }
パック内の要素数がゼロ (空のパック) の場合、パック展開のインスタンス化は、囲む構造の構文解釈を変更しません。これは、パック展開を完全に省略すると、整形式でなくなるか、構文の曖昧さが発生する場合でも同様です。インスタンス化は空のリストを生成します。
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
[編集] 展開箇所
展開が行われる場所によって、結果のコンマ区切り (またはアライメント指定子の場合はスペース区切り) のリストは、関数パラメーターリスト、メンバー初期化子リスト、属性リストなど、異なる種類のリストになります。以下は、許可されているすべてのコンテキストのリストです。
[編集] 関数引数リスト
パック展開は関数呼び出し演算子の括弧内に現れることがあります。この場合、省略記号の左側の最も大きな式または波括弧で囲まれた初期化子リストが展開されるパターンになります。
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)
[編集] 括弧で囲まれた初期化子
パック展開は、直接初期化子、関数スタイルキャスト、およびその他のコンテキスト (メンバー初期化子、new式など) の括弧内に現れることがあります。この場合の規則は、上記の関数呼び出し式の規則と同一です。
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
[編集] 波括弧で囲まれた初期化子
波括弧で囲まれた初期化子リストにもパック展開が現れることがあります。
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)...}; }
[編集] テンプレート引数リスト
パック展開は、テンプレートが展開に一致するパラメーターを持つ場合、テンプレート引数リストのどこでも使用できます。
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> }
[編集] 関数パラメーターリスト
関数パラメーターリストにおいて、パラメーター宣言に省略記号が現れる場合 (関数パラメーターパックに名前を付ける場合 (例: Args... args) またはそうでない場合)、そのパラメーター宣言がパターンになります。
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]
注: パターン Ts (&...arr)[N] では、省略記号は最も内側の要素であり、他のすべてのパック展開のように最後の要素ではありません。
注: Ts (&...)[N] は許可されていません。C++11 の文法では、括弧で囲まれた省略記号に名前があることを要求しているためです: CWG issue 1488。
[編集] テンプレートパラメーターリスト
パック展開はテンプレートパラメーターリストに現れることがあります。
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]> };
[編集] 基底指定子とメンバー初期化子リスト
パック展開は、クラス宣言における基底クラスのリストを指定することがあります。通常、これはコンストラクタがこれらの基底のコンストラクタを呼び出すために、メンバー初期化子リストでパック展開を使用する必要があることも意味します。
template<class... Mixins> class X : public Mixins... { public: X(const Mixins&... mixins) : Mixins(mixins)... {} };
[編集] ラムダキャプチャ
パック展開は、ラムダ式のキャプチャ句に現れることがあります。
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まで) |
[編集] アライメント指定子
パック展開は、キーワードalignasが使用する型のリストと式のリストの両方で許可されています。インスタンス化はスペース区切りです。
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)
[編集] 属性リスト
属性の仕様で許可されている場合、属性のリストでパック展開が許可されます。たとえば、
template<int... args> [[vendor::attr(args)...]] void* f();
畳み込み式畳み込み式では、パターンは未展開のパックを含まないサブ式全体です。 Using宣言using宣言では、宣言子のリストに省略記号が現れることがあります。これはテンプレートパラメーターパックから派生する場合に便利です。 template<typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // OK: B::g and D::g introduced |
(C++17以降) |
パックインデックスパックインデックスでは、パック展開には未展開のパックとそれに続く省略記号および添字が含まれます。パックインデックス式のパターンは識別子であり、パックインデックス指定子のパターンはtypedef名です。 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); フレンド宣言クラスのフレンド宣言では、各型指定子の後に省略記号を続けることができます。 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>> 畳み込み展開制約畳み込み展開制約では、パターンはその畳み込み展開制約の制約です。 畳み込み展開制約はインスタンス化されません。 |
(C++26以降) |
[編集] 注記
| このセクションは未完成です 理由: 部分特殊化や個々の要素にアクセスする他の方法について少し説明するべきでしょうか?再帰 vs 対数 vs 畳み込み式のようなショートカットについて言及するべきでしょうか? |
| 機能テストマクロ | 値 | 規格 | 機能 |
|---|---|---|---|
__cpp_variadic_templates |
200704L |
(C++11) | 可変引数テンプレート |
__cpp_pack_indexing |
202311L |
(C++26) | パックインデックス |
[編集] 例
以下の例では、std::printf に似た関数を定義しています。この関数は、書式指定文字列内の文字 % の出現箇所を値で置き換えます。
最初のオーバーロードは、書式指定文字列のみが渡され、パラメーター展開がない場合に呼び出されます。
2番目のオーバーロードは、引数の先頭に対する個別のテンプレートパラメーターとパラメーターパックを含んでいます。これにより、再帰呼び出しがパラメーターの残りの部分のみを渡すことができ、最終的に空になるまで繰り返されます。
Targs はテンプレートパラメーターパック、Fargs は関数パラメーターパックです。
#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); }
出力
Hello world! 123
[編集] 欠陥報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 1533 | C++11 | メンバーに対するメンバー初期化子にパック展開が発生する可能性がある | 許可されなくなった。 |
| CWG 2717 | C++11 | アライメント指定子のインスタンス化はコンマ区切りだった | それらはスペース区切りです |
[編集] 関連項目
| 関数テンプレート | 関数のファミリを定義する |
| クラステンプレート | クラスのファミリを定義する |
sizeof...
|
パック内の要素数を問い合わせる |
| Cスタイルの可変引数関数 | 可変個の引数を取る |
| プリプロセッサマクロ | 可変引数にもなりうる |
| 畳み込み式 | 二項演算子でパックを縮小する |
| パックインデックス | 指定されたインデックスでパックの要素にアクセスする |