60 votes

Préprocesseur C ++: évite la répétition de code de la liste des variables membres

J'ai plusieurs classes, chacune avec différentes variables de membre qui sont initialisés de façon triviale dans un constructeur. Voici un exemple:

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

Chacun est associé à un print<>() fonction.

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

Ce code est sujette aux erreurs depuis la liste des paramètres est reproduit dans quatre lieux. Comment puis-je réécrire le code pour éviter cette duplication? Je voudrais utiliser le préprocesseur et/ou des modèles.

Par exemple, pourrais-je utiliser le X-args préprocesseur technique -- quelque chose comme ça?

#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<Person>(const Person &person)
{
   LIST_COUT_LINE(ARGUMENTS)
}

#undef ARGUMENTS

Ou mieux, une approche basée sur des modèles?

Merci de ne pas poser la question pourquoi je veux le faire, il y a motivé les décisions de conception qui ont donné lieu à de multiples objets similaires avec des paramètres nommés. Les paramètres doivent être nommée membre variables pour des raisons de performances. Je suis tout simplement découvrir si il est possible de lister les paramètres et leurs types qu'une seule fois.

200voto

Paul Points 4552

Ce que vous devez faire est d'avoir le préprocesseur générer des données de réflexion sur le champs. Ces données peuvent être stockées en tant que classes imbriquées.

Tout d'abord, pour le rendre plus facile et plus propre de l'écrire dans le préprocesseur, nous allons utiliser tapé expression. Typé expression est juste une expression qui met le type entre parenthèses. Donc au lieu d'écrire int x vous écrirez (int) x. Voici quelques macros rapides pour aider à tapé expressions:

#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 un REFLECTABLE macro pour générer les données relatives à chaque domaine(ainsi que le champ lui-même). Cette macro sera appelée comme ceci:

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

Donc, à l'aide de coup de pouce.PP nous itérer sur chaque argument et de générer 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)); \
    } \
}; \

Ce qu'il fait est de générer une constante fields_n c'est le nombre de reflectable champs de la classe. Puis il se spécialise l' field_data pour chaque champ. Il a aussi des amis de l' reflector classe, c'est de sorte qu'il peut accéder aux champs, même quand 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 itérer sur les champs, nous utilisons le modèle visiteur. Nous avons créer une MPL comprise entre 0 et le nombre de champs, et d'accéder aux données de terrain à l'index. Elle transmet ensuite les données de terrain sur l'utilisateur fourni par visiteur:

struct field_visitor
{
    template<class C, class Visitor, class T>
    void operator()(C& c, Visitor v, T)
    {
        v(reflector::get_field_data<T::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));
}

Voici maintenant le moment de vérité nous mettre tout cela ensemble. Voici comment nous pouvons définir l' Person classe:

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

Ici est la généralisation de l' print_fields fonction de:

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:

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

Sorties:

name=Tom
age=82

Et voila, nous venons de mise en œuvre de réflexion en C++, en moins de 100 lignes de code.

6voto

bytemaster Points 433

J'ai résolu le même problème avec mon générique structure de code JSON.

Définir une macro: RÉFLÉCHIR( CLASS_NAME, MEMBER_SEQUENCE ) où MEMBER_SEQUENCE est (nom)(âge)(autre)(...)

Ont REFLÈTENT développer quelque chose de similaire à:

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

Vous pouvez utiliser BOOST_PP_SEQ_FOREACH pour développer la SEQ dans les visiteurs.

Définissez ensuite votre impression du visiteur:

template<typename T>
struct print_visitor {
  print_visitor( T& s ):self(s){}

  template<typename R>
  void operator( const char* name, R (T::*member) )const {
     std::cout<<name<<"= "<<self.*member<<std::endl;
  } 
  T& self;
}

template<typename T>
void print( const T& val ) {
   reflector<T>::visit( print_visitor<T>(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

4voto

Matthieu M. Points 101624

Je crains que votre solution est assez optimal pour cette réduction des cas d'utilisation. Où nous pouvons vous aider si vous avez d'autres fonctions en plus d' print , qui bénéficieraient d'une itération sur les champs.

Ceci est un exemple parfait pour Stimuler.La Fusion la Fusion de Séquences; ils peuvent être utilisés pour introduire au moment de la compilation de la réflexion. Sur le dessus de cela, vous pouvez alors générer plus de générique de comportement d'exécution.

Ainsi, par exemple, vous pouvez déclarer vos éléments à l'aide d'une Fusion.Carte (qui vous limite à une seule occurrence de chaque type) ou d'autres fantasmes.

Si votre type n'est pas conforme à une Séquence de Fusion (ou vous ne voulez pas à s'intéresser à son fonctionnement interne), il y a les cartes de l' adaptés section telle que BOOST_FUSION_ADAPT_STRUCT. Et, bien sûr, puisque tout n'est pas un struct (ou de membres du public), il y a aussi une plus version générique pour les classes, il est juste laid bientôt: BOOST_FUSION_ADAPT_ADT.

Volant de démarrage rapide:

struct print_xml {
    template <typename T>
    void operator()(T const& x) const {
        std::cout
            << '<' << typeid(x).name() << '>'
            << x
            << "</" << typeid(x).name() << '>'
            ;
    }
};

int main() {
    vector<int, char, std::string> 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());
}

Les cartes vous permettra de "s'adapter" à un type, donc, vous obtiendrez:

struct Foo { int bar; char const* buzz; };

BOOST_FUSION_ADAPT_STRUCT(
    Foo,
    (int, bar)
    (char const*, buzz)
)

Et puis:

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

C'est assez impressionnant de la bibliothèque :)

2voto

zvrba Points 14028

Pourquoi avez-vous besoin d'utiliser le préprocesseur? Introduction à la bibliothèque boost.fusion a un exemple assez similaire à votre cas d'utilisation.

1voto

Puppy Points 90818

Vous avez besoin d'un tuple, pas d'un cours. Cela résoudrait facilement tous vos problèmes sans avoir à recourir au hackery du pré-processeur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X