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】:

简单的答案是你不能。这是为模板参数选择&lt;...&gt; 的副作用; &lt;&gt; 也出现在不平衡的上下文中,因此不能像处理括号一样扩展宏机制来处理它们。 (一些委员会成员曾主张使用不同的令牌,例如 (^...^),但他们无法用 &lt;...&gt; 说服大多数问题。)

【讨论】:

(^...^) 这是一张幸福的脸 :)【参考方案4】:

因为尖括号也可以表示(或出现在)比较运算符&lt;&gt;&lt;=&gt;=,所以宏扩展不能像在括号中那样忽略尖括号内的逗号。 (这也是方括号和大括号的问题,即使它们通常作为平衡对出现。)您可以将宏参数括在括号中:

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&lt;class KeyType, class ValueType&gt; void SomeFunc(FOO(std::map&lt;KeyType, ValueType&gt;) element) 如果我在此处应用此解决方案,宏后面的结构将成为依赖类型,并且类型现在需要类型名前缀。您可以添加它,但类型推导已被破坏,因此您现在必须手动列出类型参数才能调用该函数。我最终使用了 Temple 为逗号定义宏的方法。它可能看起来不那么漂亮,但效果很好。 答案的一个小问题:它指出逗号在[] 中被忽略,它们不是,它只适用于() 遗憾的是。见:However, there is no requirement for square brackets or braces to balance... 不幸的是,这在 MSVC 中不起作用:godbolt.org/z/WPjYW8。似乎 MSVC 不允许添加多个括号并且无法解析它。一个不那么优雅但更快(更少模板实例化)的解决方案是将逗号参数包装到包装宏中:#define PROTECT(...) argument_type&lt;void(__VA_ARGS__)&gt;::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&lt;int , int&gt; has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE"

【讨论】:

#define COMMA 哇,你为我节省了数小时的工作量……为什么我几年前没有想到这一点。感谢您分享这个想法。这甚至允许我构建宏来设置具有不同参数计数的函数。 恐怖加 1 @kiw 如果您使用#define STRVX(...) STRV(__VA_ARGS__)#define STRV(...) # __VA_ARGS__,那么std::cout &lt;&lt; STRV(type&lt;A COMMA B&gt;) &lt;&lt; std::endl; 将打印type&lt;A COMMA B&gt;std::cout &lt;&lt; STRVX(type&lt;A COMMA B&gt;) &lt;&lt; std::endl; 将打印type&lt;A , B&gt;。 (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&lt;int, int&gt; 参数周围的括号。这可能是宏参数中逗号问题的最终解决方案,但是,当宏参数周围没有括号时会发生什么?如果我理解正确,生成的代码将是无效的,因为它会留下一个悬空的UNPACK 宏调用。

以上是关于C/C++ 宏中的逗号的主要内容,如果未能解决你的问题,请参考以下文章

逗号运算符有啥作用?

C语言逗号表达式

c语言这个逗号表达式的结果是啥,为啥?

C/C++中逗号表达式的用法

为啥 Microsoft 的 C/C++ 编译器允许使用逗号分隔表达式的 if 语句? [复制]

C/C++,Java,PHP,JavaScript,Json数组对象赋值时,最后一个元素后面是否可以带逗号?