読者です 読者をやめる 読者になる 読者になる

Boost Fusion Library - Fusion Sequence

C++

C++ Advent Calendar jp 2010の記事です。
今回は、Boost Fusion Libraryを紹介します。


Boost Fusion Libraryは、タプルのデータ構造とアルゴリズムを提供するライブラリです。
Fusionでは、2つの重要な概念を与えます。


1つは、タプルを異なる型のリストと見なしイテレートする、Fusion Sequenceという概念。
もうひとつは、タプルを名前のないユーザー定義型、ユーザー定義型を名前ありタプルと見なし、それらを相互運用可能にするという概念です。


まず、基本的な使い方を見ていきましょう:

#include <iostream>
#include <string>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/for_each.hpp>

namespace fusion = boost::fusion;

struct disp {
    template <class T>
    void operator()(const T& x) const
    {
        std::cout << x << std::endl;
    }
};

int main()
{
    const fusion::vector<int, char, std::string> v(1, 'a', "Hello");
    fusion::for_each(v, disp());
}
1
a
Hello

fusion::vectorというのは、各要素にランダムアクセス可能なタプルです。
これに対し、fusion::for_eachといったSTLライクなアルゴリズムを適用することで、タプルの要素をイテレートし、タプルをリストのように扱うことができます。


タプルに対するアルゴリズムに使用する関数オブジェクトには毎回異なる型が渡されることになるため、多相的にするか、タプルに含まれる要素の型全てのオーバーロードを用意する必要があります。
Boost.Lambdaは多相的なので、アルゴリズムに渡すことができます。

#include <iostream>
#include <string>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/lambda/lambda.hpp>

namespace fusion = boost::fusion;
using boost::lambda::_1;

int main()
{
    const fusion::vector<int, char, std::string> v(1, 'a', "Hello");
    fusion::for_each(v, std::cout << _1 << '\n');
}

ここではデフォルトで使用するべきFusion Sequenceであるfusion::vectorのみを紹介しますが、他にも要素に単方向アクセス可能なfusion::list、双方向にアクセス可能なfusion::dequeがあります。


Fusion Sequenceでは、タプルをイテレートするというアイデアに加えて、そのタプルに対する操作結果の型がどうなるか、という問題にも着手しています。タプルの要素の型を変換する処理を考えましょう。

#include <string>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/transform.hpp>
#include <boost/lexical_cast.hpp>

namespace fusion = boost::fusion;

struct to_string {
    typedef std::string result_type;

    template <class T>
    std::string operator()(const T& x) const
    {
        return boost::lexical_cast<std::string>(x);
    }
};

int main()
{
    const fusion::vector<int, char, std::string> v(1, 'a', "Hello");

    ??? result = fusion::transform(v, to_string());
}

ここでは、fusion::transformによってタプルの要素を文字列に変換していますが、変換結果のタプルの型はどう書けばいいでしょうか。
以下のように、愚直に変換結果の型を書くのもひとつの手ですが、

const fusion::vector<std::string, std::string, std::string> result =
    fusion::transform(v, to_string());

これは人間が頭の中でアルゴリズムを実行した結果を書き下しているので、好ましい書き方ではありません。


Fusionでは、fusion::for_eachやfusion::transformのようなタプルの「値」に対する操作のほかに、タプルの「型」に対する操作も提供されているため、以下のように書くことができます。

#include <string>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/transform.hpp>
#include <boost/lexical_cast.hpp>

namespace fusion = boost::fusion;

struct to_string {
    typedef std::string result_type;

    template <class T>
    std::string operator()(const T& x) const
    {
        return boost::lexical_cast<std::string>(x);
    }
};

int main()
{
    typedef fusion::vector<int, char, std::string> vector_t;
    const vector_t v(1, 'a', "Hello");

    const fusion::result_of::transform<const vector_t, to_string>::type result =
        fusion::transform(v, to_string());
}

値に対するtransformの結果を、型に対しても同じくtransformを適用した型で受け取っています。
Fusionでは、boost::fusion名前空間に「値」に対するアルゴリズム、boost::fusion::result_of名前空間に「型」に対する同名のアルゴリズムが用意されています。
このように値と型を一様に扱えること、動的と静的の融合こそが、Fusionという名前の意味するところです。


これまでの説明で、Boost.FusionがBoost.Tupleを完全に置き換えて使用できるベターTupleであることがわかると思います。それに加えて、Boost.TupleはFusion Sequenceとして扱えるため、TupleをFusionで書き直すことなくFusionのアルゴリズムをTupleに適用することもできます。

#include <iostream>
#include <string>
#include <boost/tuple/tuple.hpp>
#include <boost/fusion/include/boost_tuple.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/lambda/lambda.hpp>

using boost::lambda::_1;
namespace fusion = boost::fusion;

int main()
{
    const boost::tuple<int, char, std::string> t(1, 'a', "Hello");
    fusion::for_each(t, std::cout << _1 << '\n');
}
1
a
Hello

Boost.TupleをFusion Sequenceとして扱えるようにするのは、をインクルードするだけでできます。


さてさて、Fusion Sequenceとして扱えるようにできるのはBoost.Tupleだけなのか。もちろんそんなことはありません。Fusion Sequenceのさらに便利なところは、ユーザー定義型を少量のコードによってFusion Sequenceにアダプトする機構がある、というところです。
Personというユーザー定義型をFusion Sequenceにアダプトしてみましょう:

#include <iostream>
#include <string>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/lambda/lambda.hpp>

using boost::lambda::_1;
namespace fusion = boost::fusion;

struct Person {
    int id;
    std::string name;
    std::string location;

    Person(int id_, const std::string& name_, const std::string& location_)
        : id(id_), name(name_), location(location_) {}
};


BOOST_FUSION_ADAPT_STRUCT(
    Person,
    (int, id)
    (std::string, name)
    (std::string, location)
)

int main()
{
    const Person p(3, "Akira", "Yokohama");
    fusion::for_each(p, std::cout << _1 << '\n');
}
3
Akira
Yokohama

BOOST_FUSION_ADAPT_STRUCTというマクロによって、ユーザー定義型をFusion Sequence、すなわちタプルと見なしてFusionの多くのアルゴリズムが適用できるようになるのです。


Fusion Sequenceがとても強力なことはわかりましたが、これはいったいどのようなケースで使用するのか。それを理解するためには、名前なしタプルと名前ありタプルという考えを導入する必要があります・・・が、これはまだ考えがまとまっていないので、続きは次回のBoost.勉強会で!


というわけで、C++ Advent Calendar jp 2010、3日目のid:ranhaさんにバトンタッチ。