MSVC++ 可变参数宏扩展

Posted

技术标签:

【中文标题】MSVC++ 可变参数宏扩展【英文标题】:MSVC++ variadic macro expansion 【发布时间】:2012-02-07 21:11:21 【问题描述】:

所以我有一个在 GCC 中运行良好的宏,但在 Microsoft 的 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;

但微软出于某种原因将我所有的 __VA_ARGS__ 扩展为一个参数:

struct MyStructure

  void Foo()
  
    parent->GetProperty<Property1, Property2, Property3, Property4>();
  

  Base * parent;

有人知道这是为什么吗?有什么技巧可以让微软像 GCC 一样扩展它吗?可以多加几对括号吗?

这样的宏确实可以帮助我替换一堆“胶水”代码,但由于这个问题,我无法将它移到我的 VS 项目中。任何帮助将不胜感激!

谢谢。

【问题讨论】:

Its a [bug](http://connect.microsoft.com/VisualStudio/feedback/details/380090/variadic-macro-replacement) and I dont 认为他们计划很快修复它。 链接重复:How to fix variadic macro related issues with “macro overloading” in MSVC++ (Microsoft Visual studio)? -- @JesseGood 感谢您指出错误。 MSVC doesn't expand __VA_ARGS__ correctly的可能重复 developercommunity.visualstudio.com/content/problem/460154/… 【参考方案1】:

我知道这个问题已经有两年多了,但我想我会像我一样尝试为那些仍然偶然发现这个问题的人提供更优雅的答案。

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 基于此,这里有一个使用多达 16 个 args 来实现基于 var-args 的 ELEM 宏的示例,***.com/a/24837037/432509(可能感兴趣) 由于恕我直言,这个答案是迄今为止最好的,可能值得注意的是它(相当)便携,它适用于 MSVC、GCC、Clang(尚未检查英特尔)。 (不 MSVC)【参考方案2】:

巧合的是,我今天碰巧遇到了这个问题,经过足够的努力,我想我已经找到了适合自己目的的解决方案。该错误是 MSVC 将 __VA_ARGS__ 视为参数列表中的单个标记。但是您可以通过不在宏调用参数列表中直接使用它来解决此问题。 This comment 建议开始回答您的问题:

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

在解决了我自己的问题几个小时后,我的头脑太糊涂了,然后再去彻底解决你的问题,我很抱歉。 :-) 但我认为这足以让你得到一些有用的东西,只需一点工作。

【讨论】:

【参考方案3】:

Microsoft 已重写 C/C++ 预处理器,但默认情况下并未启用“向后兼容性”,即他们更喜欢与自己的产品兼容错误,而不是可移植性或标准合规性。

看来您可以通过添加/experimental:preprocessor flag to the command line 来修复__VA_ARGS__ handling。

【讨论】:

有兴趣的朋友,这是微软关于这个话题的官方声明:devblogs.microsoft.com/cppblog/… 自 VS2019.6 起支持/Zc:preprocessor

以上是关于MSVC++ 可变参数宏扩展的主要内容,如果未能解决你的问题,请参考以下文章

便携式可变参数宏

是否可以迭代可变参数宏中的参数?

如何在文件系统中找到可变参数宏实现

MSVC2015 更新 3 可变参数模板解决方法

可变参数宏包装器,扩展为使用与参数数量相对应的字符格式化字符串

如何制作可变参数宏(可变数量的参数)