この記事は、C++ Advent Calendar 2015の7日目の記事です。
標準ライブラリに、std::forward_as_tupleというものがあることを最近知りました。
- forward_as_tuple – cpprefjp C++日本語リファレンス
- 可変長のパラメータパックのまま扱うのがだるい時は tuple で扱うのどうだろう – sorry, uninuplemented:
これは便利だと思いました。何がどう便利かというと、std::tupleにすれば今イチ扱いづらい可変長引数をBoost.Fusionの世界に持って行けます。Boost.Fusionは、タプル的なデータ構造に対して様々な操作が用意されているライブラリです。filter, map, foldがあるので(実際にはmapではなくtransformという名前)、何でもできると言って過言ではありません(過言です)。
というわけで、今日は可変長テンプレートとBoost.Fusionの話です。これから3つサンプルコードを紹介します。なお、必ずしもstd::forward_as_tupleは使っていません。
例1: for_each
仮引数すべてに対して順に処理したければ、boost::fusion::for_eachを使います。
次のプログラムの関数Printは、実引数の値を順にstd::coutへ出力するものです。
#include <iostream> #include <tuple> #include <boost/fusion/adapted/std_tuple.hpp> #include <boost/fusion/algorithm/iteration/for_each.hpp> template<typename... Args> void Print(Args&&... args) { boost::fusion::for_each(std::tie(args...), [](const auto& x) { std::cout << x << std::endl; }); } int main() { Print(1, "abc", 2); } |
例2: fold
foldやaccumulateなど、畳み込み処理の関数もあります。次のプログラムの関数ToStringは、実引数をlexical_castで文字列化しつつ、連結するというものです。
#include <iostream> #include <string> #include <tuple> #include <boost/fusion/adapted/std_tuple.hpp> #include <boost/fusion/algorithm/iteration/fold.hpp> #include <boost/lexical_cast.hpp> template<typename... Args> std::string ToString(Args&&... args) { return boost::fusion::fold( std::tie(args...), std::string(), [](std::string s, const auto& x) { return std::move(s) + boost::lexical_cast<std::string>(x); }); } int main() { std::cout << ToString(1.5, '_', 3) << std::endl; } |
このプログラムの出力は1.5_3となります。
なお、右から処理する関数reverse_foldもあります。
例3: 他の可変長引数テンプレートに渡す
std::forward_as_tupleでタプルにしたものを再び可変長テンプレートに渡すことも可能です。それにはboost::fusion::invokeを使います。
boost::fusion::invokeは、関数オブジェクトとFusionシーケンス(タプルなど)の2つの実引数を受け取る関数です。このシーケンスが展開されて関数オブジェクトに渡されます。
たとえば、boost::fusion::invoke(f, std::make_pair(1, 2))
はf(1, 2)
のようにf
が呼び出されます。
次のプログラムの関数PrintReverseは、実引数をboost::fusion::reverseで逆順にしてからPrint(例1で登場したものと同じ)に渡すというものです。
#include <iostream> #include <string> #include <tuple> #include <boost/fusion/adapted/std_tuple.hpp> #include <boost/fusion/algorithm/iteration/for_each.hpp> #include <boost/fusion/algorithm/transformation/reverse.hpp> #include <boost/fusion/functional/invocation/invoke.hpp> #include <boost/lexical_cast.hpp> template<typename... Args> void Print(Args&&... args) { boost::fusion::for_each(std::forward_as_tuple(args...), [](const auto& x) { std::cout << x << std::endl; }); } template<typename... Args> void PrintReverse(Args&&... args) { boost::fusion::invoke( [](auto&&... args) { Print(args...); }, boost::fusion::reverse(std::forward_as_tuple(std::forward<Args>(args)...))); } int main() { PrintReverse(1, 2, 3, 'A', 'B', 'C'); } |
むすび
可変長テンプレートの引数パックの扱い方として、std::tupleでBoost.Fusionの世界に持ち込もうという話でした。困ったときにはこういう方法もいかがでしょうか。
思えば私、可変長テンプレートがまだなかった昔から、代替手段としてBoost.Fusionを使ってきました。ならば、可変長引数がある今、組み合わせて使うのは当然の道理です。
ところで、そろそろBoost.Hanaの足音が聞こえてきていますね。Boost.Hanaでもこういうことはできるようになるのではないかと思います。
2015年12月8日追記:@Flast_ROさんに、std::forward_as_tupleの使い方が適切でないという指摘をいただきました。そこで、最初の2つはstd::tieに変更し、、最後の1つはstd::forwardを挿入してPerfect forwarding化しました(けど、Boost.Fusionは対応していなかったような)。ありがとうございます。
- https://twitter.com/Flast_RO/status/673872652528783360
- https://twitter.com/Flast_RO/status/673873391321944065
- https://twitter.com/Flast_RO/status/673873714170105856
- https://twitter.com/Flast_RO/status/673874236964933632
スポンサード リンク |
この記事のカテゴリ
- C++ ⇒ 可変長引数をBoost.Fusionで処理する