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

Boost.Proto

C++

Boost.ProtoのGetting Startedを簡単に訳して解説してみる。



Boost.ProtoはExpression Templateのライブラリを作るためのライブラリです。
簡単な例を挙げるとこんな感じで使います。

#include <iostream>
#include <boost/proto/proto.hpp>

using namespace boost::proto;

terminal<std::ostream&>::type cout_ = { std::cout };

template <class Expr>
void evaluate(const Expr& expr)
{
    eval(expr, default_context());
}

int main()
{
    evaluate(cout_ << "Hello World\n");
}

このプログラムの「cout_ << "Hello World\n"」の時点ではまだstd::coutは評価されず、
式の情報だけが保存されます。
boost::proto::eval()にその式を渡すことで評価されます。


式の型は、Expression Templateの複雑な型になってるので仕方なく関数テンプレートに渡していますが、C++0xのautoを使用すれば以下のように書けます。

const auto& expr = cout_ << "Hello World\n";

eval(expr, default_context());


boost::proto::display_expr()に式を渡せばExpression Treeを表示することもできます。

display_expr(cout_ << "Hello World\n");
shift_left(
    terminal(78506BCC)
  , terminal(Hello World
)
)

これだとツリーっぽく見えないですね…。
「<<」で何個か連結すればそれっぽく見えます。

display_expr(cout_ << "Hello" << "," << " World\n");
shift_left(
    shift_left(
        shift_left(
            terminal(78506BCC)
          , terminal(Hello)
        )
      , terminal(,)
    )
  , terminal( World
)
)


Boost.Protoを使用すれば、Boost.Lambdaのようなプレースホルダによる計算をユーザーが簡単に定義することができます。
まず、プレースホルダは以下のように定義します。

template <int I>
struct placeholder {};

boost::proto::terminal<placeholder<0> >::type const _1 = {{}};
boost::proto::terminal<placeholder<1> >::type const _2 = {{}};

これを用意すれば、以下のような式が書けます。

(_1 * _2 / _2) + 100


次に、プレースホルダを評価するコンテキストを用意します。

class calculator_context
  : public boost::proto::callable_context<const calculator_context>
{
    std::vector<double> args_; // 値を置き換えるためのプレースホルダ
public:
    typedef double result_type;

    template <int I>
    double operator()(boost::proto::tag::terminal, placeholder<I>) const
    {
        return args_[I];
    }

    void add(double value)
    {
        args_.push_back(value);
    }
};

これで準備ができました。以下のように使用します。

calculator_context context;
context.add(3); // _1の値は3
context.add(5); // _2の値は5

double result = boost::proto::eval(_1 * _2 / _2 + 100, context);
std::cout << result << std::endl; // 103が出力される


また、「_1 * _2 / _2 + 100」をBoost.Lambdaのように関数オブジェクトとして使用するには
先ほど作成したcalculator_contextをラップした以下のようなクラスを用意します。

template <typename Expr>
struct calculator;

struct calculator_domain
  : boost::proto::domain<boost::proto::generator<calculator> > {};

template <typename Expr>
struct calculator
    : boost::proto::extends<Expr, calculator<Expr>, calculator_domain>
{
    typedef
        boost::proto::extends<Expr, calculator<Expr>, calculator_domain>
    base_type;

    calculator(Expr const &expr = Expr())
      : base_type(expr)
    {}

    typedef double result_type;

    double operator()(double a1 = 0, double a2 = 0) const
    {
        calculator_context context;
        context.add(a1);
        context.add(a2);
        return eval(*this, context);
    }
};

あとは、プレースホルダを用意するときにこのcalculatorクラスでラップした型にしてあげれば

calculator<boost::proto::terminal<placeholder<0> >::type> const _1;
calculator<boost::proto::terminal<placeholder<1> >::type> const _2;

以下のように使用できます。

double result = (_1 * _2 / _2 + 100)(3.0f, 5.0f);
std::cout << result << std::endl; // 103


Boost.Protoを使用すれば、Expression Templateのライブラリを作ろうと思ったときにサクっと書けそうです。