没有参数的可变参数宏
Variadic macro without arguments
我正在使用一些日志记录宏,它们应该打印出 __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 编译这段代码时,我得到了一个很好的输出,其中包含 class 和函数信息以及变量的名称和值。
但我也收到警告,即标准兼容代码不允许具有零可变参数。
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();
显然,使用零可变参数很危险。
- 有什么方法可以将此代码转换为符合标准的代码,同时又不影响只有一个接受零到两个参数的宏的便利性?
- 如果这不可能,有没有办法让 gcc 也编译这段代码?
其中最难的部分是区分 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__
中的逗号个数,p若__VA_ARGS__
开头为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
的末尾添加另一个逗号,以便我们始终有一个(可能为空)参数对应于 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
其他一切都与您的代码保持不变。 (这可以进一步概括,但以上内容足以证明该技术。)
我正在使用一些日志记录宏,它们应该打印出 __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 编译这段代码时,我得到了一个很好的输出,其中包含 class 和函数信息以及变量的名称和值。 但我也收到警告,即标准兼容代码不允许具有零可变参数。
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();
显然,使用零可变参数很危险。
- 有什么方法可以将此代码转换为符合标准的代码,同时又不影响只有一个接受零到两个参数的宏的便利性?
- 如果这不可能,有没有办法让 gcc 也编译这段代码?
其中最难的部分是区分 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)
注意:
-
如果
COMMA_IF_PARENS __VA_ARGS__
生成__VA_ARGS__
中的逗号数加 1 如果__VA_ARGS__
为空或以(...)
开头,COMMA_IF_PARENS __VA_ARGS__ ()
生成__VA_ARGS__
中的逗号数加 1。 (请注意,如果__VA_ARGS__
以类似函数的宏的名称结尾,这可能会失败,我们不会在此处解决该潜在问题。)
__VA_ARGS__
以 (...)
. 开头,设c为__VA_ARGS__
中的逗号个数,p若__VA_ARGS__
开头为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
的末尾添加另一个逗号,以便我们始终有一个(可能为空)参数对应于 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
其他一切都与您的代码保持不变。 (这可以进一步概括,但以上内容足以证明该技术。)