boost::function で Loki Chain のような処理

ここまでは考えた。

#include <iostream>

#include <boost/function.hpp>
#include <boost/functional.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/bind.hpp>
#include <boost/utility/result_of.hpp>

#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/at_c.hpp>


// boost::bind() の戻り値が boost::function_type::result_type に対応してないので自前で実装
template<typename func_t>
struct result_of{
    typedef typename boost::function_types::result_type<func_t>::type type;
};

template<class R, class F, class L>
struct result_of<boost::_bi::bind_t<R, F, L> >{
    typedef typename boost::_bi::bind_t<R, F, L>::result_type type;
};


// 2つの func を保持するクラス
// 戻り値は、fusion::vector<> に詰め込む
template<
    typename func1_t,
    typename func2_t
>
struct chainer{
private:
    typedef typename result_of<func1_t>::type func1_result;
    typedef typename result_of<func2_t>::type func2_result;
    
public:
    typedef typename boost::mpl::if_<
        boost::is_void<func1_result>,
        void,
        boost::fusion::vector<
            func1_result,
            func2_result
        >
    >::type result_type;
    chainer(func1_t& f1, func2_t& f2)
        : func1(f1), func2(f2){}
        
    result_type
    operator ()(){
        return call<result_type>();
    }
    
    template<typename T1, typename T2>
    result_type
    operator ()(T1 t1, T2 t2){
        return call<result_type>(t1, t2);
    }
    
private:
    // 戻り値の有無で切り替え
    template<typename result_type>
    result_type
    call(typename boost::enable_if_c<!boost::is_void<result_type>::value>::type* = NULL){
        return result_type(func1(), func2());
    }
    template<typename result_type>
    result_type
    call(typename boost::enable_if<boost::is_void<result_type> >::type* = NULL){
         func1(), func2();
    }

    template<typename result_type, typename T1, typename T2>
    result_type
    call(T1 t1, T2 t2,
        typename boost::enable_if_c<!boost::is_void<result_type>::value>::type* = NULL){
        return result_type(func1(t1, t2), func2(t1, t2));
    }
    template<typename result_type, typename T1, typename T2>
    result_type
    call(T1 t1, T2 t2,
        typename boost::enable_if<boost::is_void<result_type> >::type* = NULL){
         func1(t1, t2), func2(t1, t2);
    }

    func1_t& func1;
    func2_t& func2;
};

// chainer の特殊化
template<typename func1_t, typename func2_t>
struct result_of<chainer<func1_t, func2_t> >{
    typedef typename chainer<func1_t, func2_t>::result_type type;
};

template<
    typename func1_t,
    typename func2_t
>
chainer<func1_t, func2_t>
chain(func1_t& f1, func2_t& f2){
    return chainer<func1_t, func2_t>(f1, f2);
}


int
add(int n, int m){
    return n + m;
}

int
sub(int n, int m){
    return n - m;
}

float
division(int n, int m){
    return static_cast<float>(n) / static_cast<float>(m);
}

struct hoge{
    void call(){
        std::cout << "hoge::call()" << std::endl;
    }
};

int
main(){
    namespace fusion = boost::fusion;

    typedef boost::fusion::vector<int, int> result_t;

    // 2つの関数をぶっこむ
    // 戻り値は、fusion::vector<int, int>
    boost::function<result_t(int, int)>    func = chain(add, sub);
    result_t result = func(10, 5);
    assert( fusion::at_c<0>(result) == add(10, 5) );
    assert( fusion::at_c<1>(result) == sub(10, 5) );

    // バインドしてみたり
    boost::function<result_t()> func2 = chain(boost::bind(add, 3, 5), boost::bind(sub, 8, 2) );
    result_t result2 = func2();
    assert( fusion::at_c<0>(result2) == boost::bind(add, 3, 5)() );
    assert( fusion::at_c<1>(result2) == boost::bind(sub, 8, 2)() );

    // 引数型が同じなら戻り値型が違っても OK
    typedef boost::fusion::vector<int, float> result2_t;
    boost::function<result2_t(int, int)>    func3 = chain(add, division);
    result2_t result3 = func3(5, 2);
    assert( fusion::at_c<0>(result3) == add(5, 2) );
    assert( fusion::at_c<1>(result3) == division(5, 2) );

    // 一応、3つ以上もつなげることが出来るが・・・
    typedef fusion::vector<int, fusion::vector<int, float> > result4_t;
    boost::function<result4_t(int, int)> func4 = chain(add, chain(sub, division) );
    result4_t result4 = func4(7, 2);
    assert( fusion::at_c<0>(result4) == add(7, 2) );
    assert( fusion::at_c<0>(fusion::at_c<1>(result4)) == sub(7, 2) );
    assert( fusion::at_c<1>(fusion::at_c<1>(result4)) == division(7, 2) );

    // メソッド
    hoge    h;
    boost::function<void()> func5
        = chain( boost::bind(&hoge::call, h), boost::bind(&hoge::call, h) );
    func5();

    return 0;
}

素直に std::vector にでも突っ込んだほうがいいと思う。


2つの関数を保持する関数オブジェクトを作って、それの内部で呼び出しているだけです。
ロジック自体は、Loki の Chain そのまま。
Loki とは違い、関数の戻り値を fusion で返してるのでウマーな気がする。
今は引数なしと引数2つにしか対応してないから、BOOST_PP でなんとかしてみたい。
しかし、boost::result_of で boost::bind() の戻り値型が取得できないけどソースを読む気がしないな…。
(正確に言うと、読んでも分からない)


[boost]
ver 1.44.0
[参照]
http://www.kmonos.net/alang/boost/classes/result_of.html