【问题标题】:Variadic macro without arguments不带参数的可变参数宏
【发布时间】:2015-11-09 22:20:01
【问题描述】:

我正在使用一些日志记录宏,它们应该打印出__PRETTY_FUNCTION__ 宏提供的信息,如果需要,最多可以打印两个参数的名称和值。 我的代码的简化版本看起来像

template<typename Value1, typename Value2>
void Log(std::string const& function, 
         std::string const& variable_1 = "", Value1 value_1 = Value1(0),
         std::string const& variable_2 = "", Value2 value_2 = Value2(0)) {
    std::cout << function << " " 
              << variable_1 << " " << value_1 << " "
              << variable_2 << " " << value_2 << std::endl;
}
#define LOG0() Log(__PRETTY_FUNCTION__)
#define VARIABLE(value) #value, value
#define LOG1(value) Log(__PRETTY_FUNCTION__, VARIABLE(value))
#define LOG2(value, value1) Log(__PRETTY_FUNCTION__, VARIABLE(value), VARIABLE(value1))
#define LOG(arg0, arg1, arg2, arg, ...) arg
#define CHOOSE(...) LOG(,##__VA_ARGS__, LOG2, LOG1, LOG0)
#define Debug(...) CHOOSE(__VA_ARGS__)(__VA_ARGS__)

我可以像这样使用这些宏

Debug();
int x = 0;
Debug(x);
int y = 1;
Debug(x, y);

当我用 clang 编译这段代码时,我会得到一个很好的输出,其中包含类和函数信息以及变量的名称和值。 但我也收到警告,标准兼容代码不允许有零可变参数。

warning: token pasting of ',' and __VA_ARGS__ is a GNU extension [-Wgnu-zero-variadic-macro-arguments]
#define CHOOSE(...) LOG(,##__VA_ARGS__, LOG2, LOG1, LOG0)
                        ^
warning: must specify at least one argument for '...' parameter of variadic macro [-Wgnu-zero-variadic-macro-arguments]
Debug();    

另一方面,Gcc 无法编译

error: expected primary-expression before ‘)’ token
#define LOG1(value) Log(__PRETTY_FUNCTION__, VARIABLE(value))
                                                            ^
Debug();

显然,使用零可变参数是危险的。

  1. 有什么方法可以让我将此代码转换为符合标准的代码,而不会消除只有一个宏接受零到两个参数的便利性?
  2. 如果这不可能,有没有办法让 gcc 也编译这段代码?

【问题讨论】:

  • ##__VA_ARGS__ 的行为取决于您的 CPP 版本。它可能就像在## 之前的逗号前添加一个空格一样简单:LOG( , ##__VA_ARGS__,...
  • 另外,你的宏在 g++ 4.9.2 中对我来说是一样的,除了 g++ 在没有给出值时无法推断出值的模板类型。您是否发布了完整的示例,还是缺少某些内容?

标签: c++ gcc macros clang variadic-macros


【解决方案1】:

最困难的部分是区分Debug()Debug(x)。在这两种情况下,从技术上讲,您都将单个参数传递给宏 Debug。在第一种情况下,该参数的标记序列为空,而在第二种情况下,它包含单个标记。这些情况可以用a trick due to to Jens Gustedt来区分。

这是诀窍:

#define COMMA_IF_PARENS(...) ,

如果X(...) 开头,请注意COMMA_IF_PARENS X 生成逗号,否则扩展为不包含额外(顶级)逗号的标记序列。同样,如果X 为空或以(...) 开头,则COMMA_IF_PARENS X () 生成逗号,否则扩展为不包含额外(顶级)逗号的标记序列。 (在每种情况下,标记序列还包含来自X 本身的所有顶级逗号。)

我们可以这样使用这个技巧:

#define CHOOSE(...) \
    LOG(__VA_ARGS__ \
        COMMA_IF_PARENS __VA_ARGS__ \
        COMMA_IF_PARENS __VA_ARGS__ (), \
        CHOICES)

请注意:

  • 如果__VA_ARGS__(...) 开头,则COMMA_IF_PARENS __VA_ARGS__ 产生__VA_ARGS__ 中的逗号数加1。
  • 如果__VA_ARGS__ 为空或以(...) 开头,COMMA_IF_PARENS __VA_ARGS__ () 产生__VA_ARGS__ 中的逗号数加1。 (请注意,如果 __VA_ARGS__ 以类似函数的宏的名称结尾,这可能会失败,我们在这里不解决这个潜在的问题。)

c__VA_ARGS__中逗号的个数,如果__VA_ARGS__(...)开头,p为1,否则为0,e 如果__VA_ARGS__ 为空,则为 1,否则为 0。

CHOICES 之前产生的宏参数的数量是 3 c + 2 p + e。取模 3,普通参数的逗号数为 0 或 2,如果参数列表为空,则为 1。

这给了我们6个我们关心的案例:

#define CHOICES LOG2, impossible, LOG2, LOG1, LOG0, LOG1
#define LOG(a0, a1, a2, a3, a4, a5, arg, ...) arg

但是,这并不完全奏效,因为我们需要延迟扩展 LOG(...) 宏调用,直到我们扩展 COMMA_IF_PARENS 机器之后。一种方法是:

#define LPAREN (
#define EXPAND(...) __VA_ARGS__
#define CHOOSE(...) EXPAND(LOG LPAREN COMMA_IF_PARENS [...]))

我们还应该在CHOICES 的末尾添加另一个逗号,以便我们始终有一个(可能为空)参数对应于LOG... 参数。

综上所述,我们得到了这个:

#define COMMA_IF_PARENS(...) ,
#define LPAREN (
#define EXPAND(...) __VA_ARGS__
#define CHOOSE(...) \
    EXPAND(LOG LPAREN \
      __VA_ARGS__ COMMA_IF_PARENS __VA_ARGS__ COMMA_IF_PARENS __VA_ARGS__ (), \
      LOG2, impossible, LOG2, LOG1, LOG0, LOG1, ))
#define LOG(a0, a1, a2, a3, a4, a5, arg, ...) arg

您的代码中的所有其他内容均未更改。 (这可以进一步推广,但以上足以证明该技术。)

【讨论】:

    猜你喜欢
    • 2014-03-03
    • 1970-01-01
    • 2011-08-18
    • 2013-05-21
    • 2011-09-11
    • 1970-01-01
    • 1970-01-01
    • 2018-01-13
    • 1970-01-01
    相关资源
    最近更新 更多