84 votes

Est-il possible d'itérer sur les arguments dans les macros variadiques ?

Je me demandais s'il est possible d'itérer sur les arguments passés à une macro variadique en C99 ou en utilisant des extensions GCC ?

Par exemple, est-il possible d'écrire une macro générique qui prend une structure et ses champs comme arguments et imprime l'offset de chaque champ dans la structure ?

Quelque chose comme ça :

struct a {
    int a;
    int b;
    int c;
};

/\* PRN\_STRUCT\_OFFSETS will print offset of each of the fields 
   within structure passed as the first argument.
\*/

int main(int argc, char \*argv\[\])
{
    PRN\_STRUCT\_OFFSETS(struct a, a, b, c);

    return 0;
}

77voto

Gregory Pakosz Points 35546

Voici mon devoir du jour, il est basé sur les astuces macro et aujourd'hui j'ai particulièrement appris sur __VA_NARG__ inventé par Laurent Deniau . Quoi qu'il en soit, l'exemple de code suivant fonctionne jusqu'à 8 champs pour des raisons de clarté. Il suffit d'étendre le code en le dupliquant si vous en avez besoin de plus (ceci parce que le préprocesseur ne permet pas la récursion, puisqu'il ne lit le fichier qu'une seule fois).

#include <stdio.h>
#include <stddef.h>

struct a
{
  int a;
  int b;
  int c;
};

struct b
{
  int a;
  int b;
  int c;
  int d;
};

#define STRINGIZE(arg)  STRINGIZE1(arg)
#define STRINGIZE1(arg) STRINGIZE2(arg)
#define STRINGIZE2(arg) #arg

#define CONCATENATE(arg1, arg2)   CONCATENATE1(arg1, arg2)
#define CONCATENATE1(arg1, arg2)  CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2)  arg1##arg2

/* PRN_STRUCT_OFFSETS will print offset of each of the fields 
 within structure passed as the first argument.
 */
