省略記号と可変個引数のテンプレートEllipsis and Variadic Templates

ここでは、C++ の可変個引数テンプレートを使用して省略記号 (...) を指定する方法を示します。This article shows how to use the ellipsis (...) with C++ variadic templates. 省略記号は、C および C++ で多くの用途に使用されてきました。The ellipsis has had many uses in C and C++. たとえば、関数の可変個引数リストなどです。These include variable argument lists for functions. C ランタイム ライブラリの printf() 関数は、最も一般的な例の 1 つです。The printf() function from the C Runtime Library is one of the most well-known examples.

可変個引数テンプレートは、任意の数の引数をサポートするクラスまたは関数テンプレートです。A variadic template is a class or function template that supports an arbitrary number of arguments. この機構はクラス テンプレートと関数テンプレートの両方に適用でき、それによって広範なタイプ セーフと非自明の機能や柔軟性が提供されるため、C++ ライブラリの開発者に特に役立ちます。This mechanism is especially useful to C++ library developers because you can apply it to both class templates and function templates, and thereby provide a wide range of type-safe and non-trivial functionality and flexibility.

構文Syntax

可変個引数テンプレートでは、省略記号が 2 とおりの方法で使用されます。An ellipsis is used in two ways by variadic templates. パラメーター名の左側には、パラメーターパックが示されます。パラメーター名の右側には、パラメーターパックが個別の名前で展開されます。To the left of the parameter name, it signifies a parameter pack, and to the right of the parameter name, it expands the parameter packs into separate names.

可変個引数テンプレートクラス定義の構文の基本的な例を次に示します。Here's a basic example of variadic template class definition syntax:

template<typename... Arguments> class classname;

パラメーター パックとその展開のいずれの場合でも、次の例に示すように、必要に応じて省略記号の前後に空白を追加できます。For both parameter packs and expansions, you can add whitespace around the ellipsis, based on your preference, as shown in these examples:

template<typename ...Arguments> class classname;

またはOr this:

template<typename ... Arguments> class classname;

この記事では、最初の例に示されている規則が使用されていることに注意してください (省略記号はに関連付けられてい typename ます)。Notice that this article uses the convention that's shown in the first example (the ellipsis is attached to typename).

前の例では、引数はパラメーターパックです。In the preceding examples, Arguments is a parameter pack. classname クラスには、次の例のように、可変個の引数を指定できます。The class classname can accept a variable number of arguments, as in these examples:

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
vtclass<long, std::vector<int>, std::string> vtinstance4;

可変個引数テンプレート クラス定義を使用して、1 つ以上のパラメーターを要求することもできます。By using a variadic template class definition, you can also require at least one parameter:

template <typename First, typename... Rest> class classname;

可変個引数 template 関数の構文の基本的な例を次に示します。Here's a basic example of variadic template function syntax:

template <typename... Arguments> returntype functionname(Arguments... args);

次のセクション「可変個引数テンプレートについて」で説明しているように、引数parameter pack が使用できるように展開されます。The Arguments parameter pack is then expanded for use, as shown in the next section, Understanding variadic templates.

可変個引数テンプレート関数の構文は、他の形式を使用することもできます。いくつかの例を次に示します。Other forms of variadic template function syntax are possible—including, but not limited to, these examples:

template <typename... Arguments> returntype functionname(Arguments&... args);
template <typename... Arguments> returntype functionname(Arguments&&... args);
template <typename... Arguments> returntype functionname(Arguments*... args);

のような指定子 const も使用できます。Specifiers like const are also allowed:

template <typename... Arguments> returntype functionname(const Arguments&... args);

可変個引数テンプレート クラス定義を使用して、1 つ以上のパラメーターを要求する関数を作成することができます。As with variadic template class definitions, you can make functions that require at least one parameter:

template <typename First, typename... Rest> returntype functionname(const First& first, const Rest&... args);

