Ce que vous devez faire, c'est que le préprocesseur génère des données de réflexion sur les champs. Ces données peuvent être stockées sous forme de classes imbriquées.
Tout d'abord, pour faciliter l'écriture dans le préprocesseur, nous utiliserons l'expression typée. Une expression typée n'est qu'une expression qui met le type entre parenthèses. Donc au lieu d'écrire, int x
vous écrirez (int) x
. Voici quelques macros pratiques pour vous aider avec les expressions typées:
#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
Ensuite, nous définissons une REFLECTABLE
macro pour générer les données sur chaque champ (plus le champ lui-même). Cette macro sera appelée comme ceci:
REFLECTABLE
(
(const char *) name,
(int) age
)
Donc, en utilisant Boost.PP, nous itérons sur chaque argument et générons les données comme ceci:
// 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)); \
} \
}; \
Cela génère une constante fields_n
qui est le nombre de champs reflétables dans la classe. Ensuite, il spécialise le field_data
pour chaque domaine. Il aime aussi la reflector
classe, c'est pour qu'il puisse accéder aux champs même lorsqu'ils sont privés:
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;
};
};
Maintenant, pour parcourir les champs, nous utilisons le modèle de visiteur. Nous créons une plage MPL de 0 au nombre de champs et accédons aux données de champ à cet index. Il transmet ensuite les données du champ au visiteur fourni par l'utilisateur:
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));
}
Maintenant, pour le moment de vérité, nous avons mis tout cela ensemble. Voici comment définir une Person
classe reflétable:
struct Person
{
Person(const char *name, int age)
:
name(name),
age(age)
{
}
private:
REFLECTABLE
(
(const char *) name,
(int) age
)
};
Voici une print_fields
fonction généralisée utilisant les données de réflexion pour parcourir les champs:
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 exemple d'utilisation print_fields
de la Person
classe réflectable :
int main()
{
Person p("Tom", 82);
print_fields(p);
return 0;
}
Quelles sorties:
name=Tom
age=82
Et voila, nous venons d'implémenter la réflexion en C ++, en moins de 100 lignes de code.