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 以外にシーケンスを別の型に変換する方法ってないんですかね。