Boost.MPL の Sequence はその特性上 is_same を使いにくい。例えば
BOOST_MPL_ASSERT(( boost::is_same< boost::mpl::push_front< list<>, int_<1> >::type, list<int_<1> > >));
は通らない。push_front した結果は list ではなくなっているからだ。このため Boost.MPL には equal というアルゴリズムが用意されている。
BOOST_MPL_ASSERT(( boost::mpl::equal< boost::mpl::push_front< list<>, int_<1> >::type, list<int_<1> > >));
equal は Sequence の種類を気にせずその「要素」が一致しているかを判別するためこれは通る。通るのだが、equal は最上位の Sequence だけしか考慮してくれないため多次元の Sequence だとうまくいかない。equal の第 3 引数は要素を比較する際の Predicate であるため次元に合わせて equal を渡してやればいいのだが割と面倒である。
// bind base BOOST_MPL_ASSERT(( boost::mpl::equal< vector<vector<int_<1> > >, list<list<int_<1> > >, boost::mpl::bind< boost::mpl::quote3<boost::mpl::equal>, boost::mpl::_1, boost::mpl::_2, boost::mpl::protect< boost::mpl::bind< boost::mpl::quote2<boost::is_same>, boost::mpl::_1, boost::mpl::_2 > > > >)); // lambda base BOOST_MPL_ASSERT(( boost::mpl::equal< vector<vector<int_<1> > >, list<list<int_<1> > >, boost::mpl::lambda< boost::mpl::equal< boost::mpl::_1, boost::mpl::_2, boost::mpl::lambda< boost::is_same< boost::mpl::_1, boost::mpl::_2 > >::type > >::type >));
やってることは同じである。というか bind base の方は実質 lambda を展開した結果になっている。二次元でこれだとそれ以上だと絶望しそうだ。ということで再帰的な equal を書いてみた。
template<typename Arg1, typename Arg2, typename Pred = boost::is_same<boost::mpl::_1, boost::mpl::_2> > struct equal_deep { typedef typename boost::mpl::eval_if< boost::mpl::and_<boost::mpl::is_sequence<Arg1>, boost::mpl::is_sequence<Arg2> >, boost::mpl::equal<Arg1, Arg2, equal_deep<boost::mpl::_1, boost::mpl::_2, typename boost::mpl::lambda<Pred>::type> >, boost::mpl::eval_if< boost::mpl::or_<boost::mpl::is_sequence<Arg1>, boost::mpl::is_sequence<Arg2> >, boost::mpl::false_, boost::mpl::apply<Pred, Arg1, Arg2> > >::type type; }; BOOST_MPL_ASSERT(( equal_deep< vector<vector<int_<1> > >, list<list<int_<1> > >, >));
やっていることはそれほど難しくない。引数が両方 Sequence である時には equal に対して自分自身(equal_deep)を渡して再帰し、両方が Sequence でない時には渡された Predicate を呼び出す。自分が MPL Metafunction 関係をちゃんと把握していなかったので lambda かけるところと apply で迷ったぐらいである。7 行目の eval_if は if_ だと is_same の場合は大丈夫だが Sequence に対して呼び出せない Predicate (equal_to 等) を渡した場合にコンパイルエラーになるため eval_if でなければならない。また、nullary Metafunction を受け付ける部分は typename ::type を付けずに不要なインスタンス化を避けている。
これを書く際にせっかくなので一度 MPL Metafunction 系について整理してみたのだが以下次号。
0 件のコメント:
コメントを投稿