Boost.Bind のプレースホルダーの仕組み
を調べるために、超簡単な Boost.Bind を作ってみた。
namespace my_boost { // arg template<int I> class arg { }; template<int I> bool operator==(arg<I> const &, arg<I> const &) { return true; } // type template<class T> class type { }; namespace _bi { // storage1 template<class A1> struct storage1 { explicit storage1(A1 a1) : a1_(a1) { } A1 a1_; }; template<int I> struct storage1<my_boost::arg<I> > { explicit storage1(my_boost::arg<I>) { } static my_boost::arg<I> a1_() { return my_boost::arg<I>(); } }; // storage2 template<class A1, class A2> struct storage2 : public storage1<A1> { storage2(A1 a1, A2 a2) : storage1<A1>(a1), a2_(a2) {} A2 a2_; }; template<class A1, int I> struct storage2<A1, my_boost::arg<I> > : public storage1<A1> { storage2(A1 a1, my_boost::arg<I>) : storage1<A1>(a1) { } static my_boost::arg<I> a2_() { return my_boost::arg<I>(); } }; // storage3 template<class A1, class A2, class A3> struct storage3 : public storage2<A1, A2> { storage3(A1 a1, A2 a2, A3 a3) : storage2<A1, A2>(a1, a2), a3_(a3) { } A3 a3_; }; template<class A1, class A2, int I> struct storage3<A1, A2, my_boost::arg<I> > : public storage2<A1, A2> { storage3(A1 a1, A2 a2, my_boost::arg<I>) : storage2<A1, A2>(a1, a2) { } static my_boost::arg<I> a3_() { return my_boost::arg<I>(); } }; // list0 class list0 { public: template<class T> T & operator[] (T& v) const { return v; } template<class R, class F, class A> R operator()(type<R>, F& f, A&) { return f(); } }; // list1 template<class A1> class list1 : private storage1<A1> { private: typedef storage1<A1> base_type; public: explicit list1(A1 a1) : base_type(a1) { } A1 operator[] (my_boost::arg<1> (*) ()) const { return base_type::a1_; } template<class T> T& operator[] (T& v) const { return v; } template<class R, class F, class A> R operator()(type<R>, F& f, A& a) { return f(a[base_type::a1_]); } }; // list2 template<class A1, class A2> class list2 : private storage2<A1, A2> { private: typedef storage2<A1, A2> base_type; public: list2(A1 a1, A2 a2) : base_type(a1, a2) { } A1 operator[] (my_boost::arg<1> (*)()) const { return base_type::a1_; } A2 operator[] (my_boost::arg<2> (*)()) const { return base_type::a2_; } template<class T> T operator[] (T v) const { return v; } template<class R, class F, class A> R operator()(type<R>, F& f, A& a) { return f(a[base_type::a1_], a[base_type::a2_]); } }; // list3 template<class A1, class A2, class A3> class list3 : private storage3<A1, A2, A3> { private: typedef storage3<A1, A2, A3> base_type; public: list3( A1 a1, A2 a2, A3 a3 ) : base_type(a1, a2, a3) { } A1 operator[] (my_boost::arg<1> (*)()) const { return base_type::a1_; } A2 operator[] (my_boost::arg<2> (*)()) const { return base_type::a2_; } A3 operator[] (my_boost::arg<3> (*)()) const { return base_type::a3_; } template<class T> T & operator[] (T& v) const { return v; } template<class R, class F, class A> R operator()(type<R>, F& f, A& a) { return f(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_]); } }; // bind_t template<class R, class F, class L> class bind_t { public: bind_t(F f, L const & l) : f_(f), l_(l) {} R operator()() { list0 a; return l_(type<R>(), f_, a); } template<class A1> R operator()(A1 a1) { list1<A1> a(a1); return l_(type<R>(), f_, list1<A1>(a1)); } template<class A1, class A2> R operator()(A1 a1, A2 a2) { list2<A1, A2> a(a1, a2); return l_(type<R>(), f_, a); } template<class A1, class A2, class A3> R operator()(A1 a1, A2 a2, A3 a3) { list3<A1, A2, A3> a(a1, a2, a3); return l_(type<R>(), f_, a); } private: F f_; L l_; }; } // bind for function template<class R> _bi::bind_t<R, R (*)(), _bi::list0> bind(R (*f)()) { typedef R (*F)(); typedef _bi::list0 list_type; return _bi::bind_t<R, F, list_type> (f, list_type()); } template<class R, class B1, class A1> _bi::bind_t<R, R (*)(B1), _bi::list1<A1> > bind(R (*f)(B1), A1 a1) { typedef R (*F)(B1); typedef typename _bi::list1<A1> list_type; return _bi::bind_t<R, F, list_type>(f, list_type(a1)); } template<class R, class B1, class B2, class A1, class A2> _bi::bind_t<R, R (*)(B1, B2), typename _bi::list2<A1, A2> > bind(R (*f)(B1, B2), A1 a1, A2 a2) { typedef R (*F)(B1, B2); typedef typename _bi::list2<A1, A2> list_type; return _bi::bind_t<R, F, list_type>(f, list_type(a1, a2)); } template<class R, class B1, class B2, class B3, class A1, class A2, class A3> _bi::bind_t<R, R (*)(B1, B2, B3), typename _bi::list3<A1, A2, A3>::type> bind(R (*f)(B1, B2, B3), A1 a1, A2 a2, A3 a3) { typedef R (*F)(B1, B2, B3); typedef typename _bi::list3<A1, A2, A3> list_type; return _bi::bind_t<R, F, list_type>(f, list_type(a1, a2, a3)); } } static my_boost::arg<1> _1; static my_boost::arg<2> _2; static my_boost::arg<3> _3; //#include <boost/bind.hpp> int add(int a, int b) { return a + b; } void main() { int n = my_boost::bind(add, 10, _1)(100); }
storageN は _N の混ざった引数リストを保存するためのクラスっぽい。
普通に考えると、storage2 とかは
template<class A1, class A2> struct storage2 { A1 a1_; A2 a2_; storage2(A1 a1, A2 a2) : a1_(a1), a2_(a2) { } }; template<class A1, int I> struct storage2<A1, my_boost::arg<I> > { A1 a1_; static my_boost::arg<I> a2_() { return my_boost::arg<I>(); } storage2(A1 a1, my_boost::arg<I> a2) : a1_(a1) { } }; template<int I, A2> struct storage2<my_boost::arg<I>, A2> { static my_boost::arg<I> a1_() { return my_boost::arg<I>(); } A2 a2_; storage2(my_boost::arg<I> a1, A2 a2) : a2_(a2) { } };
こうやって全てのパターンを網羅しないといけないと考えるんだけど、そこで継承を使って最小限の定義しかしていないのがうまいなぁと。
んで main 側からソースをおっかけてくと、まず最初に
template<class R, class B1, class B2, class A1, class A2> _bi::bind_t<R, R (*)(B1, B2), typename _bi::list2<A1, A2> > bind(R (*f)(B1, B2), A1 a1, A2 a2) { typedef R (*F)(B1, B2); typedef typename _bi::list2<A1, A2> list_type; return _bi::bind_t<R, F, list_type>(f, list_type(a1, a2)); }
この関数が呼ばれる。
R だの B1 だのを展開すると、こうなる。
_bi::bind_t<int, int (*)(int, int), typename _bi::list2<int, arg<1> > > bind(int (*f)(int, int), int a1, arg<1> a2) { typedef int (*F)(int, int); typedef typename _bi::list2<int, arg<1> > list_type; return _bi::bind_t<int, F, list_type>(f, list_type(a1, a2)); }
list2 クラスは storage2 クラスを継承したクラスなので、storage2 は storage2
無駄な arg
で、bind_t
bind_t<int, int (*)(int, int), list2<int, arg<1> > >
こんな型になってて、コンストラクタで F と L はメンバに格納される。
で、main 関数はその後 bind_t::operator()(A1) を呼び出しているのでそこをテンプレート引数を展開して見てみる。
int (*f_)(int, int); _bi::list2<int, arg<1> > l_; int operator()(int a1) { list1<int> a(a1); return l_(type<int>(), f_, list1<int>(a1)); }
引数を list1 に格納し、l_::operator() を呼び出している。
ここで list1 というのは list2 の arg<1> の部分を埋めてくれるクラスのはず。
で、list2 の operator() を展開して見てみると、
typedef storage2<int, arg<1> > base_type; int operator()(type<int>, int (*f)(int, int), list1<int>& a) { return f(a[base_type::a1_], a[base_type::a2_]); }
こんな感じになっていて、ここで f、つまり add 関数を呼び出している。
f の第一引数、第二引数共に int 型なので、a[base_type::a1_], a[base_type::a2_] は両方とも int を返すはずだ。
このとき、base_type::a1_ の型は int で、base_type::a2_ の型は arg<1> を返す static 関数なので、base_type::a2_ を int 型に変換するマジックが a[base_type::a2_] にはあるのだろうということが予想できる。
list1 の operator[] を見てみると、
A1 operator[] (my_boost::arg<1> (*) ()) const { return base_type::a1_; } template<class T> T& operator[] (T& v) const { return v; }
こうなっている。
base_type::a1_ の型は int なので、operator(T&) が使用され、base_type::a1_ がそのまま返される。
つまり bind で束縛した際の引数(10)がそのまま使われる。
で、base_type::a2_ の型は arg<1> を返す static 関数、つまり arg<1> (*)() なので、operator(arg<1> (*)()) が使用され、list1 の a1_ が返される。
つまり bind で束縛していなかった部分を、bind_t::operator() を呼び出した引数リストの1番目の引数(100)に変換している。
で、このおかげで add(10, 100) が呼ばれると。
意外にあっさりとした仕組みでびっくりした。
ちなみに
int n = my_boost::bind(my_boost::bind(add, 10, _1), 100)();
とするとエラーが発生する。
bind 関数の第一引数は R (*)() か R (*)(A1) か R (*)(A1, A2) か R (*)(A1, A2, A3) しか受け付けないからだ。
これは R (*)(...) の部分を F という一つのテンプレート引数にすれば、
template<class R, F, class A1, class A2, class A3> _bi::bind_t<R, F, typename _bi::list3<A1, A2, A3>::type> bind(F f, A1 a1, A2 a2, A3 a3) { typedef typename _bi::list3<A1, A2, A3> list_type; return _bi::bind_t<R, F, list_type>(f, list_type(a1, a2, a3)); }
これで簡単に解決できそうな気がするけど、引数から戻り値(R)が判断できないのでエラーになる。
そこで、次のようにする。
template<class F, class A1, class A2, class A3> _bi::bind_t<F, typename _bi::list3<A1, A2, A3>::type> bind(F f, A1 a1, A2 a2, A3 a3) { typedef typename F::result_type R; typedef typename _bi::list3<A1, A2, A3> list_type; return _bi::bind_t<R, F, list_type>(f, list_type(a1, a2, a3)); }
F に result_type が定義されていれば動くようにする。
今回の場合であれば bind_t クラスに result_type を定義することによって、正常に bind 出来るようになる。
うまい仕組みだなぁ……。