C / C ++宏中的逗号


103

说我们有一个这样的宏

#define FOO(type,name) type name

我们可以使用像

FOO(int, int_var);

但并非总是那么简单:

FOO(std::map<int, int>, map_var); // error: macro "FOO" passed 3 arguments, but takes just 2

我们当然可以:

 typedef std::map<int, int> map_int_int_t;
 FOO(map_int_int_t, map_var); // OK

这不是很符合人体工程学。加上类型不兼容必须加以处理。任何想法如何解决宏?


我猜想您必须转义带有使字面值有意义的字符。
Jite 2012年

至少在C ++中,您可以将typedef放在任何位置,所以我不确定为什么您说它必须“事前”。
沃恩·卡托

Answers:


108

因为尖括号也可以代表(或发生在)比较运算符<><=>=,宏扩展不能像它括号内确实忽略角括号内的逗号。(即使方括号和大括号通常以平衡对出现,这也是方括号和大括号的问题。)您可以将宏参数括在括号中:

FOO((std::map<int, int>), map_var);

问题在于,该参数在宏扩展内仍带有括号,这在大多数情况下都无法将其作为类型读取。

解决此问题的一个好技巧是在C ++中,您可以使用函数类型从带括号的类型名称中提取类型名称:

template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
FOO((std::map<int, int>), map_var);

由于形成函数类型会忽略多余的括号,因此可以在带或不带括号的情况下使用此宏,其中类型名称不包含逗号:

FOO((int), int_var);
FOO(int, int_var2);

当然,在C语言中,这不是必需的,因为类型名称不能在括号外包含逗号。因此,对于跨语言宏,您可以编写:

#ifdef __cplusplus__
template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
#else
#define FOO(t,name) t name
#endif

这太棒了。但是您如何得知的呢?我一直在尝试大量技巧,甚至从未想到函数类型可以解决此问题。
Will Custode

我记得@WilliamCustode,我一直在参考最棘手的解析问题研究函数类型和函数声明的语法,因此很幸运,我知道可以在这种情况下对类型使用多余的括号。
ecatmur

使用模板时,我发现此方法存在问题。假设我想要的代码是这样的:template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}如果在此处应用此解决方案,则宏后面的结构将成为依赖类型,并且现在在该类型上需要typename前缀。您可以添加它,但是类型推导已被破坏,因此您现在必须手动列出类型参数以调用该函数。我最终使用了Temple的方法来为逗号定义宏。它可能看起来不那么漂亮,但是效果很好。
罗杰·桑德斯

在回答一个小问题:它指出,逗号里面忽略[]{},他们都没有,只能用工作()黯然。请参阅:但是,不需要方括号或括号来平衡...
VinGarcia

不幸的是,这在MSVC中不起作用:godbolt.org/z/WPjYW8。看来MSVC不允许添加多个解析器,但无法解析它。一个不那么优雅但是更快(更少的模板实例化)的解决方案是将逗号参数包装到包装宏中:#define PROTECT(...) argument_type<void(__VA_ARGS__)>::type。现在甚至可以通过多个宏轻松传递参数,对于简单类型,您可以省略PROTECT。但是,当像这样进行评估时,函数类型将成为函数指针
Flamefire

119

如果您不能使用括号并且不喜欢Mike的SINGLE_ARG解决方案,则只需定义一个COMMA:

#define COMMA ,

FOO(std::map<int COMMA int>, map_var);

如果您想对某些宏参数进行字符串化,这也很有帮助,例如

#include <cstdio>
#include <map>
#include <typeinfo>

#define STRV(...) #__VA_ARGS__
#define COMMA ,
#define FOO(type, bar) bar(STRV(type) \
    " has typeid name \"%s\"", typeid(type).name())

int main()
{
    FOO(std::map<int COMMA int>, std::printf);
}

打印std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE"


