Preprocesador C ++: evitar la repetición de código de la lista de variables miembro

Tengo varias clases, cada una con diferentes variables miembro que se inicializan trivialmente en un constructor. Aquí hay un ejemplo:

struct Person { Person(const char *name, int age) : name(name), age(age) { } private: const char *name; int age; }; 

Cada uno tiene una función de print() asociada print() .

 template  void print(const Person &person) { std::cout << "name=" << name << "\n"; std::cout << "age=" << age << "\n"; } 

Este código es propenso a errores ya que la lista de parámetros se replica en cuatro lugares. ¿Cómo puedo reescribir el código para evitar esta duplicación? Me gustaría usar el preprocesador y / o plantillas.

Por ejemplo, ¿podría usar la técnica del preprocesador X-args, algo como esto?

 #define ARGUMENTS \ ARG(const char *, name) \ ARG(int, age) struct Person { Person(LIST_TYPE_NAME_COMMA(ARGUMENTS)) : LIST_NAME_INIT(ARGUMENTS) { } private: LIST_TYPE_NAME_SEMICOLON(ARGUMENTS) }; template  void print(const Person &person) { LIST_COUT_LINE(ARGUMENTS) } #undef ARGUMENTS 

¿O mejor, un enfoque basado en plantillas?

Por favor, no preguntes por qué quiero hacer esto, hay decisiones de diseño razonadas que han resultado en múltiples objetos similares con parámetros nombrados. Los parámetros deben denominarse variables miembro por motivos de rendimiento. Solo estoy explorando si es posible enumerar los parámetros y sus tipos solo una vez.

Lo que debe hacer es hacer que el preprocesador genere datos de reflexión sobre los campos. Esta información se puede almacenar como clases anidadas.

Primero, para que sea más fácil y más limpio escribirlo en el preprocesador, usaremos la expresión escrita. Una expresión tipada es solo una expresión que pone el tipo entre paréntesis. Entonces, en lugar de escribir int x escribirás (int) x . Aquí hay algunas macros útiles para ayudar con las expresiones escritas:

 #define REM(...) __VA_ARGS__ #define EAT(...) // Retrieve the type #define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,) #define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__) #define DETAIL_TYPEOF_HEAD(x, ...) REM x #define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__), // Strip off the type #define STRIP(x) EAT x // Show the type without parenthesis #define PAIR(x) REM x 

A continuación, definimos una macro REFLECTABLE para generar los datos sobre cada campo (más el campo mismo). Esta macro se llamará así:

 REFLECTABLE ( (const char *) name, (int) age ) 

Entonces, usando Boost.PP iteramos sobre cada argumento y generamos los datos de esta manera:

 // A helper metafunction for adding const to a type template struct make_const { typedef T type; }; template struct make_const { typedef typename boost::add_const::type type; }; #define REFLECTABLE(...) \ static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \ friend struct reflector; \ template \ struct field_data {}; \ BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) #define REFLECT_EACH(r, data, i, x) \ PAIR(x); \ template \ struct field_data \ { \ Self & self; \ field_data(Self & self) : self(self) {} \ \ typename make_const::type & get() \ { \ return self.STRIP(x); \ }\ typename boost::add_const::type & get() const \ { \ return self.STRIP(x); \ }\ const char * name() const \ {\ return BOOST_PP_STRINGIZE(STRIP(x)); \ } \ }; \ 

