使用boost fusion adapt_struct迭代访问C++嵌套结构字段

9

有两个 stackoverflow 答案 建议使用 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上看到,不需要使用类型字符串"intype",可以使用typeid和dynamic_cast。但这只是一个小的改进。我正在寻找更加内在于C++或boost语言设计的解决方案。

2个回答

10

我在我的博客网站上制作了一个示例,您可以在此处查看:JSON序列化器。在这种情况下,它是与嵌套结构一起使用的JSON序列化器。它使用了“更多Boost”解决方案,因为我在Boost.Serialization库中看到了它。(请参见下文和Coliru上的实时演示。)

该解决方案使用了结构的Fusion序列适配和一个元函数来遍历对象成员(递归地)- 使用Boost.TypeTraits和不同类型的特性。

您可以在googlecode corbasim项目的网站上查看相同解决方案的更复杂示例,用于创建运行时反射API。

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

请在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
2
@minghua,您可以编辑答案以与答案作者分享声望。 - Miguel Angel
Andres指向的这两个链接非常有帮助。如果你正在寻找答案,请务必关注它们。 - minghua
@AndrésSenac 啊,具有讽刺意味的是,我似乎已经在Minghua的博客上找到了相同的代码,用于回答我的问题。这里 - sehe

3
安德烈斯给出了一个很好的答案。我原始代码中的问题是 "for_each" 只接受序列类型。当编译器评估 int 时,它将 int 参数传递给 "for_each",因此失败了。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;
}

另一个更直接的解决方案是,不使用高级boost技巧,为每个基本类型实现一个专门的解码器类,而不使用“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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接