【问题标题】:C++ iterate into nested struct field with boost fusion adapt_structC++ 使用 boost fusion adapt_struct 迭代到嵌套结构字段
【发布时间】:2012-08-18 13:33:11
【问题描述】:

两个 stackoverflow answers 建议使用 fusion adapt_struct 迭代结构字段的方法。这种方法看起来不错。但是,如何迭代到本身就是结构的字段?

根据前面的答案,我想出了下面的代码。问题在于代码无法编译的“#if 0”子句。作为替代解决方案,我创建了“decode()”函数来获取指向目标参数的 void 指针。这可行,但在编译时会丢失类型信息。有更好的解决方案吗?

struct Foo_s { int i; };
BOOST_FUSION_ADAPT_STRUCT( Foo_s,  (int, i) )

struct Bar_s { int v; Foo_s w; };
BOOST_FUSION_ADAPT_STRUCT( Bar_s, (int, v)  (Foo_s, w) )

struct AppendToTextBox {
    template <typename T> void operator()(T& t) const {
        int status = 0;
        const char *realname = abi::__cxa_demangle(typeid(t).name(), 0, 0, &status);
        printf("  typename: %s  value: %s  realname: %s\n", typeid(t).name(),
               boost::lexical_cast<std::string>(t).c_str(), realname);
        std::string rn(realname);
        if ( rn.rfind("_s") == rn.size()-2 ) {
#if 0 /* this can not compile */
            for_each(t, AppendToTextBox());
#else
            decode(&t, rn);
#endif
        }
    }
};

void decode(void *f, std::string & intype ) {
    if ( intype.find("Foo_s") == 0 ) 
        for_each( *(Foo_s *)f, AppendToTextBox());
};

int main(int argc, char *argv[]) {
  Bar_s f = { 2, { 3 } };
  for_each(f, AppendToTextBox());
  return 0;
}

我在wikipedia 上看到过,您可以使用 typeid 和 dynamic_cast,而不是传递类型字符串“intype”。但这只会是很小的改进。我正在寻找一种更适合 C++ 或增强语言设计的解决方案。

