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 件のコメント:
コメントを投稿