Jan*_*jer 7 c++ macros gcc 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__)
Run Code Online (Sandbox Code Playgroud)
我可以使用这些宏
Debug();
int x = 0;
Debug(x);
int y = 1;
Debug(x, y);
Run Code Online (Sandbox Code Playgroud)
当我用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();
Run Code Online (Sandbox Code Playgroud)
另一方面,Gcc无法编译
error: expected primary-expression before ‘)’ token
#define LOG1(value) Log(__PRETTY_FUNCTION__, VARIABLE(value))
^
Debug();
Run Code Online (Sandbox Code Playgroud)
显然使用零可变参数是很危险的.
其中最难的部分是Debug()和Debug(x).在这两种情况下,您在技术上都将一个参数传递给宏Debug.在第一种情况下,该参数的标记序列为空,而在第二种情况下,它包含单个标记.由于Jens Gustedt可以区分这些案例.
这是诀窍:
#define COMMA_IF_PARENS(...) ,
Run Code Online (Sandbox Code Playgroud)
观察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)
Run Code Online (Sandbox Code Playgroud)
注意:
COMMA_IF_PARENS __VA_ARGS____VA_ARGS__如果__VA_ARGS__以#开头,则生成加号1 的逗号数(...).COMMA_IF_PARENS __VA_ARGS__ ()__VA_ARGS__如果__VA_ARGS__为空或以...开头,则生成加号1 的逗号数(...).(请注意,如果__VA_ARGS__以类似函数的宏的名称结尾,则可能会失败,我们在此处未解决该潜在问题.)让Ç是逗号的数量__VA_ARGS__,p是1,如果__VA_ARGS__与启动(...),否则为0,和ë是1,如果__VA_ARGS__是空的,否则为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
Run Code Online (Sandbox Code Playgroud)
但是,这并不常用,因为我们需要延迟扩展LOG(...)宏调用,直到我们扩展COMMA_IF_PARENS机制之后.一种方法是:
#define LPAREN (
#define EXPAND(...) __VA_ARGS__
#define CHOOSE(...) EXPAND(LOG LPAREN COMMA_IF_PARENS [...]))
Run Code Online (Sandbox Code Playgroud)
我们还应该在末尾添加另一个逗号,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
Run Code Online (Sandbox Code Playgroud)
其他一切都没有改变你的代码.(这可以进一步推广,但上述内容足以证明该技术.)