やりたかったこと
あまり使い道がなさそうではありますが、下記のような関数を作りたいと考えました。
- 可変長引数を取る
- 可変長引数の個数分の要素を持つstd::tupleを戻り値とする
- 戻り値の各要素は各引数を元に計算される
中身を省くと、下記のような形となる関数です。
template<class... Args>
std::tuple<Args...> func(Args... args) {
}
分かってしまえば簡単でしたが、実際に実装できるまで少し悩んでしまったので残しておきます。
実装のポイント
他の方法もあるかもしれませんが、私が実装した方法のポイントは下記2点です。
- 再帰呼び出しにより引数を1つずつ処理
- std::tuple_catを使用
それぞれ簡単に説明していきます。
再帰呼び出しにより引数を1つずつ処理
C++で可変長引数テンプレートを使用する場合の基本的な手法ではありますが、引数を全て処理するために再帰呼び出しを利用して引数を1つずつ処理していく方法を取りました。
例えば可変長引数を取り、それらを全て標準出力に出力するような関数は下記のように実装することができます。
// 引数が1つだけの場合に呼ばれる
template<class Head>
void print(const Head& head) {
std::cout << head << std::endl;
}
// 引数が2つ以上の場合に呼ばれる
template<class Head, class... Tail>
void print(const Head& head, const Tail&... tail) {
std::cout << head << std::endl;
print(tail...);
}
引数が1つだけの場合と引数が2つ以上の場合のそれぞれに対応できるように関数をオーバーロードします。引数が1つだけの場合は2行目から始まる関数のように引数を1つだけ取り、それを標準出力に出力するだけです。一方、引数が2つ以上の場合は8行目から始まる関数のように1つ目の引数の出力と2つ目以降の残りの引数に対して再帰呼び出しを実行します。
std::tuple_catを使用
引数の数と同数の要素を持つstd::tupleが戻り値として必要なので、引数1つずつを処理した結果を1つのstd::tupleに結合する必要があります。そこで、C++11で追加されているstd::tuple_catを使用します。std::tuple_catは引数に与えられた複数のstd::tupleを1つのstd::tupleに結合することができます。
前節で引数を1つずつ処理するようにしたので、要素数1つのstd::tupleと要素数が複数のstd::tupleをstd::tuple_catによって結合することにします。例として、すべての引数を二乗した結果をstd::tupleで返すpow2multi関数を示します。
// 引数が1つだけの場合に呼ばれる
template<class Head>
std::tuple<Head> pow2multi(Head head) {
// 1つの引数を二乗してtupleとして返す
return {head * head};
}
// 引数が2つ以上の場合に呼ばれる
template<class Head, class... Tail>
std::tuple<Head, Tail...> pow2multi(Head head, Tail... tail) {
// 2つ目以降の引数を再帰呼出しにより処理
auto t = pow2multi(tail...);
// 1つ目の引数を二乗した結果と2つ目以降の引数を二乗した結果を結合し返す
return std::tuple_cat(std::make_tuple(head * head), t);
}
これにより、可変長引数で引数の個数分の要素数を持つstd::tupleを返す関数を作成することが出来ました。戻り値がstd::tupleであるため、下記のように構造化束縛を使用することができます。
int main(int argc, char *argv[]) {
auto [a] = pow2multi(1.1);
std::cout << a << std::endl;
auto [b, c, d] = pow2multi(2, 3.1, 4);
std::cout << b << ", " << c << ", " << d << std::endl;
return 0;
}
まとめ
この記事では可変長引数の関数で引数と同数の要素数を持つstd::tupleを戻り値とする関数の作成方法について説明しました。ポイントは下記2点でした。
- 再帰呼び出しにより引数を1つずつ処理
- std::tuple_catを使用
出来てしまうと簡単ですが、同じような実装をしようとしている方の参考になれば幸いです。
コメント