16
#define COMMA哇,您刚刚为我节省了数小时的工作……为什么我几年前没想到。感谢您分享这个想法。这甚至允许我构建带有设置参数总数的宏。
moliad

28
加1表示惊悚片
namezero 2015年

1
@kiw如果#define STRVX(...) STRV(__VA_ARGS__)#define STRV(...) # __VA_ARGS__std::cout << STRV(type<A COMMA B>) << std::endl;则将打印type<A COMMA B>std::cout << STRVX(type<A COMMA B>) << std::endl;打印type<A , B>。(STRV适用于“可变字符串化”,STRVX适用于“扩展可变参量字符串化”。)
非用户

1
@ not-a-user是,但是使用可变参数宏,您不需要首先使用COMMA宏。那就是我最终的目的。
kiw

我从不使用它,但是+1是为了使自己变得有趣。
拉斐尔·巴普蒂斯塔

58

如果您的预处理器支持可变参数宏:

#define SINGLE_ARG(...) __VA_ARGS__
#define FOO(type,name) type name

FOO(SINGLE_ARG(std::map<int, int>), map_var);

否则,这会更加乏味:

#define SINGLE_ARG2(A,B) A,B
#define SINGLE_ARG3(A,B,C) A,B,C
// as many as you'll need

FOO(SINGLE_ARG2(std::map<int, int>), map_var);

哦,天哪...为什么?为什么不只用括号括起来?

15
@VladLazarenko:因为您不能总是将任意代码放在括号中。特别是,您不能在声明符中的类型名称周围加上括号,而这正是该参数的含义。
Mike Seymour

2
...,也是因为您可能只能修改宏定义,而不能修改所有调用它的位置(它可能不受您的控制,或者可能分散在成千上万个文件中,等等)。例如,在添加宏以接管同名函数的职责时,就会发生这种情况。
BeeOnRope

32

只需定义FOO

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

然后总是在类型参数周围用括号括起来,例如

FOO( (std::map<int, int>), map_var );

在宏定义的注释中举例说明调用当然是个好主意。


不知道为什么这么远,这是比Mike Seymours更好的解决方案。它快速,简单,对用户完全隐藏。
iFreilicht

3
@iFreilicht:一年后发布了它。;-)
干杯和hth。-Alf

5
而且因为也很难理解它的工作原理和原因
VinGarcia

@VinGarcia,您可以解释为什么/如何运行?为什么在调用时需要括号?什么UNPACK像这样使用时做) UNPACK type name?为什么type在使用时正确获取类型) UNPACK type name?到底发生了什么事?
用户

没有@user,也许Cheers和hth可以回答您
VinGarcia

4

至少有两种方法可以做到这一点。首先,您可以定义一个带有多个参数的宏:

#define FOO2(type1, type2, name) type1, type2, name

如果这样做,您可能会发现最终定义了更多的宏来处理更多的参数。

其次,您可以在参数周围加上括号:

#define FOO(type, name) type name
F00((std::map<int, int>) map_var;

如果这样做,您可能会发现多余的括号弄乱了结果的语法。


对于第一个解决方案,每个宏都必须具有不同的名称,因为宏不会过载。第二,如果您要传递类型名称,则很有可能将其用于声明变量(或typedef),因此括号会引起问题。
James Kanze 2012年

4

使用P99可以实现:

#include "p99/p99.h"
#define FOO(...) P99_ALLBUTLAST(__VA_ARGS__) P99_LAST(__VA_ARGS__)
FOO()

上面的代码仅去除了参数列表中的最后一个逗号。核对clang -E(P99需要C99编译器)。


3

简单的答案是,你做不到。这是<...>for模板参数选择的副作用;在<>也出现在不平衡背景下这样的宏观机制无法扩展到处理他们喜欢它处理括号。(例如,有些委员会成员要求使用不同的令牌(^...^),但是使用不能说服大多数问题<...>。)


2
(^...^)这是一张开心的脸:)
CygnusX1'9
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.