C++ 関数の可変長引数を実現する方法【可変長引数テンプレート】

C++で可変長の引数を受け取りたい場合には、テンプレートを用いる必要があります。

目次

可変長引数テンプレート

以下の方法は最も簡潔な方法です。

template<class... A> int sum(A... args) {
  int s = 0;
  for (int i : std::initializer_list<int>{args...}) {
    s += i;
  }
  return s;
}
std::cout << sum();        // 0
std::cout << sum(1);       // 1
std::cout << sum(1, 2);    // 3
std::cout << sum(1, 2, 3); // 6

パラメータパックA... argsは配列ではないため、for文で処理することはできませんが、パラメータパックを初期化リスト(std::initializer_list)に展開することで、for文を用いた可変長引数の操作が可能になります。

再帰呼び出しによるパラメータパックの展開

以下の方法はより伝統的な方法です。

// ①
template<typename First>
int sum(First first) {
  return first;
}

// ②
template<typename First, typename... Rest>
int sum(First first, Rest... rest) {
  return first + sum(rest...);
}
int main() {
  int v = sum(1, 2, 3);
  std::cout << v; // 6
}

テンプレート関数が処理の肝です。

テンプレート内のtypename...という記述によって、可変長のテンプレートパラメータを受け取ることが可能になります。

また第二仮引数Rest... restの宣言によって、関数呼び出し側の第二引数以降に指定された実引数は全てこのrestの仮引数に集約されます。これらはパラメータパックと呼ばれます(Rest: テンプレート・パラメータパック、rest: 関数・パラメータパック)。

集約された複数の引数はrest...という記述で展開します(アンパック)。たとえば今回の呼び出し例では、sum(rest...)という記述はsum(2, 3)という形に展開されることになります。

よってsum(1, 2, 3);呼び出し時に、関数内ではreturn 1 + sum(2, 3);という展開が行われることになります。

sum(2, 3)は再帰呼出しとなるため、再度②の関数が適用され、先程と同じ手順による展開が行われます(return 2 + sum(3);)。そして最後にsum(3)が①の関数に適用される形となります。

これら一連の展開が完了すると、およそ以下のような実行コードが形成されることになります。

int sum(int c) { reutrn c; }
int sum(int b, int c) { reutrn b + sum(c); }
int sum(int a, int b, int c) { reutrn a + sum(b, c); }

int main() {
  int v = sum(1, 2, 3);
}

初期化子リスト

若干趣旨が異なりますが、初期化子リストを受け取るためのクラスstd::initializer_listを活用した方法もあります。

int sum(std::initializer_list<int> list) {
  int sum = 0;
  for (int i : list) sum += i;
  return sum;
}
void test() {
  int v = sum({1, 2, 3});
  std::cout << v; // 6
}

コードの役割を明確化したり、可読性を高めたりするための用途として活用するのもよいかもしれません。

return contain(extention, {"cpp", "cc", "cp"});

可変個数の実引数

C言語の仕組みを利用する方法もあります。以下の記事が参考になります。

【C言語】可変長引数を受け取る関数の作り方【stdarg.h】

広告