Lo que debe hacer es que el preprocesador genere datos de reflexión sobre los campos. Estos datos se pueden almacenar como clases anidadas.
Primero, para que sea más fácil y limpio escribirlo en el preprocesador, usaremos la expresión escrita. Una expresión escrita 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 REFLECTABLEmacro para generar los datos sobre cada campo (más el campo en sí). 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<class M, class T>
struct make_const
{
typedef T type;
};
template<class M, class T>
struct make_const<const M, T>
{
typedef typename boost::add_const<T>::type type;
};
#define REFLECTABLE(...) \
static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
friend struct reflector; \
template<int N, class Self> \
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<class Self> \
struct field_data<i, Self> \
{ \
Self & self; \
field_data(Self & self) : self(self) {} \
\
typename make_const<Self, TYPEOF(x)>::type & get() \
{ \
return self.STRIP(x); \
}\
typename boost::add_const<TYPEOF(x)>::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_nque es el número de campos reflectantes en la clase. Luego se especializa field_datapara cada campo. También es amiga de la reflectorclase, esto es para que pueda acceder a los campos incluso cuando son privados:
struct reflector
{
//Get field_data at index N
template<int N, class T>
static typename T::template field_data<N, T> get_field_data(T& x)
{
return typename T::template field_data<N, T>(x);
}
// Get the number of fields
template<class T>
struct fields
{
static const int n = T::fields_n;
};
};
Ahora, para recorrer 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<class C, class Visitor, class I>
void operator()(C& c, Visitor v, I)
{
v(reflector::get_field_data<I::value>(c));
}
};
template<class C, class Visitor>
void visit_each(C & c, Visitor v)
{
typedef boost::mpl::range_c<int,0,reflector::fields<C>::n> range;
boost::mpl::for_each<range>(boost::bind<void>(field_visitor(), boost::ref(c), v, _1));
}
Ahora, por el momento de la verdad, lo juntamos todo. Así es como podemos definir una Personclase que sea reflectante:
struct Person
{
Person(const char *name, int age)
:
name(name),
age(age)
{
}
private:
REFLECTABLE
(
(const char *) name,
(int) age
)
};
Aquí hay una print_fieldsfunción generalizada que utiliza los datos de reflexión para iterar sobre los campos:
struct print_visitor
{
template<class FieldData>
void operator()(FieldData f)
{
std::cout << f.name() << "=" << f.get() << std::endl;
}
};
template<class T>
void print_fields(T & x)
{
visit_each(x, print_visitor());
}
Un ejemplo de uso de print_fieldscon la Personclase reflectante :
int main()
{
Person p("Tom", 82);
print_fields(p);
return 0;
}
Qué salidas:
name=Tom
age=82
Y listo, acabamos de implementar la reflexión en C ++, en menos de 100 líneas de código.