#define PRN_STRUCT_OFFSETS_1(structure, field, ...) printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));
#define PRN_STRUCT_OFFSETS_2(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_1(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_3(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_2(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_4(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_3(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_5(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
 PRN_STRUCT_OFFSETS_4(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_6(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_5(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_7(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_6(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_8(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_7(structure, __VA_ARGS__)

#define PRN_STRUCT_OFFSETS_NARG(...) PRN_STRUCT_OFFSETS_NARG_(__VA_ARGS__, PRN_STRUCT_OFFSETS_RSEQ_N())
#define PRN_STRUCT_OFFSETS_NARG_(...) PRN_STRUCT_OFFSETS_ARG_N(__VA_ARGS__) 
#define PRN_STRUCT_OFFSETS_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N 
#define PRN_STRUCT_OFFSETS_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0

#define PRN_STRUCT_OFFSETS_(N, structure, field, ...) CONCATENATE(PRN_STRUCT_OFFSETS_, N)(structure, field, __VA_ARGS__)

#define PRN_STRUCT_OFFSETS(structure, field, ...) PRN_STRUCT_OFFSETS_(PRN_STRUCT_OFFSETS_NARG(field, __VA_ARGS__), structure, field, __VA_ARGS__)

int main(int argc, char *argv[])
{
  PRN_STRUCT_OFFSETS(struct a, a, b, c);
  printf("\n");
  PRN_STRUCT_OFFSETS(struct b, a, b, c, d);

  return 0;
}

qui s'imprime :

struct a:a-0
struct a:b-4
struct a:c-8

struct b:a-0
struct b:b-4
struct b:c-8
struct b:d-12

EDIT : Voici une version légèrement différente qui essaie d'être plus générique. Le site FOR_EACH(what, ...) La macro s'applique what à chaque autre argument de la liste des arguments variables.

Il suffit donc de définir une macro qui prend un seul argument comme ceci :

#define DO_STUFF(x) foo(x)

qui sera appliqué à chaque argument de la liste. Ainsi, pour votre exemple typique, vous devez bidouiller un peu mais cela reste concis :

#define PRN_STRUCT_OFFSETS_(structure, field) printf(STRINGIZE(structure)":"STRINGIZE(field)" - offset = %d\n", offsetof(structure, field));
#define PRN_STRUCT_OFFSETS(field) PRN_STRUCT_OFFSETS_(struct a, field)

Et vous l'appliquez comme ceci :

FOR_EACH(PRN_STRUCT_OFFSETS, a, b, c);

Enfin, un exemple complet de programme :

#include <stdio.h>
#include <stddef.h>

struct a
{
  int a;
  int b;
  int c;
};

#define STRINGIZE(arg)  STRINGIZE1(arg)
#define STRINGIZE1(arg) STRINGIZE2(arg)
#define STRINGIZE2(arg) #arg

#define CONCATENATE(arg1, arg2)   CONCATENATE1(arg1, arg2)
#define CONCATENATE1(arg1, arg2)  CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2)  arg1##arg2

#define FOR_EACH_1(what, x, ...) what(x)
#define FOR_EACH_2(what, x, ...)\
  what(x);\
  FOR_EACH_1(what,  __VA_ARGS__);
#define FOR_EACH_3(what, x, ...)\
  what(x);\
  FOR_EACH_2(what, __VA_ARGS__);
#define FOR_EACH_4(what, x, ...)\
  what(x);\
  FOR_EACH_3(what,  __VA_ARGS__);
#define FOR_EACH_5(what, x, ...)\
  what(x);\
 FOR_EACH_4(what,  __VA_ARGS__);
#define FOR_EACH_6(what, x, ...)\
  what(x);\
  FOR_EACH_5(what,  __VA_ARGS__);
#define FOR_EACH_7(what, x, ...)\
  what(x);\
  FOR_EACH_6(what,  __VA_ARGS__);
#define FOR_EACH_8(what, x, ...)\
  what(x);\
  FOR_EACH_7(what,  __VA_ARGS__);

#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N())
#define FOR_EACH_NARG_(...) FOR_EACH_ARG_N(__VA_ARGS__) 
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N 
#define FOR_EACH_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0

#define FOR_EACH_(N, what, x, ...) CONCATENATE(FOR_EACH_, N)(what, x, __VA_ARGS__)
#define FOR_EACH(what, x, ...) FOR_EACH_(FOR_EACH_NARG(x, __VA_ARGS__), what, x, __VA_ARGS__)

#define PRN_STRUCT_OFFSETS_(structure, field) printf(STRINGIZE(structure)":"STRINGIZE(field)" - offset = %d\n", offsetof(structure, field));
#define PRN_STRUCT_OFFSETS(field) PRN_STRUCT_OFFSETS_(struct a, field)

int main(int argc, char *argv[])
{
  FOR_EACH(PRN_STRUCT_OFFSETS, a, b, c);
  printf("\n");

  return 0;
}

2 votes

Super. Je me demandais s'il serait possible de diviser une VA_ARGS en le passant à une autre macro qui avait un argument nommé pour attraper une des VA_ARGS J'ai donc aimé cette réponse. Dommage que CPP vous oblige à écrire des macros pour chaque compte, au lieu de permettre une expansion récursive et de faire quelque chose de différent lorsqu'il n'y a plus d'args. Je ne sais pas si j'inclurais jamais une aussi grande collection de macros à moins que cela ne permette d'économiser beaucoup de code quelque part. Enfin, peut-être pour mon propre usage pendant le développement... Quoi qu'il en soit, c'est un bon truc.

1 votes

C'est un joli tour, Gregory. Je suis tombé sur le VA_NARG J'ai trouvé ce message en cherchant sur Google, mais je ne savais pas (ou j'ignorais) que l'on pouvait l'utiliser pour construire une macro de répartition basée sur le nombre d'arguments. GMan, votre approche était celle que j'avais initialement adoptée. phillipe, X-Macros est une approche intéressante. Merci à vous tous pour vos réponses.

2 votes

J'ai vu comment le double-stringize fonctionne dans stackoverflow.com/questions/2751870/ mais pourquoi STRINGIZE et CONCATENATE ont-ils trois appels ?

57voto

Marvin Points 280

Au risque de gagner un badge d'archéologue, je pense qu'il y a une amélioration mineure à la réponse de Gregory ci-dessus en utilisant la technique de Surcharge de la macro sur le nombre d'arguments

Avec foo.h :

// Make a FOREACH macro
#define FE_0(WHAT)
#define FE_1(WHAT, X) WHAT(X) 
#define FE_2(WHAT, X, ...) WHAT(X)FE_1(WHAT, __VA_ARGS__)
#define FE_3(WHAT, X, ...) WHAT(X)FE_2(WHAT, __VA_ARGS__)
#define FE_4(WHAT, X, ...) WHAT(X)FE_3(WHAT, __VA_ARGS__)
#define FE_5(WHAT, X, ...) WHAT(X)FE_4(WHAT, __VA_ARGS__)
//... repeat as needed

#define GET_MACRO(_0,_1,_2,_3,_4,_5,NAME,...) NAME 
#define FOR_EACH(action,...) \
  GET_MACRO(_0,__VA_ARGS__,FE_5,FE_4,FE_3,FE_2,FE_1,FE_0)(action,__VA_ARGS__)

// Example
// Some actions
#define QUALIFIER(X) X::
#define OPEN_NS(X)   namespace X {
#define CLOSE_NS(X)  }
// Helper function
#define QUALIFIED(NAME,...) FOR_EACH(QUALIFIER,__VA_ARGS__)NAME

// Emit some code
QUALIFIED(MyFoo,Outer,Next,Inner)  foo();

FOR_EACH(OPEN_NS,Outer,Next,Inner)
  class Foo;
FOR_EACH(CLOSE_NS,Outer,Next,Inner)

cpp foo.h génère :

Outer::Next::Inner::MyFoo foo();

namespace Outer {namespace Next {namespace Inner {
   class Foo;
}}}

3 votes

Je devais changer la définition de GET_MACRO à GET_MACRO(__VA_ARGS__,FE_5,FE_4,FE_3,FE_2,FE_1,)(action,__VA‌​_ARGS__) . Remarquez la virgule supplémentaire. Sans cela, en appliquant la macro à une liste avec un seul argument, je reçois warning: ISO C99 requires rest arguments to be used . A part ça, belle macro !

0 votes

C'est génial, vous méritez un badge d'archéologue !

6 votes

Pour ceux qui tentent de le faire avec msvc (2015 ici), ceci doit être légèrement modifié puisque msvc ne s'étend pas __VA_ARGS__ en plusieurs arguments, c'est-à-dire lorsque __VA_ARGS__ est a,b,c , FOO(X, __VA_ARGS__) devient FOO(X, (a,b,c)) au lieu de FOO(X, a, b, c) . La solution est ici : stackoverflow.com/questions/5134523/ - en bref, GET_MACRO(__VA_ARGS__, ...)(action,__VA_ARGS__) doit être réécrit comme EXPAND(GET_MACRO(__VA_ARGS__, ...)(action,__VA_ARGS__)) et le FE_X doivent être enveloppés dans un EXPAND(...) macro également.

21voto

philant Points 17345

Si votre structure est décrite avec X-Macros il est alors possible d'écrire une fonction ou une macro pour itérer sur tous les champs de la structure et imprimer leur décalage.

#include <stddef.h>   // offsetof macro

//--- first describe the structure, the fields, their types
#define X_FIELDS \
    X(int,    field1) \
    X(int,    field2) \
    X(char,   field3) \
    X(char *, field4)

//--- define the structure, the X macro will be expanded once per field
typedef struct {
#define X(type, name) type name;
    X_FIELDS
#undef X
} mystruct;

//--- "iterate" over all fields of the structure and print out their offset
void print_offset(mystruct *aStruct)
{
#define X(type, name) printf("offset of %s is %d\n", #name, offsetof(mystruct, name));
        X_FIELDS
#undef X
}

//--- demonstrate
int main(int ac, char**av)
{
    mystruct a = { 0, 1, 'a', "hello"};
    print_offset(&a);

    return 0;
}

3 votes

Elle n'obfusque que la déclaration de la structure et la fonction qui imprime les offsets, mais pas tant que ça quand on connaît l'effet de la macro X(). Mais l'avantage est que lorsque vous devez ajouter un nouveau champ à la structure, vous n'avez qu'un seul endroit à modifier, la macro X_FIELDS. Recompilez et la fonction print_offset() imprimera l'offset du nouveau champ. Ne vous répétez pas !

0 votes

Et ne s'applique que si la structure est la vôtre et que vous êtes prêt à encombrer (imho) sa définition

4 votes

Je viens d'utiliser cette approche dans un cas où je voulais avoir un enum et avoir accès aux éléments de l'enum par leur nom. Cela obfusque effectivement le code mais rend l'expérience utilisateur finale meilleure et sans contraintes.

5voto

Peter Cordes Points 1375

Peut-être utiliser les varargs comme un initialisateur de tableau, et itérer sur countof(array) ? c'est-à-dire sizeof(array)/sizeof(array[0]). Le tableau pourrait potentiellement être un tableau anonyme C99.

Je ne vois pas d'autre moyen d'itérer sur les var-args d'une macro, puisque je ne sais pas comment faire quoi que ce soit au texte de chaque élément var-arg. La partie var-arg pourrait aussi bien être un argument unique contenant des virgules, pour tout ce que vous pouvez faire avec CPP, AFAIK.

Mais voici mon idée pour itérer sur les var-args :

#define countof(a) ( sizeof(a)/sizeof((a)[0]) )
#define MACRO(fd, format, ...) do { int ar_[] = { __VA_ARGS__ }; \
for(int i=0; i<countof(ar_) ; ++i){ \
    fprintf(fd, format, ar_[i]); \
} } while(0)

1 votes

Je suis désolé mais je ne vois pas comment cet extrait répond à la question. D'abord, il manque dans le code une définition de countof bien que vous le donniez dans le premier paragraphe. Alors il devrait être int ar_[] . Enfin, elle ne fonctionnerait que si l'on invoque la macro avec une liste d'arguments variables égale à int arguments ; comme ceci MACRO(stdout, "%d", 1, 2, 3)

1 votes

Vous devez évidemment adapter la macro à votre situation. Vous pourriez faire du type l'un des paramètres de la macro. Merci d'avoir remarqué le [] manquant, cependant.

2 votes

Néanmoins, l'utilisation de ce tableau signifie 2 choses : tous les arguments passés par la liste d'arguments de la variable doivent être du même type (dans votre cas int ) et doit avoir un constructeur de copie publique

0voto

Daniel Points 49

Voici ma solution pour cela
Profitez de

#include <stdlib.h>
#include <stdio.h>

#define ITERATE_OVER_VARADICT_MACROS( str, ...)\
do{\
    int i, _arr_[] = {__VA_ARGS__};\
    fprintf(stderr,"msg =%s\n",  str); \
    for(i=0; i<sizeof(_arr_)/sizeof(int) ; i++){ \
    fprintf(stderr,"_arr_[%d]= %d\n", i, _arr_[i] ); \
    }\
}while(0)

int main(int argc, char* argv[])
{
    ITERATE_OVER_VARADICT_MACROS("Test of iterate over arguments in variadic macros", 10,12, 34);
    return 0;
}

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