パラメータパック(C++11以上)
テンプレートパラメータパックは、任意個の��ンプレート引数 (非型、型、またはテンプレート) を受理するテンプレート引数です。 関数パラメータパックは、任意個の関数引数を受理する関数引数です。
少なくとも1つのパラメータパックを持つテンプレートは可変長引数テンプレートと言います。
目次 |
[編集] 構文
テンプレートパラメータパック (エイリアステンプレート、クラステンプレート、変数テンプレートおよび関数テンプレートのテンプレート引数リストで使用できます):
type ... Args(オプション)
|
(1) | ||||||||
typename|class ... Args(オプション)
|
(2) | ||||||||
template < parameter-list > typename(C++17)|class ... Args(オプション)
|
(3) | ||||||||
関数パラメータパック (宣言子の形式のひとつ) (可変長引数関数テンプレートの関数引数リストで使用できます):
Args ... args(オプション)
|
(4) | ||||||||
パラメータパックの展開 (可変長引数テンプレートの本体で使用できます):
pattern ...
|
(5) | ||||||||
pattern
のコンマ区切りのリストに展開されます。 パターンには少なくとも1つのパラメータパックが含まれていなければなりません。[編集] 説明
可変長引数クラステンプレートは、任意個のテンプレート引数を用いて実体化できます。
template<class ... Types> struct Tuple {}; Tuple<> t0; // OK、 Types は引数を持ちません。 Tuple<int> t1; // OK、 Types は1個の引数 (int) を持ちます。 Tuple<int, float> t2; // OK、 Types は2個の引数 (int, float) を持ちます。 Tuple<0> error; // エラー、 0 は型ではありません。
可変長引数関数テンプレートは、任意個の関数引数を用いて呼ぶことができます。 テンプレート引数はテンプレートの実引数推定によって推定されます。
template<class ... Types> void f(Types ... args); f(); // OK、 Types は引数を持ちません。 f(1); // OK、 Types は1個の引数 (int) を持ちます。 f(2, 1.0); // OK、 Types は2個の引数 (int, double) を持ちます。
プライマリクラステンプレートでは、テンプレートパラメータパックは、テンプレート仮引数リストの最後の引数でなければなりません。 関数テンプレートでは、テンプレートパラメータパックは、その後の引数すべてが実引数から推定できるかデフォルト引数を持つならば、最後でなくても構いません。
template<typename... Ts, typename U> struct Invalid; // エラー、 Ts は最後でなければなりません。 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つのパラメータパックの名前が含まれていなければなりません) に省略記号が続いたものは、そのパターンの0個以上のコンマ区切りの実体化に展開されます。 パラメータパックの名前はパック内の各要素に順番通りに置き換えられます。
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 に展開されます。
1つのパターン内に複数のパラメータパックの名前が現れた場合は、それらは同時に展開されます (同じ長さでなければなりません)。
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... です。 まずこれが例えば E1, E2, E3 のように展開されます。 // 外側のパック展開は h(E1, E2, E3) + args... です。 次にこれが例えば // h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + 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 T1*>(&E1), const_cast<const T2*>(&E2), const_cast<const T3*>(&E3)) に展開されます。 f(h(args...) + args...); // f(h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3) に展開されます。
形式的には、関数呼び出し式の式リストは初期化子リストに分類され、パターンは初期化子節 (代入式または波括弧初期化子リストのいずれか) です。
[編集] 丸括弧初期化子
直接初期化、関数スタイルのキャスト、およびその他の文脈 (メンバ初期化子、 new 式、など) の丸括弧内で、パック展開を使用できます。 この場合のルールは前節の関数呼び出し式に対するルールと同一です。
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... は char, int に展開されます。 f(0.1); // Ts... は 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]
の場合、省略記号は最も内側の要素です (他のパック展開のように最後の要素ではありません)。
ノート: C++11 の文法上、括弧で囲まれた省略記号は名前を持つ必要があるため、 Ts (&...)[N]
は使用できません (CWG #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)... { } };
[編集] ラムダキャプチャ
ラムダ式のキャプチャ節でパック展開を使用できます。
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...) { // ... 状況に応じて異なる X を投げる ... } |
(C++17未満) |
[編集] アライメント指定子
キーワード alignas で使用される型のリストおよび式のリストの両方でパック展開を使用できます。
[編集] 属性リスト
属性のリストで [[attributes...]] のようにパック展開を使用できます。 例: void [[attributes...]] function()
畳み込み式畳み込み式では、パターンは未展開のパラメータパックを含まない部分式全体です。 using 宣言using 宣言では、宣言子のリストに省略記号が現れても構いません。 これはパラメータパックから派生するときに便利です。 template <typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // OK、 B::g と D::g が両方とも導入されます。 |
(C++17以上) |
[編集] ノート
This section is incomplete Reason: a few words about partial specializations and other ways to access individual elements? Mention recursion vs logarithmic vs shortcuts such as fold expressions |
[編集] 例
#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); return 0; }
出力:
Hello world! 123
上記の例は std::printf に似た関数を定義します。 書式文字列内の文字 % の各出現を値に置き換えます。
1つめのオーバーロードは書式文字列だけが渡され引数展開がないときに呼ばれます。
2つめのオーバーロードは最初の引数のために分離されたテンプレート引数とパラメータパックを取ります。 これにより空になるまで残りの引数を渡して再帰呼び出しすることが可能になります。
Targs
はテンプレートパラメータパックで、 Fargs
は関数パラメータパックです。
[編集] 関連項目
関数テンプレート | |
クラステンプレート | |
sizeof... | パラメータパック内の要素数を問い合わせます |
C スタイルの可変長引数関数 | |
プリプロセッサマクロ | も可変長引数を取れます |
畳み込み式 |