パスワードを忘れた? アカウント作成
457273 journal

Yak!の日記: C++ で似非 closure

日記 by Yak!

きっと誰かがやっているはず、だとは思うのだが、ふと思いついたので C++ で似非 closure を作成してみた。

#include "closure.hpp"
#include <boost/test/auto_unit_test.hpp>
#include <boost/function.hpp>

typedef boost::function<int (void)> Func;
boost::tuple<Func, Func, Func> make(void)
{
    using yak::closure::captured;
    using yak::closure::capvar;
    using boost::lambda::constant;

    captured<int> value(0);

    Func incr = (capvar(value)++);
    Func cur = (-capvar(value));
    Func reset = (capvar(value) = constant(0));

    return make_tuple(incr, cur, reset);
}

BOOST_AUTO_TEST_CASE(test_counter2)
{
    Func incr1, cur1, reset1;
    Func incr2, cur2, reset2;
    boost::tie(incr1, cur1, reset1) = make();
    boost::tie(incr2, cur2, reset2) = make();

    BOOST_CHECK(incr1()  ==  0);
    BOOST_CHECK(cur1()   == -1);
    BOOST_CHECK(cur2()   ==  0);
    BOOST_CHECK(incr2()  ==  0);
    BOOST_CHECK(incr2()  ==  1);
    BOOST_CHECK(incr2()  ==  2);
    BOOST_CHECK(cur2()   == -3);
    BOOST_CHECK(cur1()   == -1);
}

参照する側でも capvar をつけなければならない点がいけてない感じだが、とりあえず現状の成果物ではこんなところである。実装について説明すると、似非 closure が消滅するまで補足された変数が消滅してもらっては困るということなので、うすうす想像はされているかもしれないが、内部では shared_ptr を使っている。

class captured
{
    boost::shared_ptr<T> ptr;
public:
    captured() : ptr(new T) {}
    explicit captured(const T& t) : ptr(new T(t)) {}
// ...
};

そして、boost::lambda::var に対して、参照ではなく値で引数を保持するよう変更して capvar を定義している(var では boost::lambda::identitiy)。

template <class T>
inline boost::lambda::lambda_functor<boost::lambda::identity<T> >
capvar(const T& t) { return boost::lambda::identity<T>(t); }

また、captured は T 型の変数として扱えるようにしたいため、変換関数を定義している。

class captured
{
// ...
    operator T& () { return *ptr; }
    operator const T& () const { return *ptr; }
};

この変換関数を経由して T に対する演算子が呼び出されることになる。しかし、これだけでは不十分である。一つは Boost.Lambda の返値型導出への対応である(ref. Extending return type deduction system)。

namespace boost { namespace lambda {

template<class Act, class A>
struct plain_return_type_1<unary_arithmetic_action<Act>, yak::closure::captured<A> > {
  typedef typename plain_return_type_1<unary_arithmetic_action<Act>, A>::type type;
};

//...
}}

基本的にはテンプレートパラメータの型の場合に帰着させている。いくつか特別に対応しているものもあるが、さらになぜか arithmetic_action に対しては plain_return_type_2 の定義だけだとうまくいかなかったので無理矢理内部で使用されているテンプレートに対して特殊化を与えている。どういうことなのか分かる方はいないだろうか?

namespace boost { namespace lambda { namespace detail {

template<class A, class B>
struct return_type_2_arithmetic_phase_2<yak::closure::captured<A>, B> {
  typedef typename return_type_2_arithmetic_phase_2<A, B>::type type;
};

template<class A, class B>
struct return_type_2_arithmetic_phase_2<A, yak::closure::captured<B> > {
  typedef typename return_type_2_arithmetic_phase_2<A, B>::type type;
};

}}}

もう一つは、operator= への対応である。14882:2003 13.3.1.2/4 によると

For the built-in assignment operators, conversions of the left operand are restricted as follows:
- no temporaries are introduced to hold the left operand, and
- no user-defined conversions are applied to the left operand to achieve a type match with the left-most parameter of a built-in candidate.

代入演算子の左辺に対してはユーザー定義変換関数は適用されない。ということで別に定義している。

class captured
{
//...
    captured& operator=(const T& t) { *ptr = t; return *this; }
//...
};

実際のファイルはこちら

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

長期的な見通しやビジョンはあえて持たないようにしてる -- Linus Torvalds

読み込み中...