web-dev-qa-db-fra.com

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

J'ai plusieurs classes chacune avec différentes variables membres qui sont initialisées trivialement 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 a une fonction print<>() associée.

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

Ce code est sujet aux erreurs car la liste des paramètres est répliquée à quatre endroits. Comment puis-je réécrire le code pour éviter cette duplication? Je souhaite utiliser le préprocesseur et/ou les modèles.

Par exemple, pourrais-je utiliser la technique du préprocesseur X-args - 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 un modèle?

Veuillez ne pas vous demander pourquoi je veux faire cela, il y a des décisions de conception raisonnées qui ont abouti à plusieurs objets similaires avec des paramètres nommés. Les paramètres doivent être nommés variables membres pour des raisons de performances. J'explore simplement s'il est possible de répertorier les paramètres et leurs types une seule fois.

59
paperjam

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. Ainsi, 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 macro REFLECTABLE 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 champ. Il amis également la classe reflector, c'est ainsi qu'il peut 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 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));
}

Maintenant, pour le moment de vérité, nous avons mis tout cela ensemble. Voici comment définir la classe Person:

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

Voici la fonction généralisée print_fields:

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;
}

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.

197
Paul Fultz II

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

Définissez une macro: REFLECT (CLASS_NAME, MEMBER_SEQUENCE) où MEMBER_SEQUENCE est (name) (age) (other) (...)

Demandez à REFLECT de s'étendre à 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 étendre la SEQ aux visiteurs.

Définissez ensuite votre visiteur imprimé:

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_ macis _reflect__typeinfo.html

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

6
bytemaster

Je crains que votre solution soit assez optimale pour cette utilisation réduite. Nous pouvons vous aider si vous avez des fonctions supplémentaires en plus de print qui bénéficieraient d'une itération sur les champs.

Ceci est un exemple parfait pour Boost.FusionFusion Sequences ; ils peuvent être utilisés pour introduire une réflexion au moment de la compilation. De plus, vous pouvez ensuite générer un comportement d'exécution plus générique.

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

Si votre type n'est pas conforme à une séquence de fusion (ou si vous ne voulez pas vous mêler de ses composants internes), il existe des adaptateurs dans la section adapté tels que BOOST_FUSION_ADAPT_STRUCT . Et bien sûr, comme tout n'est pas un struct (ou a des membres publics), il y a aussi une version plus générique pour les classes, ça devient vite laid: BOOST_FUSION_ADAPT_ADT .

Voler le 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 adaptateurs vous permettront "d'adapter" un type, vous obtiendrez donc:

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

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

Puis:

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

C'est une bibliothèque assez impressionnante :)

4
Matthieu M.

Pourquoi avez-vous besoin d'utiliser un préprocesseur? L'introduction à la bibliothèque boost.fusion a un exemple quelque peu similaire à votre cas d'utilisation.

2
zvrba

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

1
Puppy

Voici mes 2 cents en plus de la grande macro REFLECTABLE de Paul. J'avais besoin d'avoir une liste de champs vide, c'est-à-dire REFLECTABLE(), pour gérer correctement une hiérarchie d'héritage. La modification suivante gère ce cas:

// http://stackoverflow.com/a/2831966/2725810
#define REFLECTABLE_0(...)                                                     \
    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 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__) 
1
AlwaysLearning