本文へスキップ
BecomeCoder

C++コース · 第19章 並行処理とC++20 · レッスン76

可変長テンプレート ― 任意個の引数を型安全に

ブラウザで完結

導入

「合計を求める関数」を、引数が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::vectoremplace_back なども、内部では可変長テンプレートで作られています。

手元の環境(g++ / clang など)では、std::filesystem<filesystem>)というファイル・ディレクトリ操作の標準APIも使えます。std::filesystem::path でパスを表現し、exists(存在確認)・create_directory(作成)・directory_iterator(一覧取得)などで、OSに依存しない形でファイルシステムを扱えます。ブラウザの学習環境にはファイルシステムが無いため実行できませんが、実務のC++でファイルを扱う際によく使う標準APIとして覚えておいてください。

やってみよう

右のコードを実行し、sumprintAll に渡す引数の個数や型を変えて挙動を確かめましょう。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;

実際に動かしてみよう

下のエディタにC++を書いて「コンパイル & 実行」を押すと、ブラウザ内で本物のC++コンパイラ(clang + libc++)がその場でコンパイルして実行し、出力が表示されます。std::string・vector・クラス・テンプレート・STL などモダンC++がそのまま動きます。本文の例を書き換えて試しながら進めましょう(初回だけコンパイラの読み込みに時間がかかります。以降はキャッシュされます。この回は標準入力(cin)は使わず、値はコードに直接書きます)。

C/C++ — ブラウザ内で本物のコンパイラで実行

ブラウザ内で本物のC/C++コンパイラ(clang + libc++ / WebAssembly)を動かします。構造体・malloc/free・std::string・vector・クラス・テンプレート・STL などがそのまま実行できます(初回のみコンパイラの読み込みに時間がかかります。以降はブラウザにキャッシュされます)。
スクロールして表示された時点でも自動で読み込まれます。