【问题讨论】:

    标签: c++ reflection introspection template-meta-programming boost-fusion


    【解决方案1】:

    我制作了an example of what you want that you can see at my blog site。在这种情况下,它是一个适用于嵌套结构的 JSON 序列化程序。自从我在 Boost.Serialization 库中看到它以来,它使用了“更多 Boost”解决方案。 (另见下文和live on Coliru。)

    该解决方案使用 Fusion Sequence 适应结构和遍历对象成员(递归)的元函数 - 使用 Boost.TypeTraits 和特定类型的不同特征。

    您可以看到 a more complex example of the same solution at the site for googlecode corbasim project 用于创建运行时自反 API。

    通用 JSON 序列化程序的代码清单:

    Live on Coliru

    #ifndef JSON_SERIALIZER_HPP
    #define JSON_SERIALIZER_HPP
    
    #include <boost/type_traits.hpp> // is_array, is_class, remove_bounds
    
    #include <boost/mpl/eval_if.hpp>
    #include <boost/mpl/identity.hpp>
    #include <boost/mpl/next_prior.hpp>
    
    #include <boost/fusion/mpl.hpp>
    #include <boost/fusion/adapted.hpp> // BOOST_FUSION_ADAPT_STRUCT
    
    // boost::fusion::result_of::value_at
    #include <boost/fusion/sequence/intrinsic/value_at.hpp>
    #include <boost/fusion/include/value_at.hpp>
    
    // boost::fusion::result_of::size
    #include <boost/fusion/sequence/intrinsic/size.hpp>
    #include <boost/fusion/include/size.hpp>
    
    // boost::fusion::at
    #include <boost/fusion/sequence/intrinsic/at.hpp>
    #include <boost/fusion/include/at.hpp>
    
    namespace json
    {
    
    // Forward
    template < typename T >
    struct serializer;
    
    namespace detail
    {
    
    namespace iterator
    {
    
    template < typename S, typename N >
    struct Comma
    {
        template < typename Ostream >
        static inline void comma(Ostream& os)
        {
            os << ", ";
        }
    };
    
    template < typename S >
    struct Comma< S, typename boost::mpl::prior< typename boost::fusion::result_of::size< S >::type >::type >
    {
        template < typename Ostream >
        static inline void comma(Ostream& os)
        {
        }
    };
    
    // Iteracion sobre una estructura
    template < typename S, typename N >
    struct StructImpl
    {
        // Tipo del campo actual
        typedef typename boost::fusion::result_of::value_at< S, N >::type current_t;
        typedef typename boost::mpl::next< N >::type next_t;
        typedef boost::fusion::extension::struct_member_name< S, N::value > name_t;
    
        template < typename Ostream >
        static inline void serialize(Ostream& os, const S& s)
        {
            os << "\"" << name_t::call() << "\": ";
            ::json::serializer< current_t >::serialize(os, boost::fusion::at< N >(s));
    
            // Insert comma or not    
            Comma< S, N >::comma(os);
    
            StructImpl< S, next_t >::serialize(os, s);
        }
    };
    
    // Fin de la iteracion sobre estructuras.
    template < typename S >
    struct StructImpl< S, typename boost::fusion::result_of::size< S >::type >
    {
        template < typename Ostream >
        static inline void serialize(Ostream& os, const S& s)
        {
            // Nada que hacer
        }
    };
    
    // Iterador sobre una estructura. Template fachada.
    template < typename S >
    struct Struct : StructImpl< S, boost::mpl::int_< 0 > > {};
    
    } // iterator
    
    template < typename T >
    struct array_serializer 
    {
        typedef array_serializer< T > type;
    
        typedef typename boost::remove_bounds< T >::type slice_t;
    
        static const size_t size = sizeof(T) / sizeof(slice_t);
    
        template < typename Ostream >
        static inline void serialize(Ostream& os, const T& t)
        {
            os << "[";
            for(size_t idx=0; idx<size; idx++)
            {
                ::json::serializer< slice_t >::serialize(os, t[idx]);
                if (idx != size-1)
                    os << ", ";
            }
            os << "]";
        }
    
    };
    
    template < typename T >
    struct struct_serializer 
    {
        typedef struct_serializer< T > type;
    
        template < typename Ostream >
        static inline void serialize(Ostream& os, const T& t)
        {
            os << "{";
            iterator::Struct< T >::serialize(os, t);
            os << "}";
        }
    };
    
    template < typename T >
    struct arithmetic_serializer 
    {
        typedef arithmetic_serializer< T > type;
    
        template < typename Ostream >
        static inline void serialize(Ostream& os, const T& t)
        {
            os << t;
        }
    };
    
    template < typename T >
    struct calculate_serializer
    {
        typedef
            typename boost::mpl::eval_if< boost::is_array< T >,
                boost::mpl::identity< array_serializer < T > >,
            //else
            typename boost::mpl::eval_if< boost::is_class< T >,
                boost::mpl::identity< struct_serializer < T > >,
            //else
                boost::mpl::identity< arithmetic_serializer < T > >
            >
            >::type type;
    
    };
    
    } // detail
    
    template < typename T >
    struct serializer : public detail::calculate_serializer < T >::type
    {
    };
    
    
    } // json
    
    #endif // JSON_SERIALIZER_HPP
    
    //#include "json.hpp"
    #include <iostream>
    
    struct my_other_struct
    {
        int my_other_integer;
    };
    
    struct my_struct
    {
        int my_integer;
    
        typedef int my_array_t[2];
        my_array_t my_array;
    
        typedef my_other_struct my_other_structs_t[3];
        my_other_structs_t my_other_structs;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(my_struct, (int, my_integer) (my_struct::my_array_t, my_array) (my_struct::my_other_structs_t, my_other_structs))
    BOOST_FUSION_ADAPT_STRUCT(my_other_struct, (int, my_other_integer))
    
    
    int main(int argc, char *argv[])
    {
        my_struct s1 = my_struct { 1, { 42, -42 }, { { 11 }, { 22 }, { 33 } } };
    
        json::serializer< my_struct >::serialize(std::cout, s1);
    
        std::cout << std::endl;
    }
    

    【讨论】:

    • 谢谢,很好的答案!我详细说明了一点,并将工作代码放在单独的答案中,只是为了说清楚。
    • @minghua 您可以编辑答案以便与答案作者分享声誉
    • Andres 指出的两个链接非常有帮助。如果您正在寻找答案,请关注他们。
    • @AndrésSenac 啊,具有讽刺意味的是,我似乎找到了same code via Minghua's blog 我的答案here
    【解决方案2】:

    安德烈斯给出了一个很好的答案。我原始代码中的问题是“for_each”只接受序列类型。当编译器为一个 int 计算 T 时,它传递给“for_each”一个 int 参数,因此它失败了。 Adries 解决方案背后的想法是将“for_each”隐藏在特定于序列的类(下面的 DecImplSeq_s)中,并为非序列字段提供替代类(DecImplVoid_s)。然后创建一个门面类来划分序列和非序列字段的解码(DecCalc_s)。

    通用标题与下面的第一个示例一起显示 Adres 的想法。

    /* compile with g++ 4.4.6: g++ -I boost_1_35_0 test.cpp */
    #include <typeinfo>
    #include <string>
    #include <boost/fusion/include/sequence.hpp>
    #include <boost/fusion/include/algorithm.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/is_sequence.hpp>
    #include <boost/mpl/eval_if.hpp>
    #include <boost/lexical_cast.hpp>
    #include <cxxabi.h>
    #include <stdio.h>
    using namespace boost::fusion;
    

    直接来源于 Adres 示例的解决方案的通用代码:

    template <typename T2> struct Dec_s;
    struct AppendToTextBox {
      template <typename T> void operator()(T& t) const {
            //decode T and t as the original code here...
            Dec_s<T>::decode(t);
      }
    };
    template <typename T2> struct DecImplSeq_s {
      typedef DecImplSeq_s<T2> type;
      static void decode(T2   & f) { for_each(f, AppendToTextBox()); };
    };
    template <typename T2> struct DecImplVoid_s {
      typedef DecImplVoid_s<T2> type;
      static void decode(T2   & f) { };
    };
    
    template <typename T2> struct DecCalc_s {
      typedef typename
        boost::mpl::eval_if< traits::is_sequence<T2>, DecImplSeq_s<T2>, DecImplVoid_s<T2> >
      ::type type;
    };
    
    template <typename T2> struct Dec_s : public DecCalc_s<T2>::type { };
    

    下面是如何使用上面的通用代码:

    struct Foo_s { int i; char k[100]; };
    struct Bar_s { int v; Foo_s w; };
    
    BOOST_FUSION_ADAPT_STRUCT( Foo_s,  (int, i)  (char, k[100]) )
    BOOST_FUSION_ADAPT_STRUCT( Bar_s, (int, v)  (Foo_s, w) )
    
    int main(int argc, char *argv[]) {
      Bar_s f = { 2, { 3, "abcd" } };
      Dec_s<Bar_s>::decode(f);
      return 0;
    }
    

    另一个不使用高级增强技巧的更直接的解决方案是,您可以为每个原始类型实现一个专门的解码器类,而无需使用“eval_if”。要使用此解决方案,您需要对结构中的每个原始类型进行专门化。

    struct Foo_s { int i; char k[100]; };
    BOOST_FUSION_ADAPT_STRUCT( Foo_s,  (int, i)  (char, k[100]) )
    
    struct Bar_s { int v; Foo_s w; };
    BOOST_FUSION_ADAPT_STRUCT( Bar_s, (int, v)  (Foo_s, w) )
    
    template <typename T2> struct Dec_s {  static void decode(T2   & f); };
    struct AppendToTextBox {
        template <typename T>
        void operator()(T& t) const {
            //decode T and t as the original code here...
            Dec_s<T>::decode(t);
        }
    };
    
    template <typename T2> void Dec_s<T2>::decode(T2 & f) {
        for_each(f, AppendToTextBox());
    };
    template<> void Dec_s<int >::decode(int  & f) {};
    template<> void Dec_s<char>::decode(char & f) {};
    
    int main(int argc, char *argv[]) {
      Bar_s f = { 2, { 3, "abcd" } };
      Dec_s<Bar_s>::decode(f);
      return 0;
    }
    

    经过一些渐进的探索,这里有一个完整的例子。它使用更新的 boost 功能,但不使用早期的 boost 版本(如 1.35.0)构建。它适用于 boost 1.47.0 和 1.51.0。

    通用头部分:

    #include <typeinfo>
    #include <string>
    #include <boost/fusion/include/sequence.hpp>
    #include <boost/fusion/include/algorithm.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/is_sequence.hpp>
    #include <boost/mpl/eval_if.hpp>
    #include <boost/type_traits.hpp> // is_array, is_class, remove_bounds
    #include <boost/lexical_cast.hpp>
    #include <cxxabi.h>
    #include <stdio.h>
    
    extern int dec_indents; /* 0, 4, 8, ... */
    struct NL {
        static void print() { printf("\n");
            for (int i=0; i<dec_indents; i++) printf(" ");
        }
    };
    
    using namespace boost::fusion;
    

    然后是带有输出格式的普通解码器:

    template <typename T2> struct Dec_s;
    
    template <typename S, typename N> struct Comma {
      static inline void comma() { printf(" , "); }
    };
    template <typename S> struct Comma<S, typename
     boost::mpl::prior<typename boost::fusion::result_of::size<S>::type >::type> {
       static inline void comma() {}
    };
    
    template <typename S, typename N> struct DecImplSeqItr_s {
      typedef typename boost::fusion::result_of::value_at<S, N>::type current_t;
      typedef typename boost::mpl::next<N>::type next_t;
      typedef boost::fusion::extension::struct_member_name<S, N::value> name_t;
      static inline void decode(S& s) {
        printf(" \"%s\" = ", name_t::call() );
        Dec_s<current_t>::decode(boost::fusion::at<N>(s));
        Comma<S, N>::comma();  // Insert comma or not
        DecImplSeqItr_s<S, next_t>::decode(s);
      }
    };
    template <typename S>
    struct DecImplSeqItr_s<S, typename boost::fusion::result_of::size<S>::type > {
        static inline void decode(S& s) { }
    };
    template <typename S>
    struct DecImplSeqStart_s:DecImplSeqItr_s<S, boost::mpl::int_<0> > {};
    
    template <typename S> struct DecImplSeq_s {
      typedef DecImplSeq_s<S> type;
      static void decode(S & s) {
        printf("  struct  start --- { --- ");
        dec_indents += 4;
        NL::print();
        DecImplSeqStart_s<S>::decode(s);
        dec_indents -= 4;
        NL::print();
        printf("  struct  done  --- } --- ");
        NL::print();
      };
    };
    
    template <typename T2> struct DecImplArray_s {
      typedef DecImplArray_s<T2> type;
      typedef typename boost::remove_bounds<T2>::type slice_t;
      static const size_t size = sizeof(T2) / sizeof(slice_t);
      static inline void decode(T2 & t) {
        printf("  array start --- [ --- ");
        dec_indents += 4;
        NL::print();
        for(size_t idx=0; idx<size; idx++) {
            Dec_s<slice_t>::decode(t[idx]);
            if (idx < size-1) {
                NL::print(); printf(" , ");
            }
        }
        dec_indents -= 4;
        NL::print();
        printf("  array done  --- ] --- \n");
        NL::print();
      }
    };
    
    template <typename T2> struct DecImplVoid_s {
      typedef DecImplVoid_s<T2> type;
      static void decode(T2   & t) {
        int status = 0;
        const char *realname = abi::__cxa_demangle(typeid(t).name(),0,0,&status);
        printf(" type %s", realname);
        NL::print();
      };
    };
    
    template <typename T2> struct DecCalc_s {
      typedef
        typename boost::mpl::eval_if< traits::is_sequence<T2>, DecImplSeq_s<T2>,
        typename boost::mpl::eval_if< boost::is_array<T2>,
                                     boost::mpl::identity< DecImplArray_s<T2> >,
        DecImplVoid_s<T2>   > >
      ::type type;
    };
    
    template <typename T2> struct Dec_s : public DecCalc_s<T2>::type { };
    

    要使用这个通用解码器,你可以把它放到一个.h文件中,并使用下面的.c代码:

    /* compile with g++ 4.5.1: g++ -I boost_1_47_0 test.cpp */
    
    #include "common_decoder.h"
    
    using namespace boost::fusion;
    
    int dec_indents=0;
    
    struct Foo_s { int i; typedef char j_t[10]; Foo_s::j_t j; };
    BOOST_FUSION_ADAPT_STRUCT( Foo_s, (int, i) (Foo_s::j_t, j) )
    
    struct Bar_s { int v; typedef Foo_s w_t[2]; Bar_s::w_t w; };
    BOOST_FUSION_ADAPT_STRUCT( Bar_s, (int, v) (Bar_s::w_t, w) )
    
    int main(int argc, char *argv[]) {
      Bar_s f = { 2, {{ 3, "abcd" },{ 4, "defg" }} };
      Dec_s<Bar_s>::decode(f);
      return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-17
      • 2016-12-02
      • 1970-01-01
      • 2012-10-30
      • 1970-01-01
      • 2010-11-11
      相关资源
      最近更新 更多