【问题标题】:Comma in C/C++ macroC/C++ 宏中的逗号
【发布时间】: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 放在任何地方,所以我不知道你为什么说它必须是“预先处理的”。

标签: c++ c macros c-preprocessor


【解决方案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 宏调用。
    猜你喜欢
    • 2018-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-17
    相关资源
    最近更新 更多