Boost.Lambda/Phoenix ラムダ式でメンバ変数を扱う

Boost.Lambdaを使用したことがあるユーザーなら、_1.memberができなくて残念だと誰もが思ったことでしょう。これができなくて泣く泣くメンバをbindしてました。
しかし、どうやらoperator->*()というメンバポインタ用の演算子が用意されているらしく、これを使えば以下のように書けるようです。

_1 ->* &T::data_member
_1 ->* &T::member_func

今回はデータメンバのみ検証しました。
まずはBoost.Lambda:

#include <iostream>
#include <vector>
#include <boost/lambda/lambda.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/range/adaptor/indirected.hpp>
#include <boost/assign/list_of.hpp>

struct X {
    int value;
    X(int value) : value(value) {}
};

int main()
{
    using boost::lambda::_1;

    std::vector<X*> v = boost::assign::list_of
        (new X(3))
        (new X(1))
        (new X(4))
    ;

    boost::for_each(v, _1 ->* &X::value += 1);

    boost::for_each(v | boost::adaptors::indirected,
        [](const X& x) { std::cout << x.value << std::endl; });
} // プロセス終了時に解放されるので明示的なdeleteはしてない
4
2
5

Boost.Lambdaでメンバポインタを扱う際の注意事項は、「ポインタでなければいけない」という点です。スマートポインタは使用できません。
boost::is_pointer等、いくつかを特殊化すればできそうな気もしましたがダメそうです。
なので、Boost.Lambdaの方はアルゴリズムと組み合わせて使用する場合に、生のポインタのコンテナを必要とするという点で、あまり有用とは言えません。


次に、Boost.Phoenix。
こちらは、ポインタもスマートポインタもいちおう扱えます。

#include <iostream>
#include <vector>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/range/adaptor/indirected.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/make_shared.hpp>

struct X {
    int value;
    X(int value) : value(value) {}
};

void disp(const X& x) { std::cout << x.value; }

int main()
{
    std::vector<boost::shared_ptr<X> > vs = boost::assign::list_of
        (boost::make_shared<X>(3))
        (boost::make_shared<X>(1))
        (boost::make_shared<X>(4))
    ;

    std::vector<X*> vp = boost::assign::list_of
        (new X(3))
        (new X(1))
        (new X(4))
    ;

    using boost::phoenix::arg_names::_1;
    boost::for_each(vs, _1 ->* &X::value += 1);
    boost::for_each(vp, _1 ->* &X::value += 1);

    boost::for_each(vs | boost::adaptors::indirected, disp); std::cout << std::endl;
    boost::for_each(vp | boost::adaptors::indirected, disp); std::cout << std::endl;
}
425
425

しかし、Phoenixが標準サポートしているのは、以下の型のみです:

  • ポインタ
  • boost::shared_ptr
  • boost::scoped_ptr
  • std::auto_ptr

つまり、std::shared_ptr, std::unique_ptr, イテレータといった型を扱うことができません。
これらの型をひとつひとつ特殊化していくのはめんどくさいので、これらを判定している関数、メタ関数のプライマリテンプレートを定義して統一的に扱えるようにしましょう:

#include <iostream>
#include <vector>
#include <memory>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/range/adaptor/indirected.hpp>
#include <boost/assign/list_of.hpp>

namespace boost { namespace phoenix { namespace meta {
    template <class T>
    T declid();

    template <class T>
    struct pointed_type { typedef decltype(*declid<T>()) type; };
}}}

namespace boost {
    template <class Pointee>
    auto get_pointer(const Pointee& p) -> decltype(&*p) { return &*p; }
}

struct X {
    int value;
    X(int value) : value(value) {}
};

void disp(const X& x) { std::cout << x.value; }

int main()
{
    std::vector<std::shared_ptr<X> > vs = boost::assign::list_of
        (std::make_shared<X>(3))
        (std::make_shared<X>(1))
        (std::make_shared<X>(4))
    ;

    using boost::phoenix::arg_names::_1;
    boost::for_each(vs, _1 ->* &X::value += 1);

    boost::for_each(vs | boost::adaptors::indirected, disp); std::cout << std::endl;
}
425

これでstd::shared_ptr等、Phoenix非サポートのスマートポインタと、イテレータが扱えるようになりました。
イテレータに対応したことによって、実は値を格納するコンテナも扱えるようになります。
pstade::oven::outdirectedを使用します:

#include <iostream>
#include <vector>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/assign/list_of.hpp>
#include <pstade/oven/outdirected.hpp>

namespace boost { namespace phoenix { namespace meta {
    template <class T>
    T declid();

    template <class T>
    struct pointed_type { typedef decltype(*declid<T>()) type; };
}}}

namespace boost {
    template <class Pointee>
    auto get_pointer(const Pointee& p) -> decltype(&*p) { return &*p; }
}

struct X {
    int value;
    X(int value) : value(value) {}
};

void disp(const X& x) { std::cout << x.value; }

int main()
{
    std::vector<X> v = boost::assign::list_of
        (X(3))
        (X(1))
        (X(4))
    ;

    using boost::phoenix::arg_names::_1;
    boost::for_each(v | pstade::oven::outdirected, _1 ->* &X::value += 1);

    boost::for_each(v, disp); std::cout << std::endl;
}
425

少々前準備が必要なのと、使用する際にoutdirected Rangeアダプタを適用する必要はありますが、これで十分有用になりました。


ちなみに、operator->*()を値に対応するよう書き換えたいと思う方もいるでしょうから書いておくと、スマートポインタを値とポインタ、どちらと見なすかが曖昧なので、operator->*()演算子だけで値とポインタ両方扱えるようにする、というのはやめたほうがよさそうです。
値用の別な演算子を定義することをオススメします。演算子の選択肢はカンマ演算子くらいしか残っていませんが。


追記 2010/11/10 11:53:

&_1 ->* T::member

で問題なく値も扱えました。以下検証コードです:


Boost.Lambda

#include <iostream>
#include <vector>
#include <boost/lambda/lambda.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/assign/list_of.hpp>

struct X {
    int value;
    X(int value) : value(value) {}
};

void disp(const X& x) { std::cout << x.value; }

int main()
{
    std::vector<X> v = boost::assign::list_of
        (X(3))
        (X(1))
        (X(4))
    ;

    using boost::lambda::_1;
    boost::for_each(v, &_1 ->* &X::value += 1);

    boost::for_each(v, disp); std::cout << std::endl;
}
425


Boost.Phoenix

#include <iostream>
#include <vector>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/assign/list_of.hpp>

struct X {
    int value;
    X(int value) : value(value) {}
};

void disp(const X& x) { std::cout << x.value; }

int main()
{
    std::vector<X> v = boost::assign::list_of
        (X(3))
        (X(1))
        (X(4))
    ;

    using boost::phoenix::arg_names::_1;
    boost::for_each(v, &_1 ->* &X::value += 1);

    boost::for_each(v, disp); std::cout << std::endl;
}
425