MSVC++ extension de macro variadique

J'ai donc une macro qui fonctionne bien dans GCC, mais pas dans le compilateur C++ de Microsoft. J'espère que quelqu'un connais une solution de contournement, ou peut-être peut m'expliquer pourquoi il se comporte de cette façon.

Je suis sûr que cette macro n'est pas exactement "standard", mais cela m'aiderait vraiment.

Voici un exemple fonctionnel de la macro:

#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(__VA_ARGS__, 5, 4, 3, 2, 1)

#define FULLY_EXPANDED(count, ...) 
  MAC ## count (__VA_ARGS__)

#define SEMI_EXPANDED(count, ...) FULLY_EXPANDED(count, __VA_ARGS__)

#define EXPAND_THESE(...) SEMI_EXPANDED(VA_NARGS(__VA_ARGS__), __VA_ARGS__)

#define ACTUAL_MACRO(x) parent->GetProperty<x>();
#define MAC1(a) ACTUAL_MACRO(a)
#define MAC2(a,b) MAC1(a) ACTUAL_MACRO(b)
#define MAC3(a,b,c) MAC2(a,b) ACTUAL_MACRO(c)
#define MAC4(a,b,c,d) MAC3(a,b,c) ACTUAL_MACRO(d)
#define MAC5(a,b,c,d,e) MAC4(a,b,c,d) ACTUAL_MACRO(e)

Voici comment je pourrais utiliser cette macro:

struct MyStructure
{
  void Foo()
  {
    EXPAND_THESE(Property1, Property2, Property3, Property4)
  }

  Base * parent;
}

Voici comment GCC développe ce qui précède:

struct MyStructure
{
  void Foo()
  {
    parent->GetProperty<Property1>(); 
    parent->GetProperty<Property2>(); 
    parent->GetProperty<Property3>(); 
    parent->GetProperty<Property4>();
  }

  Base * parent;
}

Mais Microsoft pour une raison quelconque développe tous mes_ _ VA _ ARGS_ _ comme un argument:

struct MyStructure
{
  void Foo()
  {
    parent->GetProperty<Property1, Property2, Property3, Property4>();
  }

  Base * parent;
}

Est-ce que quelqu'un sait pourquoi c'est? Y a-t-il une astuce que je peux tirer pour que Microsoft développe cela comme GCC? Peut-être ajouter quelques paires de parenthèses supplémentaires?

Des Macros comme celle-ci pourraient vraiment m'aider à remplacer un tas de code "colle", mais à cause de ce problème, Je ne peux pas le déplacer dans mon projet VS. Toute aide serait grandement appréciée!

Merci.

21
demandé sur a3f 2012-02-08 01:11:21

2 réponses

Par coïncidence, il m'est arrivé de rencontrer ce problème aujourd'hui, et après assez d'efforts, je pense avoir trouvé une solution à mes propres fins. Le bogue est que MSVC traite __VA_ARGS__ comme un seul jeton dans les listes d'arguments. Mais vous pouvez contourner cela en ne l'utilisant pas directement dans une liste d'arguments d'appel de macro. Ce commentaire suggère le début d'une réponse à vos problèmes:

#define VA_NARGS(...) VA_NUM_ARGS_IMPL_((__VA_ARGS__, 5,4,3,2,1))
#define VA_NARGS_IMPL_(tuple) VA_NUM_ARGS_IMPL tuple
#define VA_NARGS_IMPL(_1,_2,_3,_4,_5,N,...) N

Mais alors je soupçonne que vous allez probablement rencontrer la question de s'assurer que cela est complètement étendu à la "N" réel que vous voulez, et ne pas VA_NARGS_IMPL (arg1, arg2, 5, 4, 3, 2, 1), dire. J'ai trouvé que mon code (qui ressemblait au vôtre) devait changer pour développer MAC##code tout comme une unité, puis cela devait être combiné séparément avec la liste d'arguments. Voici le code que j'ai trouvé travaillé pour moi:

#define ASSERT_HELPER1(expr) singleArgumentExpansion(expr)
#define ASSERT_HELPER2(expr, explain) \
   twoArgumentExpansion(expr, explain)

/*
 * Count the number of arguments passed to ASSERT, very carefully
 * tiptoeing around an MSVC bug where it improperly expands __VA_ARGS__ as a
 * single token in argument lists.  See these URLs for details:
 *
 *   http://connect.microsoft.com/VisualStudio/feedback/details/380090/variadic-macro-replacement
 *   http://cplusplus.co.il/2010/07/17/variadic-macro-to-count-number-of-arguments/#comment-644
 */
#define COUNT_ASSERT_ARGS_IMPL2(_1, _2, count, ...) \
   count
#define COUNT_ASSERT_ARGS_IMPL(args) \
   COUNT_ASSERT_ARGS_IMPL2 args
#define COUNT_ASSERT_ARGS(...) \
   COUNT_ASSERT_ARGS_IMPL((__VA_ARGS__, 2, 1, 0))
 /* Pick the right helper macro to invoke. */
