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

ここでは、C++ の可変個引数テンプレートを使用して省略記号 (...) を指定する方法を示します。 省略記号には、C と C++ で多くの用途がありました。 たとえば、関数の可変個引数リストなどです。 C ランタイム ライブラリの printf() 関数は、最も一般的な例の 1 つです。

可変個引数テンプレートは、任意の数の引数をサポートするクラス テンプレートまたは関数テンプレートです。 このメカニズムは、C++ ライブラリ開発者にとって特に役立ちます。クラス テンプレートと関数テンプレートの両方に適用できるため、さまざまなタイプ セーフで簡単でない機能と柔軟性を提供できます。

構文

可変個引数テンプレートでは、省略記号が 2 とおりの方法で使用されます。 パラメーター名の左側では、省略記号がパラメーター パックを示します。パラメーター名の右側では、パラメーター パックが別個の名前に展開されます。

可変数クラス テンプレート定義構文の基本的な例を次に示します。

template<typename... Arguments> class classname;

パラメーター パックと展開の両方で、次の例に示すように、ユーザー設定に基づいて省略記号の周囲に空白を追加できます。

template<typename ...Arguments> class classname;

または、次の例を実行します。

template<typename ... Arguments> class classname;

この記事では、最初の例に示されている規則を使用します (省略記号が添付されています typename)。

前の例では、 Arguments パラメーター パックです。 classname クラスには、次の例のように、可変個の引数を指定できます。

template<typename... Arguments> class vtclass;

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

可変数クラス テンプレート定義を使用すると、少なくとも 1 つのパラメーターを必要とすることもできます。

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

可変関数テンプレート構文の基本的な例を次に示します。

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

次の Arguments セクションに示すように、パラメーター パックは使用できるように拡張されます。

その他の形式の可変関数テンプレート構文を使用できます。これには、次の例が含まれますが、これらに限定されません。

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

const などの指定子も使用できます。

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

可変個引数テンプレート クラス定義を使用して、1 つ以上のパラメーターを要求する関数を作成することができます。

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

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

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...);
}

省略記号の配置の詳細

この記事では、この形式でパラメーター パックと展開を定義する省略記号の配置について説明しました。"パラメーター名の左側には、パラメーター パックを示し、パラメーター名の右側には、パラメーター パックを別の名前に展開します"。 技術的には当てはまりますが、コードへの翻訳では混乱を招く可能性があります。 以下を検討してください。

  • テンプレートのパラメーター リスト (template <parameter-list>) では、typename... はテンプレート パラメーター パックを定義します。

  • パラメーター宣言句 (func(parameter-list)) では、"トップレベル" の省略記号は、関数パラメーター パックを定義し、省略記号の位置は重要です。

    // 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);
    
  • 省略記号がパラメーター名の直後にある場合、パラメーター パックの展開を示します。

可変関数テンプレートメカニズムを示す良い方法は、次の機能 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);
}

出力

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

Note

可変関数テンプレートを組み込むほとんどの実装では何らかの形式の再帰が使用されますが、従来の再帰とは若干異なります。 従来の再帰には、同じシグネチャを使用して関数自体を呼び出す関数が含まれています。 (オーバーロードまたはテンプレート化することもできますが、毎回同じ署名が選択されます)。可変再帰では、異なる (ほぼ常に減少する) 数の引数を使用して可変関数テンプレートを呼び出し、毎回異なるシグネチャをスタンプアウトします。 "基本ケース" が必要なのは同じですが、再帰の性質は異なります。