可変個引数テンプレートでは、sizeof...() 演算子が使用されます (従来の sizeof() 演算子とは無関係です)。Variadic templates use the sizeof...() operator (unrelated to the older sizeof() operator):

template<typename... Arguments>
void tfunc(const Arguments&... args)
{
    constexpr auto numargs{ sizeof...(Arguments) };

    X xobj[numargs]; // array of some previously defined type X

    helper_func(xobj, args...);
}

省略記号の配置の詳細More about ellipsis placement

以前にこの記事で、パラメーター パックとその展開を定義する省略記号の配置について、パラメーター名の左に配置すると、パラメーター パックを意味し、パラメーター名の右に配置すると、パラメーター パックの展開を意味する (パラメーター パックが個々の名前に展開される) と説明しました。Previously, this article described ellipsis placement that defines parameter packs and expansions as "to the left of the parameter name, it signifies a parameter pack, and to the right of the parameter name, it expands the parameter packs into separate names". これは技術的には事実ですが、コードへの変換の点では混乱することがあります。This is technically true but can be confusing in translation to code. 次の点を考慮してください。Consider:

  • テンプレートパラメーターリスト () では template <parameter-list>typename... テンプレートパラメーターパックが導入されています。In a template-parameter-list (template <parameter-list>), typename... introduces a template parameter pack.

  • パラメーター宣言句 () では func(parameter-list) 、"最上位" の省略記号に関数パラメーターパックが導入され、省略記号の配置が重要になります。In a parameter-declaration-clause (func(parameter-list)), a "top-level" ellipsis introduces a function parameter pack, and the ellipsis positioning is important:

    // v1 is NOT a function parameter pack:
    template <typename... Types> void func1(std::vector<Types...> v1);
    
    // v2 IS a function parameter pack:
    template <typename... Types> void func2(std::vector<Types>... v2);
    
  • 省略記号がパラメーター名の直後にある場合、パラメーター パックの展開を示します。Where the ellipsis appears immediately after a parameter name, you have a parameter pack expansion.

Example

可変個引数テンプレート関数の機構について説明するには、これを使用して printf の機能の一部を変更するのが最も適切です。A good way to illustrate the variadic template function mechanism is to use it in a re-write of some of the functionality of printf:

#include <iostream>

using namespace std;

void print() {
    cout << endl;
}

template <typename T> void print(const T& t) {
    cout << t << endl;
}

template <typename First, typename... Rest> void print(const First& first, const Rest&... rest) {
    cout << first << ", ";
    print(rest...); // recursive call using pack expansion syntax
}

int main()
{
    print(); // calls first overload, outputting only a newline
    print(1); // calls second overload

    // these call the third overload, the variadic template,
    // which uses recursion as needed.
    print(10, 20);
    print(100, 200, 300);
    print("first", 2, "third", 3.14159);
}

出力Output

1
10, 20
100, 200, 300
first, 2, third, 3.14159

注意

可変個引数テンプレート関数を組み込むほとんどの実装では、何らかの形式の再帰が使用されますが、従来の再帰とは少し異なります。Most implementations that incorporate variadic template functions use recursion of some form, but it's slightly different from traditional recursion. 従来の再帰には、同じシグネチャを使用して関数自体を呼び出す関数が含まれています。Traditional recursion involves a function calling itself by using the same signature. (オーバーロードまたはテンプレート化されている場合もありますが、毎回同じ署名が選択されます)。可変個引数の再帰では、異なる (ほとんどの場合には減少する) 引数の数を使用して可変個引数関数テンプレートを呼び出すことができます。これにより、毎回異なる署名がスタンプされます。(It may be overloaded or templated, but the same signature is chosen each time.) Variadic recursion involves calling a variadic function template by using differing (almost always decreasing) numbers of arguments, and thereby stamping out a different signature every time. "基本ケース" が必要なのは同じですが、再帰の性質は異なります。A "base case" is still required, but the nature of the recursion is different.