我正在使用一些日志记录宏,它们打印出__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();
显然使用零可变参数是很危险的。
答案 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
其他所有内容都与代码保持不变。 (这可以进一步推广,但上述内容足以证明该技术。)