一种计算 __VA_ARGS__ 参数包括 0数量的方法,无需编译器特定的构造

问题描述

有很多问题讨论如何计算 __VA_ARGS__ 和零参数的问题(例如 [1][2])。然而,这些问题的答案通常不是可移植的,因为它们使用 GCC 特定的 ##__VA_ARGS__ 来解释“0 个参数”的情况,或者它们是可移植的,但不能解释 0 个参数(两个 {{1} } 和 COUNT_ARGS() 的计算结果为 COUNT_ARGS(something))。

是否有一种解决方案可以计算 1 中的参数数量包括 0,并且可以在任何符合标准的 C 编译器中运行?

解决方法

经过一番搜索,我找到了 Jens Gustedt 的一篇博文,名为“Detect empty macro arguments”(我在 this answer 中找到了他的评论)。结合 H Walterscounting solution found in another answer(类似于 this one),我们可以构建一个可以在任何 C99 编译器中工作的解决方案。下面的代码是这两种方法的统一。

我所做的一项值得注意的更改是添加了额外的 __VA_ARGS__ 宏。如 this question 中所述,MSVC 不像大多数其他编译器那样扩展 EXPAND,因此需要额外的扩展步骤。

__VA_ARGS__

这些是一些示例输出:

#define EXPAND(x) x

#define _GLUE(X,Y) X##Y
#define GLUE(X,Y) _GLUE(X,Y)

#define _ARG_100(_,\
   _100,_99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81,\
   _80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_64,_63,_62,_61,\
   _60,_59,_58,_57,_56,_55,_54,_53,_52,_51,_50,_49,_48,_47,_46,_45,_44,_43,_42,_41,\
   _40,_39,_38,_37,_36,_35,_34,_33,_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21,\
   _20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,X_,...) X_
#define HAS_COMMA(...) EXPAND(_ARG_100(__VA_ARGS__,\
   1,1,0))

#define _TRIGGER_PARENTHESIS_(...),#define _PASTE5(_0,_1,_4) _0 ## _1 ## _2 ## _3 ## _4
#define _IS_EMPTY_CASE_0001,#define _IS_EMPTY(_0,_3) HAS_COMMA(_PASTE5(_IS_EMPTY_CASE_,_0,_3))
#define IS_EMPTY(...)  \
   _IS_EMPTY(                                                               \
      /* test if there is just one argument,eventually an empty    \
         one */                                                     \
      HAS_COMMA(__VA_ARGS__),\
      /* test if _TRIGGER_PARENTHESIS_ together with the argument   \
         adds a comma */                                            \
      HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__),\
      /* test if the argument together with a parenthesis           \
         adds a comma */                                            \
      HAS_COMMA(__VA_ARGS__ (/*empty*/)),\
      /* test if placing it between _TRIGGER_PARENTHESIS_ and the   \
         parenthesis adds a comma */                                \
      HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__ (/*empty*/))      \
   )

#define _VAR_COUNT_EMPTY_1(...) 0
#define _VAR_COUNT_EMPTY_0(...) EXPAND(_ARG_100(__VA_ARGS__,\
   100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,\
   80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,\
   60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,\
   40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,\
   20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1))
#define VAR_COUNT(...) GLUE(_VAR_COUNT_EMPTY_,IS_EMPTY(__VA_ARGS__))(__VA_ARGS__)

正如 Jens Gustedt 在他的博客文章中所指出的,这个解决方案有一个缺陷。引用:

事实上,当使用宏作为期望 #define EATER0(...) #define EATER1(...),#define EATER2(...) (/*empty*/) #define EATER3(...) (/*empty*/),#define EATER4(...) EATER1 #define EATER5(...) EATER2 #define MAC0() () #define MAC1(x) () #define MACV(...) () #define MAC2(x,y) whatever VAR_COUNT() // 0 VAR_COUNT(/*comment*/) // 0 VAR_COUNT(a) // 1 VAR_COUNT(a,b) // 2 VAR_COUNT(a,b,c) // 3 VAR_COUNT(a,c,d) // 4 VAR_COUNT(a,d,e) // 5 VAR_COUNT((a,e)) // 1 VAR_COUNT((void)) // 1 VAR_COUNT((void),d) // 3 VAR_COUNT((a,b),d) // 3 VAR_COUNT(_TRIGGER_PARENTHESIS_) // 1 VAR_COUNT(EATER0) // 1 VAR_COUNT(EATER1) // 1 VAR_COUNT(EATER2) // 1 VAR_COUNT(EATER3) // 1 VAR_COUNT(EATER4) // 1 VAR_COUNT(MAC0) // 1 VAR_COUNT(MAC1) // 1 VAR_COUNT(MACV) // 1 /* This one will fail because MAC2 is not called correctly.*/ VAR_COUNT(MAC2) // error 0 或可变参数列表的参数调用 ISEMPTY 时,它应该可以工作。如果使用宏 1 作为参数调用,而该参数本身需要多个参数(例如 X),则扩展会导致该宏 MAC2 的无效使用。

因此,如果传递的列表包含具有两个或多个参数的类似函数的宏,则 X 将失败。

除此之外,我已经在 GCC 9.3.0Visual Studio 2019 上测试了宏,它也应该适用于任何 C99(或更新版本)编译器。

对修复上述缺陷的任何修改表示赞赏。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...