この記事は、C++ Advent Calendar 2015の7日目の記事です。


標準ライブラリに、std::forward_as_tupleというものがあることを最近知りました。

これは便利だと思いました。何がどう便利かというと、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

foldaccumulateなど、畳み込み処理の関数もあります。次のプログラムの関数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は対応していなかったような)。ありがとうございます。

スポンサード リンク

この記事のカテゴリ

  • ⇒ 可変長引数をBoost.Fusionで処理する