C/C++ 宏中的逗号
Posted
技术标签:
【中文标题】C/C++ 宏中的逗号【英文标题】:Comma in C/C++ macro 【发布时间】:2012-11-30 07:36:35 【问题描述】:假设我们有一个像这样的宏
#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
这不是很符合人体工程学。必须处理 Plus 类型的不兼容性。知道如何用宏解决这个问题吗?
【问题讨论】:
我猜你必须转义具有含义的字符才能使它们成为文字。 至少在 C++ 中,你可以将 typedef 放在任何地方,所以我不知道你为什么说它必须是“预先处理的”。 【参考方案1】:如果您的预处理器支持可变参数宏:
#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);
【讨论】:
哦,天哪……为什么?为什么不直接用括号括起来? @VladLazarenko:因为您不能总是将任意代码段放在括号中。特别是,您不能在声明器中的类型名称周围加上括号,这正是这个参数的含义。 ... 也因为您可能只能修改宏 definition 而不是所有调用它的地方(可能不在您的控制之下,或者可能分布在 1000 多个文件中,等等)。例如,当添加一个宏来接管同名函数的职责时,就会发生这种情况。【参考方案2】:至少有两种方法可以做到这一点。首先,您可以定义一个带有多个参数的宏:
#define FOO2(type1, type2, name) type1, type2, name
如果你这样做,你可能会发现你最终定义了更多的宏来处理更多的参数。
其次,您可以在参数周围加上括号:
#define FOO(type, name) type name
F00((std::map<int, int>) map_var;
如果这样做,您可能会发现多余的括号会破坏结果的语法。
【讨论】:
对于第一个解决方案,每个宏都必须有不同的名称,因为宏不会重载。其次,如果你传入一个类型名称,它很有可能被用来声明一个变量(或 typedef),所以括号会引起问题。【参考方案3】:简单的答案是你不能。这是为模板参数选择<...>
的副作用; <
和 >
也出现在不平衡的上下文中,因此不能像处理括号一样扩展宏机制来处理它们。 (一些委员会成员曾主张使用不同的令牌,例如 (^...^)
,但他们无法用 <...>
说服大多数问题。)
【讨论】:
(^...^)
这是一张幸福的脸 :)【参考方案4】:
因为尖括号也可以表示(或出现在)比较运算符<
、>
、<=
和>=
,所以宏扩展不能像在括号中那样忽略尖括号内的逗号。 (这也是方括号和大括号的问题,即使它们通常作为平衡对出现。)您可以将宏参数括在括号中:
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
【讨论】:
这太棒了。但是你是怎么知道的呢?我一直在尝试大量的技巧,甚至从未想过函数类型可以解决问题。 @WilliamCustode 我记得,我一直在研究函数类型和函数声明的语法,参考最令人头疼的解析问题,所以我很幸运地知道可以应用多余的括号到该上下文中的类型。 我在使用模板时发现此方法存在问题。假设我想要的代码是这样的:template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element)
如果我在此处应用此解决方案,宏后面的结构将成为依赖类型,并且类型现在需要类型名前缀。您可以添加它,但类型推导已被破坏,因此您现在必须手动列出类型参数才能调用该函数。我最终使用了 Temple 为逗号定义宏的方法。它可能看起来不那么漂亮,但效果很好。
答案的一个小问题:它指出逗号在[]
和
中被忽略,它们不是,它只适用于()
遗憾的是。见:However, there is no requirement for square brackets or braces to balance...
不幸的是,这在 MSVC 中不起作用:godbolt.org/z/WPjYW8。似乎 MSVC 不允许添加多个括号并且无法解析它。一个不那么优雅但更快(更少模板实例化)的解决方案是将逗号参数包装到包装宏中:#define PROTECT(...) argument_type<void(__VA_ARGS__)>::type
。即使通过多个宏,现在也可以轻松传递参数,对于简单类型,您可以省略 PROTECT。然而,当像这样评估时,函数类型会变成函数指针【参考方案5】:
P99 可以做到这一点:
#include "p99/p99.h"
#define FOO(...) P99_ALLBUTLAST(__VA_ARGS__) P99_LAST(__VA_ARGS__)
FOO()
上面的代码实际上只去掉了参数列表中的最后一个逗号。检查clang -E
(P99 需要 C99 编译器)。
【讨论】:
【参考方案6】:如果您不能使用括号并且不喜欢 Mike 的 SINGLE_ARG 解决方案,只需定义一个逗号:
#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"
。
【讨论】:
#define COMMA 哇,你为我节省了数小时的工作量……为什么我几年前没有想到这一点。感谢您分享这个想法。这甚至允许我构建宏来设置具有不同参数计数的函数。 恐怖加 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
用于“扩展可变参数字符串化”。)
@not-a-user 是的,但是对于可变参数宏,您首先不需要 COMMA
宏。这就是我最终的结果。
我永远不会使用它,但 +1 是为了搞笑。【参考方案7】:
只需将FOO
定义为
#define UNPACK( ... ) __VA_ARGS__
#define FOO( type, name ) UNPACK type name
然后总是在类型参数周围加上括号来调用它,例如
FOO( (std::map<int, int>), map_var );
在宏定义的注释中举例说明调用当然是个好主意。
【讨论】:
不知道为什么会如此落后,这是一个比 Mike Seymours 更好的解决方案。它快速简单,对用户完全隐藏。 @iFreilicht:一年多之后才发布。 ;-) 而且因为也很难理解它的工作原理和原因 @VinGarcia,你能解释一下它为什么/如何工作吗?为什么调用时需要括号?UNPACK
这样使用 ) UNPACK type name
时会做什么?为什么type
在) UNPACK type name
上使用时正确获取类型?这里到底发生了什么?
我现在明白了。函数调用上的括号使预处理器不处理括号内的逗号。 UNPACK
宏删除了 std::map<int, int>
参数周围的括号。这可能是宏参数中逗号问题的最终解决方案,但是,当宏参数周围没有括号时会发生什么?如果我理解正确,生成的代码将是无效的,因为它会留下一个悬空的UNPACK
宏调用。以上是关于C/C++ 宏中的逗号的主要内容,如果未能解决你的问题,请参考以下文章