導入
「合計を求める関数」を、引数が2個でも3個でも5個でも、そのたびに型ごとに書き直さずに済ませたい――そんな「任意個の引数」を型安全に受け取れるのが可変長テンプレート(variadic templates)です。
説明
template <typename... Args> の Args... は「0個以上の型をまとめたパック」を表します。関数の引数側では Args... args として「値のパック」を受け取ります。C++17 の「畳み込み式(fold expression)」(... + args) を使うと、パックの中身をまとめて演算できます。
#include <iostream>
using namespace std;
template <typename... Args> // Args... は「型のパック」(0個以上の型)
auto sum(Args... args) { // args... は「値のパック」
return (... + args); // 畳み込み式:args を + でつなげて合計する
}
int main() {
cout << sum(1, 2, 3) << endl; // 6
cout << sum(1, 2, 3, 4, 5) << endl; // 15
cout << sum(1.5, 2.5) << endl; // 4
return 0;
}
パックの要素数は sizeof...(args) で調べられます。また、渡す値の型が揃っている必要はありません。畳み込み式をカンマ演算子と組み合わせると、型がバラバラの引数を1つずつ処理できます(printf の型安全版が作れる、というのが動機です)。
#include <iostream>
using namespace std;
template <typename... Args>
void printAll(Args... args) {
((cout << args << " "), ...); // カンマ演算子の畳み込みで1つずつ出力
cout << endl;
}
int main() {
printAll(1, "abc", 3.5, 'x'); // 型がバラバラでも1つの関数で対応できる
return 0;
}
// 1 abc 3.5 x
flowchart LR call["sum(1, 2, 3, 4, 5)"] --> pack["Args... args<br/>= 5個の値のパック"] pack --> fold["(... + args)<br/>畳み込み式で + をたたむ"] fold --> result["15"]
引数の型・個数がその場で決まる(コンパイル時に確定する)ため、通常の関数と同じくらい高速に動きます。STL の std::make_tuple(第18章)や std::vector の emplace_back なども、内部では可変長テンプレートで作られています。
手元の環境(g++ / clang など)では、std::filesystem(<filesystem>)というファイル・ディレクトリ操作の標準APIも使えます。std::filesystem::path でパスを表現し、exists(存在確認)・create_directory(作成)・directory_iterator(一覧取得)などで、OSに依存しない形でファイルシステムを扱えます。ブラウザの学習環境にはファイルシステムが無いため実行できませんが、実務のC++でファイルを扱う際によく使う標準APIとして覚えておいてください。
やってみよう
右のコードを実行し、sum や printAll に渡す引数の個数や型を変えて挙動を確かめましょう。sizeof...(args) を cout してみるのもおすすめです。
演習
可変長テンプレート int countArgs(Args... args) を作り、sizeof...(args) を返すようにしてください。countArgs(10, 20, 30, 40) を呼び出し、その結果を表示してください(結果は 4)。
ヒント1を見る
template <typename... Args> int countArgs(Args... args) { return sizeof...(args); }
ヒント2を見る
cout << countArgs(10, 20, 30, 40) << endl;