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してるだけなのでコンテナに限るものではありません。