Two stackoverflow answers suggest the approach using fusion adapt_struct to iterate over struct fields. The approach looks nice. However, how do you iterate into a field which itself is a struct?
Following the previous answers, I come up with the code below. The problem is at the "#if 0" clause the code does not compile. As an alternative solution I created "decode()" function to take a void pointer to the target argument. That works, but loses the type information at compile time. Is there a better solution?
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;
}
I have seen on wikipedia instead of passing a type string "intype" you can use typeid and dynamic_cast. But that will only be a minor improvement. I'm looking for a solution that is more intrinsic to C++ or boost language design.
I made an example of what you want that you can see at my blog site. In this is case it's a JSON serializer that works with nested structs. It uses a 'more Boost' solution since I saw it in the Boost.Serialization library. (See also below and live on Coliru.)
The solution uses Fusion Sequence adaptation of structs and a metafunction that walks object members (recursively) - using Boost.TypeTraits and different traits for specific types.
You can see a more complex example of the same solution at the site for googlecode corbasim project for creating an run-time reflexive API.
Code listing for the generic JSON serializer:
See it 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;
}
Andres gives an excellent answer. The problem in my original code is that "for_each" takes only sequence types. When compiler evaluates the T for an int, it passes to "for_each" an int argument thus it fails. The idea behind Adries' solution is to hide "for_each" in a sequence-specific class (DecImplSeq_s below), and provide an alternative class (DecImplVoid_s) for non-sequence fields. Then create a facade class to divide the decoding of sequence and non-sequence fields (DecCalc_s).
The common header goes with the first example below to show Adres' idea.
/* 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;
The common code of the solution derived directly from Adres' sample:
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 { };
Here is how you can use the common code above:
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;
}
Another solution that is more straightforward without using advanced boost tricks, you can implement a specialized decoder class for each primitive types, without using "eval_if". To use this solution, you need to do a specialization for each primitive type in your structs.
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;
}
After some progressive exploration, here is a complete example. It uses more recent boost features, but does not build with early boost versions like 1.35.0. It works well with boost 1.47.0 and 1.51.0.
The common header part:
#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;
Then the common decoder with output formatting:
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 { };
To use this common decoder, you can put it into a .h file, and use the following .c code:
/* 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;
}
来源:https://stackoverflow.com/questions/12084781/c-iterate-into-nested-struct-field-with-boost-fusion-adapt-struct