C++ переходит во вложенное поле struct с помощью boost fusion adapt_struct

Два ответа stackru предлагают подход с использованием fusion adapt_struct для итерации по структурам полей. Подход выглядит красиво. Однако как вы перебираете поле, которое само по себе является структурой?

Следуя предыдущим ответам, я придумаю код ниже. Проблема в предложении "#if 0", код не компилируется. В качестве альтернативного решения я создал функцию decode() для получения пустого указателя на целевой аргумент. Это работает, но теряет информацию о типе во время компиляции. Есть ли лучшее решение?

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;
}

Я видел в википедии вместо передачи строки типа "intype" вы можете использовать typeid и dynamic_cast. Но это будет лишь незначительное улучшение. Я ищу решение, которое более присуще C++ или улучшило бы дизайн языка.

2 ответа

Решение

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

В решении используется адаптация структуры Fusion Sequence и метафункция, которая обходит элементы объекта (рекурсивно) - используя 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;
}

Андрес дает отличный ответ. Проблема в моем исходном коде состоит в том, что for_each принимает только типы последовательности. Когда компилятор вычисляет T для int, он передает "for_each" аргумент int, таким образом, он терпит неудачу. Идея решения Adries заключается в том, чтобы скрыть "for_each" в классе для определенной последовательности (DecImplSeq_s ниже) и предоставить альтернативный класс (DecImplVoid_s) для непоследовательных полей. Затем создайте класс фасада для разделения декодирования полей последовательности и непоследовательности (DecCalc_s).

Общий заголовок идет с первым примером ниже, чтобы показать идею Адреса.

/* 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;

Общий код решения, полученный непосредственно из примера Адреса:

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;
}

После некоторого прогрессивного исследования, вот полный пример. Он использует более поздние функции повышения, но не собирается с ранними версиями повышения, такими как 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;
}
Другие вопросы по тегам