LoginSignup
9
4

More than 5 years have passed since last update.

可変引数テンプレート関数

Last updated at Posted at 2017-11-13

前置き

参考文献1に出てきた可変引数テンプレートをいじってみました。
作業ログと覚え書きを合わせたような何かです。

基本形

基本形
template <class... Args>
void func(Args... args)
{
    // sizeof...で可変引数にいくつ変数を持っているか調べる
    std::cout << sizeof...(args) << std::endl;
    // argsはそのままでは使えない
    // std::cout << args << std::endl;    // compile error
}

int main()
{
    func("foo", "bar", "hoge", "huga");    // 4
    func();    // 0, ...Argsは引数無しでもOK
    return 0;
}

通常のテンプレート関数では<class T>とするところ、可変引数では<class... Ts>のように宣言します。呼び出す際は引数から型解決されるので、(自動的に解決可能なかぎり)テンプレート部分を明示しなくても大丈夫です。
...は変数名の前につく場合と後につく場合があります。前につく場合は複数の引数を1つの可変引数にまとめます(pack)。一方、後の場合は可変引数を元の引数に展開する(unpack)作用があります。参考文献2によると可変引数は仮想的な型になっているそうで、このままでは通常の関数などに渡せません。なので、unpackして元の複数の変数に戻してあげるわけです。

参考文献3から、可変引数テンプレートを使うと以下のようなことも可能です。

可変引数の展開
// argsを展開してprintf関数に渡す。
// printf関数に合わない型だとコンパイルエラーになる。
template<class... Args>
void func(Args... args)
{
    printf(args...);
}

// argsの中身で配列を初期化する。
// 配列に合わない型だとコンパイルエラーになる。
template<class... Args>
void func2(Args... args)
{
    int a[] = { args... };
}

個々の引数を扱う

せっかくたくさんの引数をもらっても、他の関数に丸投げするだけではおいしくありません。もちろん、テンプレート内で個々の引数を扱う方法があります。

可変引数の切り出し(誤)
// 可変引数から先頭の1つだけ取り出して処理する
template <class Head, class... Body>
void func(Head head, Body... body)
{
        // 先頭の引数だけ取り出す。ついでに残った可変引数の個数を調べる
    std::cout << head << "\t" << sizeof...(body) << "\n";
}

// funcを応用し、再帰呼び出しを使ってBodyから1つずつ変数を切り出していく
// 注意:このままでは動きません
template <class Head, class... Body>
void func2(Head head, Body... body)
{
        // 先頭の引数だけ取り出す。ついでに残った可変引数の個数を調べる
    std::cout << head << "\t" << sizeof...(body) << "\n";
    // 残りの引数を再起呼び出しに回す(コンパイルエラー)
    func2(body...);
}

再帰呼び出しをするたびに、可変引数Bodyから1つずつ引数が切り出されていきます。関数型言語に似ていますね。
ただしコメントにも書いたように、func2このままでは動きません。コンパイルエラーになります。
可変引数...Bodyの部分は引数が無くてもテンプレートにマッチします。しかし、Headのほうは何かしら引数を与えないとマッチできません。再帰呼び出しを繰り返して引数が尽きたとき、Headにあたる引数がないためコンパイルエラーになる、といった寸法です。
ちなみに、func2の再起にif(sizeof...(body)>0)のような小細工を挟んでもやっぱりエラーになります。通常の(実行時の)ifではなく、コンパイル時に分岐するifを使えば回避できそうですが、浅学のためそれはまた後日。

上のfunc2は、たとえるならフィボナッチ数列でN=0を分岐し忘れたような状態です。ということはつまり、引数が尽きたときに呼び出す別関数を用意してあげれば解決できます。

可変引数の切り出し(正)
// 再帰呼び出しの終わり、Headが無くなったときのために別関数を用意する
void func2()
{
    std::cout << "no args func is called\n" << std::endl;
}

// 先の例と同じ
template <class Head, class... Body>
void func2(Head head, Body... body)
{
    std::cout << head << "\t" << sizeof...(body) << "\n";
    // 今度はうまくいく
    func2(body...);
}

可変引数が残っているうちは関数テンプレートのfunc2が呼ばれて、引数が無くなったところで通常の関数func2が呼ばれて再起が終了します。
ちなみに、static変数を使って関数の呼び出し回数をカウントすると、どれだけ再起しても呼び出し回数は1のままです。上記のfunc2は再起のたびにHeadが切り出されて、毎回異なるBodyで呼び出されます。テンプレート関数は与えられた型ごとに関数の実体を生成するため、異なるBodyの再起呼び出しからは異なるfunc2がそのつど生成されるのです。

最後に呼び出されるfunc2を可変引数ではないテンプレート関数にすることも可能です。

可変引数の切り出し_再起の終端をテンプレート関数化
// 関数テンプレートにする
template<class T>
void func2(T head)
{
    std::cout << head << "\t" << std::endl;
}

// 先の例と同じ
template <class Head, class... Body>
void func2(Head head, Body... body)
{
    std::cout << head << "\t" << sizeof...(body) << "\n";
    // これもうまくいく
    func2(body...);
}

こちらは可変引数が残り1つになったところで上の方のfunc2が呼ばれます。
引数1つでfunc2を呼び出した場合、<class T>Bodyが空な<class Head, class...Body>の両方にマッチしますが、マッチングの優先順位から前者が実体化されます。

参考文献

  1. C++テンプレートテクニック 第2版
    • 読んでいる途中です。
  2. (C++0x)可変長テンプレート引数は再帰でおいしくいただきましょう -- さかな前線
  3. 可変引数テンプレート - cpprefjp C++日本語リファレンス
9
4
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
9
4