С++ итерации во вложенное поле struct с boost fusion adapt_struct

Два qaru.site/info/21935/... предлагают использовать метод 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. Но это будет лишь незначительное улучшение. Я ищу решение, более подходящее для С++ или форсированного языкового дизайна.

Ответ 1

Я сделал пример того, что вы хотите увидеть на моем сайте в блоге. В этом случае это сериализатор JSON, который работает с вложенными структурами. Он использует решение "больше Boost", так как я видел его в библиотеке Boost.Serialization. (См. Также ниже и жить на Coliru.)

Решение использует адаптацию Fusion Sequence для структур и метафайла, который перемещает элементы объекта (рекурсивно) - используя Boost.TypeTraits и различные черты для определенных типов.

Вы можете увидеть более сложный пример того же решения на сайте для проекта googlecode corbasim для создания рефлексивного 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;
}

Ответ 2

Андрес дает отличный ответ. Проблема в моем исходном коде заключается в том, что "for_each" принимает только типы последовательностей. Когда компилятор оценивает значение T для int, он переходит к аргументу "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, такими как 1.35.0. Он хорошо работает с повышением 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;
}