STL非互換コンテナのRange
この記事はC++ Advent Calendar jp 2010 : ATNDの参加記事です
軽い気持ちでAdvent Calenderに参加してしまった事を若干後悔しながら
この記事を書いておりますが、なにぶん僕はそんな変態プログラミングが出来るわけでもないですし、変態じゃないですし、
そういったことは僕より前の日付の人の記事や僕より後の日付の記事に期待していただくとして、
僕は今回Oven関連で。ええ、やっぱり好きなので。
Motivetion
他の人が書いたライブラリとか、コンテナとか、STL互換じゃなかったりするじゃないですか。
それだけです。
(ところでSTL互換ていうのは、コンテナであれば規格でいうContainer Requirementsを満たして、STLの他のコンテナと同じように扱えて振舞うみたいなことでいいんですかね。)
で、まあ、要素にアクセスするのにSome3rdPartyContainer<>::GetAt(int index)で取得するみたいな仕様だったとするじゃないですか。
そうすると、例えば次のようにしてコンテナの要素を渡れない。
class A;
Some3rdPartyContainer<A> ct;
PSTADE_OVEN_FOREACH(_1, ct) {
std::cout << _1 << std::endl;
}
回避策としては
using namespace pstade::oven; PSTADE_OVEN_FOREACH(_1, counting(0, ct.GetSize())) { std::cout << ct.GetAt(_1) << std::endl; }
こうやって、counting Rangeでもって取得したいRangeだけ作って
コンテナアクセスはそのRangeから取ったインデックスで行うみたいな。
でも、これだとせっかくのRange Adaptorが使えませんね。
この場合であればクラスAにbool IsEnabled() constみたいなメンバ関数があって、
有効な要素だけ欲しいとか思ったら
using namespace pstade::oven; PSTADE_OVEN_FOREACH(_1, counting(0, ct.GetSize())) { if(_1.IsEnabled()) { std::cout << ct.GetAt(_1) << std::endl; } }
と、こんな感じですが、
せっかくfiltered Rangeアダプタがあるならそれを使ったほうがドヤ顔できるので
このコンテナをRangeで渡れるようにしたい。
「そしたら、You、Boost.Iteratorでイテレータ作っちゃいなYO!」っていうJohnnyさんの声も聞こえてくるかもですが、
なんかそれもわざわざな気がしたりして。
簡単にコンテナを渡るにはcounting Rangeから取れるインデックスを用いて
このコンテナの要素が取れるようにRangeを変換(Transformed)してしまいます。
PSTADE_OVEN_FOREACH(
_1,
counting(0, ct.GetCount())
| transformed(boost::bind(&Some3rdPartyContainer<A>::GetAt, ct, _1)) )
{
std::cout << _1 << std::endl;
}
このようにすれば、要素一つ一つを渡るRangeになります。まあ、コンテナがランダムアクセスじゃないと
使いものにならないでしょうが。
countingとtransformedの機能をまとめてしまえばもっと簡潔です。
PSTADE_OVEN_FOREACH( _1, some_imaginary_range( ct.GetCount(), boost::bind(&Some3rdPartyContainer<A>::GetAt, ct, _1)) ) { std::cout << _1 << std::endl; }
そのためのRangeを作るのがこちらです。
hwm::make_container_rangeが上でいうsome_imaginary_rangeになります。
#ifndef HWM_MAKE_CONTAINER_RANGE_HPP #define HWM_MAKE_CONTAINER_RANGE_HPP #include <pstade/result_of.hpp> #include <pstade/oven/counting.hpp> #include <pstade/oven/range_value.hpp> #include <pstade/oven/transformed.hpp> namespace hwm { namespace make_container_range_detail { using namespace pstade; using namespace pstade::oven; struct little { template< class Myself, class Incrementable, class UnaryFun > struct apply { typedef typename result_of<T_counting(Incrementable, Incrementable)>::type counting_range_t; typedef typename result_of< X_make_transformed<>(counting_range_t, UnaryFun&) >::type type; }; template< class Result, class Incrementable, class UnaryFun > Result call(Incrementable n, UnaryFun &fun) const { return counting(0, n)|transformed(fun); } }; } //namespace make_container_range_detail struct X_make_container_range : pstade::derived_from< pstade::egg::function<make_container_range_detail::little> > { }; typedef X_make_container_range::base_class T_make_container_range; PSTADE_POD_CONSTANT((T_make_container_range), make_container_range) = {{}}; } //namespace hwm #endif //HWM_MAKE_CONTAINER_RANGE_HPP
ついでに、
boost::bind(&Some3rdPartyContainer<A>::GetAt, ct, _1))
とか書いてますが、
GetAtが(例えばconst版と非const版で)OverloadされていたらBindするGetAtが曖昧になってこのままでは呼べません。
そのため、
boost::bind( static_cast<A &(Some3rdPartyContainer<A>::*)(int n)>(&Some3rdPartyContainer<A>::GetAt), ct, _1);
とするとか、
struct get_pred { mutable Some3rdPartyContainer<A> &ct_; get_pred(Some3rdPartyContainer<A> &ct) : ct_(ct) {} A & operator()(int n) const { return ct_.GetAt(n); } }; PSTADE_OVEN_FOREACH(_1, hwm::make_container_range(ct.GetCount(), get_pred(ct))) { std::cout << _1 << std::end; }
こんな感じで。
実際の動作はcounting Rangeつくってtransformedしてるだけなのでコンテナに限るものではありません。