MSVC++ 可变宏展开

28

我有一个宏在GCC中运行得很好,但在微软的C++编译器中却不行。我希望有人可以知道解决方法,或者能够解释为什么会出现这种情况。

我确定这个宏不完全符合“标准”,但它真的可以帮助我。

这里是宏的功能示例:

#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)

这是我可能会使用该宏的方式:

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

  Base * parent;
}

以下是GCC如何扩展上述内容:

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

  Base * parent;
}

但是出于某些原因,Microsoft会将我所有的__VA_ARGS__扩展为一个参数:

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

  Base * parent;
}

有人知道为什么会这样吗?有没有什么诀窍可以让微软像GCC一样扩展它?也许再加入几对额外的括号?

类似这样的宏可能真的可以帮助我替换一堆“粘合”代码,但由于这个问题,我无法将其移到我的VS项目中。任何帮助都将不胜感激!

谢谢。


5
这是一个程序漏洞,我认为他们暂时没有计划修复它。 - Jesse Good
可能是MSVC不正确地展开__VA_ARGS__的重复问题。 - TooTone
https://developercommunity.visualstudio.com/content/problem/460154/-va-args-seems-to-be-trated-as-a-single-parameter.html - Antti Haapala -- Слава Україні
相关链接:https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly - undefined
3个回答

26

我知道这个问题已经超过两年了,但我还是想尝试给那些像我一样仍然碰巧遇到这个问题的人提供一个更加优化的答案。

Jeff Walden的回答没错,但是你必须为想要有可变参数的每个FOO宏声明FOO_CHOOSE_HELPER/1/2。我已经开发了一个抽象层来解决这个问题。考虑以下内容:

#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__))

使用这种结构,您可以将变量宏定义为以下形式:

#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__)

使用Jeff的答案,您需要定义宏如下:

#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__))

这并不是什么大问题,但我喜欢我的代码尽可能地简洁。如果你正在使用多个可变参数宏,这也可以极大地帮助减少代码重复和可能引起的复杂性。据我所知,这种方法也是可移植的。我已在许多常见编译器上进行了测试,它们都产生了相同的结果。

示例用法:

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

    ERROR("Only print a title");
    ERROR("Error title", "Extended error description");
}

注意,我需要在#define CALL_OVERLOAD的末尾删除';',否则使用gcc4.9会出现错误error: expected ')' before ';' token - ideasman42
基于此,以下是一个示例,使用多达16个参数来实现基于var-args的ELEM宏,http://stackoverflow.com/a/24837037/432509(可能会感兴趣) - ideasman42
5
由于我认为这个答案目前是最好的,可能值得注意的是它(相当)便携,适用于MSVC、GCC、Clang(尚未检查Intel)。并不仅限于MSVC。 - ideasman42
这个很好用!另一个优点是与我遇到的其他解决方案不同,不需要为其他编译器使用_MSVC_TRADITIONAL宏/在使用符合标准的预处理器的情况下用于MSVC(链接)。 - undefined

19
碰巧地,我今天刚好遇到了这个问题,经过足够的努力,我想我已经找到了适用于自己目的的解决方案。该错误是因为MSVC将__VA_ARGS__作为参数列表中的单个标记处理。但是你可以通过在宏调用参数列表中不直接使用它来解决这个问题。此评论建议从这里开始回答你的问题。
#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

但我猜你可能会遇到确保将其完全展开为你想要的实际"N",而不是例如VA_NARGS_IMPL (arg1, arg2, 5, 4, 3, 2, 1)。我发现我的代码(看起来和你的一样)必须更改为将MAC##code作为一个整体展开,然后再将其与参数列表分别组合。以下是我发现适用于我的代码:
#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")
}

我的脑袋在解决自己的问题几个小时后已经变得一片混乱,所以很抱歉无法完全帮你解决你的问题。但是我认为这已经足够让你找到一个可行的解决方案,只需要再做一点努力。:-)

9

微软已经重写了C/C++预处理器,但出于“向后兼容”的考虑,默认情况下未启用,也就是说,他们更喜欢与自己的产品保持"缺陷兼容性"而不是可移植性或标准合规性。

似乎你可以通过在命令行中添加/experimental:preprocessor 标志来修复__VA_ARGS__ 处理问题。


对于那些感兴趣的人,这是微软关于该主题的官方声明:https://devblogs.microsoft.com/cppblog/announcing-full-support-for-a-c-c-conformant-preprocessor-in-msvc/ - Rhaokiel
自VS2019.6起支持/Zc:preprocessor - linquize

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接