Menu
Coddy logo textTech

可変引数テンプレート

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 67/104。

任意の型、任意の数の引数を受け取る関数が必要な場合はどうすればよいでしょうか?従来のテンプレートでは、必要な型パラメータの数を正確に指定する必要があります。可変引数テンプレート (Variadic templates) は、テンプレートが任意の数のテンプレート引数を受け取れるようにすることで、この問題を解決します。

構文は、...(省略記号)を使用してパラメータパックを作成します:

template <typename... Args>
void printAll(Args... args) {
    // Argsはテンプレートパラメータパックです
    // argsは関数パラメータパックです
}

引数を処理するには、通常、ベースケース(停止条件)を伴う再帰を使用します。パラメータパックは、引数がなくなるまで1つずつ展開されます:

// ベースケース:引数が残っていない場合
void print() {
    std::cout << std::endl;
}

// 再帰ケース:最初の引数を処理し、残りで再帰する
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
    std::cout << first << " ";
    print(rest...);  // 残りの引数を展開する
}

print(1, 3.14, "hello", 'x');  // 出力: 1 3.14 hello x

また、明示的な再帰を使わずに、より単純な操作を行うために、畳み込み式 (fold expressions) (C++17) を使用することもできます:

template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 畳み込み式: すべての引数を加算します
}

std::cout << sum(1, 2, 3, 4) << std::endl;  // 出力: 10

可変引数テンプレートは、std::make_uniquestd::tuplestd::function のような多くの標準ライブラリ機能を支えています。これらは、引数の型や数のあらゆる組み合わせに対応する、型安全な関数を可能にします。

challenge icon

チャレンジ

簡単

可変引数テンプレートを使用して、任意の数の引数を持つメッセージを処理する柔軟なロギングシステムを構築しましょう。パラメータパックを使用して、値の連結、引数のカウント、フォーマットされた出力の表示を行うユーティリティを作成します。

コードは2つのファイルに分けて構成します:

  • Variadic.h: ここで可変引数テンプレート関数を定義します。

    すべての引数をスペースで区切って表示し、最後に改行を出力する printAll 関数を作成してください。改行のみを出力するベースケースと、最初の引数とスペースを出力してから残りの引数で再帰する再帰ケースを用いた再帰的なアプローチを使用してください。

    渡された引数の数を返す countArgs 関数を作成してください。パラメータパックのサイズを取得するには sizeof... を使用します。

    すべての数値引数を加算してその結果を返す sum 関数を作成してください。+ 演算子を用いた畳み込み式(fold expression)を使用します。

    すべての数値引数を乗算してその結果を返す product 関数を作成してください。* 演算子を用いた畳み込み式(fold expression)を使用します。

  • main.cpp: 4つの入力(それぞれ別々の行)を読み取ります:
    1. 整数
    2. double型の数値
    3. 文字列
    4. 文字

    以下の手順で可変引数テンプレートの実演を行ってください:

    1. 4つすべての入力を指定して printAll を呼び出し、結果を出力する
    2. 整数、double型の数値、文字を指定して countArgs を呼び出したときの引数の数を出力する: Argument count: <count>
    3. 整数、double型の数値、および値 10 と 5 の合計を計算し、次のように出力する: Sum: <result>
    4. 整数と値 2 および 3 の積を計算し、次のように出力する: Product: <result>
    5. 文字列のみ(単一の引数)を指定して printAll を呼び出す
    6. 引数なしで printAll を呼び出す(改行のみが出力されるはずです)

例えば、入力が 52.5HelloX の場合:

5 2.5 Hello X 
Argument count: 3
Sum: 22.5
Product: 30
Hello 

printAll が、4つの引数から0個の引数まで、混合した型の任意の数の引数をどのように処理するかに注目してください。sum 関数と product 関数は、畳み込み式を使用して、明示的な再帰なしにすべての値をエレガントに結合します。countArgs 関数は、sizeof... がコンパイル時にパックのサイズをどのように取得するかを示しています。

チートシート

可変引数テンプレート(Variadic templates)を使用すると、...(省略記号)構文を使用してパラメータパックを作成し、テンプレートで任意の数のテンプレート引数を受け取ることができます。

template <typename... Args>
void printAll(Args... args) {
    // Args はテンプレートパラメータパックです
    // args は関数パラメータパックです
}

ベースケース(基底ケース)を用いた再帰を使用して引数を処理します:

// ベースケース:残りの引数がない場合
void print() {
    std::cout << std::endl;
}

// 再帰ケース:最初の引数を処理し、残りで再帰する
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
    std::cout << first << " ";
    print(rest...);  // 残りの引数を展開する
}

print(1, 3.14, "hello", 'x');  // 出力: 1 3.14 hello x

パラメータパック内の引数の数を取得するには sizeof... を使用します:

template <typename... Args>
size_t countArgs(Args... args) {
    return sizeof...(args);
}

明示的な再帰を使わずに、より単純な操作を行うには畳み込み式(fold expressions)(C++17)を使用します:

template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 畳み込み式:すべての引数を加算する
}

std::cout << sum(1, 2, 3, 4) << std::endl;  // 出力: 10

畳み込み式は、乗算などの他の演算子でも機能します:

template <typename... Args>
auto product(Args... args) {
    return (args * ...);  // すべての引数を乗算する
}

自分で試してみよう

#include <iostream>
#include <string>
#include "Variadic.h"

using namespace std;

int main() {
    // 入力を読み込む
    int intVal;
    double doubleVal;
    string strVal;
    char charVal;
    
    cin >> intVal;
    cin >> doubleVal;
    cin >> strVal;
    cin >> charVal;
    
    // TODO: 4つすべての引数を指定して printAll を呼び出す
    
    // TODO: intVal、doubleVal、charVal を指定して countArgs を使用し、引数の数を出力する
    // 形式: "Argument count: <count>"
    
    // TODO: intVal、doubleVal、10、5 の合計を計算して出力する
    // 形式: "Sum: <result>"
    
    // TODO: intVal、2、3 の積を計算して出力する
    // 形式: "Product: <result>"
    
    // TODO: 文字列のみを指定して printAll を呼び出す
    
    // TODO: 引数なしで printAll を呼び出す
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン