0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C++】再帰もフォールドも怖くない!可変長引数テンプレート超入門

Last updated at Posted at 2025-10-10

C++にて開発を行っていると、Args...va_listなど、可変長引数に関してのコードに遭遇すると思いますが、個人的には仕様がわかりづらく、初見では頭を悩まされる事と思います。
本記事では、可変長引数について、できる限り優しく詳細に解説します。

可変長引数とは

そもそも可変長引数とは、その名の通り、宣言時に引数の数を定めなくてよいもののことです。
この仕組みを使用して工夫をすることで、汎用的な関数を作成できるなどのメリットがあります。

コード例

コード例1

まずは、Cスタイルの古い方法で可変長引数を作ってみたいと思います。

variable_arguments1.cpp
#include <cstdarg>//va_start, va_arg, va_endを使用するために必要
#include <iostream>
//-------------------------------------------------------------
// 可変長引数を受け取って、整数をすべて表示する関数
//! @param count 引数の個数
//! @param ... 可変長引数(整数)
//-------------------------------------------------------------
void printAll(int count, ...) {
	va_list args;                        // 引数リストを扱うための変数
	va_start(args, count);               // 可変長引数の処理を開始(countの次の引数から)

	for (int i = 0; i < count; ++i) {
		int value = va_arg(args, int);   // 次の引数をint型として取得
		std::cout << value << " ";       // 取得した値を表示
	}

	va_end(args);                        // 引数リストの処理を終了
	std::cout << std::endl;              // 改行
}

//メイン関数
int main() {
	printAll(3, 10, 20, 30); // 出力: 10 20 30
	//終了
	return 0;
	system("pause");
}
result
10 20 30

大前提として、可変長引数...は、関数内で直接アクセスすることが不可能です。
そのため、va_list型の変数を宣言し、間接的に可変長引数にアクセスを行うのです。

	va_list args;                        // 引数リストを扱うための変数

宣言した時点ではva_listに可変長引数が入っておらず、va_start(va_list型の変数,可変長引数の数)とすることで、va_start()の第一引数にとったva_list型の変数を通して、可変長引数にアクセスすることが可能になります。

ここで可変長引数の数と、int型の値が一致していないと、バグが起こる可能性があります。

	va_start(args, count);               // 可変長引数の処理を開始(countの次の引数から)

可変長引数にアクセスする際にはva_arg(va_list型の変数,引き出す可変長引数の型)とすることで、可変長引数に、左から順にアクセスすることができます。

引き出す可変長引数の型の値が一致していないと、バグが起こる可能性があります。

		int value = va_arg(args, int);   // 次の引数をint型として取得

この一連の処理は、内部的にメモリ領域を使用しているため、解放処理が必要となります。
va_end(va_list型の変数)とすることで、お片づけをしています。

va_end()を呼び出さないと、メモリリークやスタック破損など、深刻なバグが発生する場合があります。

	va_end(args);                        // 引数リストの処理を終了

コード例2

次に、C++11以降の機能を使った方法を紹介したいと思います。
テンプレートと再帰関数の仕組みを使用して、自作のprint関数を作成してみます。

variable_arguments2.cpp
#include <iostream>
//-------------------------------------------------------------
// 終了時の関数
//! @note 引数なしの関数をオーバーロード
//-------------------------------------------------------------
void print() {
	std::cout << std::endl;// 改行のみ表示
}
//-------------------------------------------------------------
// 再帰的分解で一つずつ出力:最初の引数を処理し、残りを再帰的に渡す
//! @tparam T 最初の引数の型
//! @tparam Rest 残りの引数の型(0個以上)
//! @param first 最初の引数
//! @param rest 残りの引数(0個以上)
//-------------------------------------------------------------
template<typename T, typename... Rest>
void print(T first, Rest... rest) {
	std::cout << first << " ";	// 最初の引数を表示
	print(rest...);				// 残りの引数を再帰的に処理
}

//メイン関数
int main() {
	print(1, 2.5, "Hello", 'A', 42);//自作のプリント関数で表示させてみる
	//終了
	return 0;
	system("pause");
}
result
1 2.5 Hello A 42

この方法ではprint()をテンプレートの再帰関数にし、可変長引数を一つづつ取り出しています。
一度の関数呼び出しで出力する引数は第一引数のみで、可変長引数の内容は、再帰処理の次に回すため、先ほどのva_startのような、メモリ領域をいじるような処理が不要となります。
テンプレートで<typename... テンプレート引数の型名>のように宣言することで、可変長引数をテンプレートの型として持つことが可能となります。

//-------------------------------------------------------------
// 再帰的分解で一つずつ出力:最初の引数を処理し、残りを再帰的に渡す
//! @tparam T 最初の引数の型
//! @tparam Rest 残りの引数の型(0個以上)
//! @param first 最初の引数
//! @param rest 残りの引数(0個以上)
//-------------------------------------------------------------
template<typename T, typename... Rest>
void print(T first, Rest... rest) {
	std::cout << first << " ";	// 最初の引数を表示
	print(rest...);				// 残りの引数を再帰的に処理
}

処理を行うたびに可変長引数のが一つづつ減っていくため、当然再帰処理のどこかのタイミングで、可変長引数の数が0になるタイミングがあります。
その際に、再帰関数と同名で引数なしのオーバーロードした関数が必要となります。

//-------------------------------------------------------------
// 終了時の関数
//! @note 引数なしの関数をオーバーロード
//-------------------------------------------------------------
void print() {
	std::cout << std::endl;// 改行のみ表示
}

コード例3

最後は、C++17以降で使用可能な、フォールド式という仕組みを使用した方法になります。
フォールド式の構文が((expr op args), ...)となり、argsの中身を、左から順に一括で処理を行います。

variable_arguments3.cpp
#include <iostream>
//-------------------------------------------------------------
// フォールド式を使った可変長引数テンプレートで自作のprint関数
//! @template Args 可変長引数
//! @param args [in] 可変長引数
//-------------------------------------------------------------
template<typename... Args>
void print(Args... args) {
	((std::cout << args << " "), ...);	// 左フォールド式
	std::cout << std::endl;				//改行
}

//メイン関数
int main() {
	print(1, 2.5, "Hello", 'A', 42);	//自作のプリント関数で表示させてみる
	//終了
	return 0;
	system("pause");
}
result
1 2.5 Hello A 42

フォールド式を使用することで、再帰処理などを行わずとも、一括での出力が可能です。

//-------------------------------------------------------------
// フォールド式を使った可変長引数テンプレートで自作のprint関数
//! @template Args 可変長引数
//! @param args [in] 可変長引数
//-------------------------------------------------------------
template<typename... Args>
void print(Args... args) {
	((std::cout << args << " "), ...);	// 左フォールド式
	std::cout << std::endl;				//改行
}

総括

  • 自由に引数の数を決められる引数のことを可変長引数と呼ぶ。
  • 可変長引数を工夫して利用することで、汎用性の高い関数を作成することが可能。
  • va_listを使用することで、C言語ライクな可変長引数の実装が可能。
  • C++11からは、テンプレートと再帰処理を利用することで、va_listなど、メモリを触る処理を行わずとも、可変長引数の値にアクセスすることが可能。
  • C++17からは、フォールド式を利用することで、再帰処理を使わずとも、一括で可変長引数の処理が可能。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?