作为标准的扩展,
##
预处理器运算符可用于“吃掉”__VA_ARGS__
的尾随逗号。我感兴趣的所有编译器(GCC、clang、MSVC)都支持这一点。
但是,他们的处理方式并不一样。特别是,MSVC 以我不理解的方式中断(下面的示例)。 如何编写
MYLOG
宏才能在所有平台上运行?
示例程序(实时链接)
#include<cstdio>
#define LOG_HELPER(...) printf(__VA_ARGS__)
#define MYLOG(fmt, ...) LOG_HELPER("mylog: " fmt, ##__VA_ARGS__)
#define MYPRINTF(fmt, ...) printf( "printf: " fmt, ##__VA_ARGS__)
int main() {
// Works in all cases I care about (GCC, clang, MSVC)
MYPRINTF("foo\n");
// Works in GCC+clang, but not MSVC!
MYLOG("foo\n");
return 0;
}
MSVC给出的错误是:
error C2059: syntax error: ')'
事实上,查看处理后的输出,
MYLOG
行扩展为:
printf("foo\n", ) // <- Note trailing comma - bad!
有没有一种半干净的方法可以让它在所有情况下都起作用?
注意: 我的主要愿望是让它以跨平台的方式工作。我意识到
##
是非标准的,但我对“在 MSVC+GCC+clang 上工作”更感兴趣,而不是严格遵守标准。
另外:我知道
__VA_OPT__
将出现在 C++20 中,但现在这对我来说不是一个选择。
最简单的方法是根据检测到的编译器有不同的定义。请注意,如果 VA_ARGS 为空,MSVC(至少在 /Zc: 预处理器开关之前)将吃掉尾随逗号。
#define LOG_HELPER(...) printf(__VA_ARGS__)
#if defined(_MSC_VER)
#define MYLOG(fmt, ...) LOG_HELPER("mylog: " fmt, __VA_ARGS__)
#else
#define MYLOG(fmt, ...) LOG_HELPER("mylog: " fmt, ##__VA_ARGS__)
#endif
我找到了一种适合我的目的的方法:
#define MYLOG(...) LOG_HELPER("mylog: " __VA_ARGS__)
遗憾的是,它有点特定于这种特定用法(组合字符串文字)。
聚会有点晚了,但只需将
LOG_HELPER()
宏更改为以下内容似乎就可以在您关心的所有平台上运行:
#define LOG_HELPER(fmt, ...) printf(fmt, ##__VA_ARGS__)
...
不允许映射到 0 个参数。MYLOG("foo\n"); // Here the expansion of ... maps to zero parameters.
解决这个问题的简单方法是添加另一个层和一个最终忽略的人工参数。
#define LOG_HELPER_INDIRECT(fmt, ...) LOG_HELPER("mylog: " fmt, ##__VA_ARGS__)
#define MYLOG(...) LOG_HELPER_INDIRECT(__VA_ARGS__, 1)
这样,
...
始终映射到至少一个参数。聚会有点晚了,但似乎没有一个可以接受的答案,所以这里有另一个选择。
如果您向
LOG_HELPER
宏的参数列表添加另一个被忽略的前导参数,则所有编译器都会正确扩展其余参数(即使在古老的版本上,请参阅 godbolt),包括 MSVC 的传统和新预处理器。
通过编辑您的示例:(godbolt 链接)
#include<cstdio>
#define LOG_HELPER(ignored, ...) printf(__VA_ARGS__)
#define MYLOG(fmt, ...) LOG_HELPER(IGNORED, "mylog: " fmt, ##__VA_ARGS__)
#define MYPRINTF(fmt, ...) printf( "printf: " fmt, ##__VA_ARGS__)
int main() {
// Works in all cases I care about (GCC, clang, MSVC)
MYPRINTF("foo\n");
// Works in GCC+clang, ~~but not~~ AND MSVC!
MYLOG("foo\n"); // printf("mylog: " "foo\n");
MYLOG("foo %d\n", 1); // printf("mylog: " "foo %d\n",1);
return 0;
}