Boost.Fusion のシーケンスに動的にアクセスする

さて、Boost.Fusion のシーケンスの要素にアクセスする場合、次のようにコンパイル時定数を用いてアクセスする必要があります。

auto v = f::make_vector(42, std::string("homu"), 3.14f, std::string("mado"));

constexpr int index0 = 0;
int n = f::get<index0>(v);        // OK

int index1 = 1;
auto str = get(v, index1);        // NG


これはコンパイル時にシーケンスのどの型を返すのかを決定する必要がある為です。
逆にいえば、型さえ決まっていれば動的にアクセスする事は可能です。
と、いうことで戻り値型に Boost.Variant を使用する事で動的にアクセス出来るようにしてみました。

[ソース]

#include <boost/variant.hpp>
#include <boost/mpl/unique.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/fusion/include/mpl.hpp>
#include <boost/type_traits/is_same.hpp>


template<typename Seq>
struct as_variant : boost::make_variant_over<
    typename boost::mpl::unique<
        Seq,
        boost::is_same<boost::mpl::_1, boost::mpl::_2>
    >::type
>{};


#include <boost/fusion/include/copy.hpp>
#include <boost/fusion/include/boost_array.hpp>
#include <boost/array.hpp>

template<typename Seq>
typename as_variant<Seq>::type
get(Seq const& seq, std::size_t index){
    typedef typename as_variant<Seq>::type var_type;
    boost::array<var_type, 4> result;
    boost::fusion::copy(seq, result);
    return index < boost::mpl::size<Seq>::value
         ? result[index]
         : throw(std::exception("out of range"));
}


//
// fusion::copy を使用しない方法
// 
// #include <boost/fusion/include/tuple.hpp>
// 
// namespace detail{
// 
//     template<std::size_t Index, typename Seq>
//     typename boost::lazy_enable_if_c<
//         boost::mpl::size<Seq>::value == Index,
//         as_variant<Seq>
//     >::type
//     get(Seq const& seq, std::size_t index){
//         throw(std::exception("out of range"));
//     }
// 
//     template<std::size_t Index, typename Seq>
//     typename boost::lazy_enable_if_c<
//         boost::mpl::size<Seq>::value != Index,
//         as_variant<Seq>
//     >::type
//     get(Seq const& seq, std::size_t index){
//         typedef typename as_variant<Seq>::type result_type;
//         return index == Index ? result_type(boost::fusion::get<Index>(seq))
//             : detail::get<Index+1>(seq, index);
//     }
// 
// } // namespace detail
// 
// template<typename Seq>
// typename as_variant<Seq>::type
// get(Seq const& seq, std::size_t index){
//     return detail::get<0>(seq, index);
// }


#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/make_vector.hpp>
#include <boost/fusion/include/size.hpp>
#include <boost/lambda/lambda.hpp>
#include <iostream>
#include <string>
#include <tuple>

template<typename Result>
struct twice : boost::static_visitor<Result>{
    template<typename T>
    Result
    operator ()(T const& t) const{
        return t + t;
    }
};


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

    auto v = f::make_vector(42, std::string("homu"), 3.14f, std::string("mado"));

    typedef as_variant<decltype(v)>::type var_type;

    int index1 = 1;
    var_type str = get(v, index1);
    std::cout << str << std::endl;
    
    for(int i = 0 ; i < f::size(v) ; ++i){
        var_type var = get(v, i);
        std::cout << var << std::endl;
        std::cout << boost::apply_visitor(twice<var_type>(), var) << std::endl;
    }
    try{
        std::cout << get(v, 4) << std::endl;
    }
    catch(std::exception const& exp){
        std::cout << exp.what() << std::endl;
    }
    
    return 0;
}

[出力]

homu
42
84
homu
homuhomu
3.14
6.28
mado
madomado
out of range


Boost.Variant を使用するのである程度制限はあるんですが、割と柔軟に対応できそう。
関係ないですけど fusion::copy 以外にシーケンスを別の型に変換する方法ってないんですかね。