#define ASSERT_CHOOSE_HELPER2(count) ASSERT_HELPER##count
#define ASSERT_CHOOSE_HELPER1(count) ASSERT_CHOOSE_HELPER2(count)
#define ASSERT_CHOOSE_HELPER(count) ASSERT_CHOOSE_HELPER1(count)
 /* The actual macro. */
#define ASSERT_GLUE(x, y) x y
#define ASSERT(...) \
   ASSERT_GLUE(ASSERT_CHOOSE_HELPER(COUNT_ASSERT_ARGS(__VA_ARGS__)), \
               (__VA_ARGS__))

int foo()
{
  ASSERT(one); // singleArgumentExpansion(one)
  ASSERT(two, "foopy"); // twoArgumentExpansion(two, "foopy")
}

Mon esprit est trop de bouillie après quelques heures à résoudre mes propres problèmes pour ensuite aller résoudre complètement le vôtre, je suis désolé de le dire. :- ) Mais je pense que cela suffit pour vous amener à quelque chose qui fonctionne, avec un peu de travail.

17
répondu Jeff Walden 2012-02-18 04:06:05

Je sais que cette question a plus de deux ans, mais je pensais essayer de donner une réponse plus polie à ceux qui trébuchent encore sur cela, comme je l'ai fait.

La réponse de Jeff Walden fonctionne et tout, mais vous devez déclarer FOO_CHOOSE_HELPER/1 / 2 pour chaque macro FOO que vous voulez avoir des arguments variadiques. J'ai développé une couche d'abstraction pour résoudre ce problème. Considérez ce qui suit:

#define GLUE(x, y) x y

#define RETURN_ARG_COUNT(_1_, _2_, _3_, _4_, _5_, count, ...) count
#define EXPAND_ARGS(args) RETURN_ARG_COUNT args
#define COUNT_ARGS_MAX5(...) EXPAND_ARGS((__VA_ARGS__, 5, 4, 3, 2, 1, 0))

#define OVERLOAD_MACRO2(name, count) name##count
#define OVERLOAD_MACRO1(name, count) OVERLOAD_MACRO2(name, count)
#define OVERLOAD_MACRO(name, count) OVERLOAD_MACRO1(name, count)

#define CALL_OVERLOAD(name, ...) GLUE(OVERLOAD_MACRO(name, COUNT_ARGS_MAX5(__VA_ARGS__)), (__VA_ARGS__))

Avec cette architecture, vous pouvez définir des macros variadiques en tant que telles:

#define ERROR1(title) printf("Error: %s\n", title)
#define ERROR2(title, message)\
    ERROR1(title);\
    printf("Message: %s\n", message)
#define ERROR(...) CALL_OVERLOAD(ERROR, __VA_ARGS__)

#define ASSERT1(expr) singleArgumentExpansion(expr)
#define ASSERT2(expr, explain) twoArgumentExpansion(expr, explain)
#define ASSERT(...) CALL_OVERLOAD(ASSERT, __VA_ARGS__)

Avec La réponse de Jeff, vous devez définir les macros comme suit:

#define ERROR1(title) printf("Error: %s\n", title)
#define ERROR2(title, message)\
    ERROR1(title);\
    printf("Message: %s\n", message)

#define ERROR_CHOOSE_HELPER2(count) ERROR##count
#define ERROR_CHOOSE_HELPER1(count) ERROR_CHOOSE_HELPER2(count)
#define ERROR_CHOOSE_HELPER(count) ERROR_CHOOSE_HELPER1(count)

#define ERROR(...) GLUE(ERROR_CHOOSE_HELPER(COUNT_ARGS_MAX5(__VA_ARGS__)),\
    (__VA_ARGS__))

#define ASSERT1(expr) singleArgumentExpansion(expr)
#define ASSERT2(expr, explain) twoArgumentExpansion(expr, explain)

#define ASSERT_CHOOSE_HELPER2(count) ASSERT##count
#define ASSERT_CHOOSE_HELPER1(count) ASSERT_CHOOSE_HELPER2(count)
#define ASSERT_CHOOSE_HELPER(count) ASSERT_CHOOSE_HELPER1(count)

#define ASSERT(...) GLUE(ASSERT_CHOOSE_HELPER(COUNT_ARGS_MAX5(__VA_ARGS__)),\
    (__VA_ARGS__))

Ce n'est pas un gros problème, mais j'aime que mon code soit aussi concis que possible. Cela aide également de façon exponentielle, si vous utilisez plusieurs macros variadiques, à réduire la duplication de code et les complications qui peuvent en résulter. Autant que je sache, cette méthode est également portable. Je l'ai testé sur la plupart des compilateurs et ils ont produit les mêmes résultats.

Exemple d'utilisation:

int foo()
{
    ASSERT(one); // singleArgumentExpansion(one)
    ASSERT(two, "foopy"); // twoArgumentExpansion(two, "foopy")

    ERROR("Only print a title");
    ERROR("Error title", "Extended error description");
}
16
répondu Braden Steffaniak 2014-07-27 06:38:19