Lo que esto hace es generar una constante fields_n que es el número de campos reflectables en la clase. Luego, especializa field_data para cada campo. También es amiga de la clase de reflector , esto es para que pueda acceder a los campos incluso cuando son privados:

 struct reflector { //Get field_data at index N template static typename T::template field_data get_field_data(T& x) { return typename T::template field_data(x); } // Get the number of fields template struct fields { static const int n = T::fields_n; }; }; 

Ahora para iterar sobre los campos usamos el patrón de visitante. Creamos un rango MPL de 0 a la cantidad de campos y accedemos a los datos de campo en ese índice. Luego pasa los datos del campo al visitante proporcionado por el usuario:

 struct field_visitor { template void operator()(C& c, Visitor v, T) { v(reflector::get_field_data(c)); } }; template void visit_each(C & c, Visitor v) { typedef boost::mpl::range_c::n> range; boost::mpl::for_each(boost::bind(field_visitor(), boost::ref(c), v, _1)); } 

Ahora, por el momento de la verdad, lo ponemos todo junto. Aquí es cómo podemos definir la clase de Person :

 struct Person { Person(const char *name, int age) : name(name), age(age) { } private: REFLECTABLE ( (const char *) name, (int) age ) }; 

Aquí está la función print_fields generalizada:

 struct print_visitor { template void operator()(FieldData f) { std::cout < < f.name() << "=" << f.get() << std::endl; } }; template void print_fields(T & x) { visit_each(x, print_visitor()); } 

Un ejemplo:

 int main() { Person p("Tom", 82); print_fields(p); return 0; } 

Qué salidas:

 name=Tom age=82 

Y voila, acabamos de implementar la reflexión en C ++, en menos de 100 líneas de código.

He resuelto el mismo problema con mi estructura genérica al código JSON.

Definir una macro: REFLECT (CLASS_NAME, MEMBER_SEQUENCE) donde MEMBER_SEQUENCE es (nombre) (edad) (otro) (…)

Haga que REFLECT se expanda a algo similar a:

 template<> struct reflector { template void visit( Visitor&& v ) { v( "name" , &CLASS_NAME::name ); v( "age", &CLASS_NAME::age ); ... } } 

Puede usar BOOST_PP_SEQ_FOREACH para expandir SEQ en los visitantes.

Luego defina su visitante de impresión:

 template struct print_visitor { print_visitor( T& s ):self(s){} template void operator( const char* name, R (T::*member) )const { std::cout<  void print( const T& val ) { reflector::visit( print_visitor(val) ); } 

http://bytemaster.github.com/mace/group_ mace _reflect__typeinfo.html

https://github.com/bytemaster/mace/blob/master/libs/reflect/include/mace/reflect/reflect.hpp

Me temo que su solución es bastante óptima para este uso reducido. Donde podemos ayudar es si tiene funciones adicionales además de la print que se beneficiarían al iterar sobre los campos.

Este es un ejemplo perfecto para Boost.Fusion Fusion Sequences ; se pueden usar para introducir la reflexión en tiempo de comstackción. Además, puede generar un comportamiento de tiempo de ejecución más genérico.

Por lo tanto, puede declarar sus elementos utilizando un Fusion.Map (que lo restringe a una sola aparición de cada tipo) u otras fantasías similares.

Si su tipo no se ajusta a una Secuencia de fusión (o no desea interferir con sus BOOST_FUSION_ADAPT_STRUCT internas), hay adaptadores en la sección adaptada , como BOOST_FUSION_ADAPT_STRUCT . Y, por supuesto, como no todo es una struct (o tiene miembros públicos), también hay una versión más genérica para las clases, simplemente se pone fea pronto: BOOST_FUSION_ADAPT_ADT .

Robo del comienzo rápido :

 struct print_xml { template  void operator()(T const& x) const { std::cout < < '<' << typeid(x).name() << '>' < < x << "' ; } }; int main() { vector stuff(1, 'x', "howdy"); int i = at_c<0>(stuff); char ch = at_c<1>(stuff); std::string s = at_c<2>(stuff); for_each(stuff, print_xml()); } 

Los adaptadores le permitirán “adaptar” un tipo, por lo que obtendría:

 struct Foo { int bar; char const* buzz; }; BOOST_FUSION_ADAPT_STRUCT( Foo, (int, bar) (char const*, buzz) ) 

Y entonces:

 int main() { Foo foo{1, "Hello"); for_each(foo, print_xml()); } 

Es una biblioteca bastante impresionante 🙂

Necesitas una tupla, no una clase. Esto resolvería fácilmente todos sus problemas sin tener que recurrir a hacks de preprocesador.

¿Por qué necesitas usar el preprocesador? La introducción a la biblioteca boost.fusion tiene un ejemplo similar a su caso de uso.

Aquí están mis 2 centavos como una adición a la gran macro REFLECTABLE de Paul. Tenía una necesidad de tener una lista vacía de campos, es decir REFLECTABLE() , para manejar una jerarquía de herencia correctamente. La siguiente modificación maneja este caso:

 // http://stackoverflow.com/a/2831966/2725810 #define REFLECTABLE_0(...) \ static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \ friend struct reflector; \ template  struct field_data {}; \ BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, \ BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) #define REFLECTABLE_1(...) \ static const int fields_n = 0; #define REFLECTABLE_CONST2(b, ...) REFLECTABLE_##b(__VA_ARGS__) #define REFLECTABLE_CONST(b, ...) REFLECTABLE_CONST2(b,__VA_ARGS__) #define REFLECTABLE(...) \ REFLECTABLE_CONST(BOOST_PP_IS_EMPTY(__VA_ARGS__), __VA_ARGS__)