没有参数的变量宏

时间:2015-08-17 10:07:09

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

我正在使用一些日志记录宏,它们打印出__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. 有没有什么方法可以将这些代码转换为符合标准的代码,而不会删除只有一个带有0到2个参数的宏的便利?
  2. 如果这不可能,有没有办法让gcc编译这段代码?

1 个答案:

答案 0 :(得分:6)

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

这就是诀窍:

#define COMMA_IF_PARENS(...) ,

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

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

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

请注意:

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

c __VA_ARGS__中的逗号数,如果__VA_ARGS__(...)开头, p 为1,否则为0 ,如果__VA_ARGS__为空, e 为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的末尾添加另一个逗号,以便我们始终有一个(可能为空)参数对应...的{​​{1}}参数。

总而言之,我们得到了这个:

LOG

其他所有内容都与代码保持不变。 (这可以进一步推广,但上述内容足以证明该技术。)