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

A way to count the number of __VA_ARGS__ arguments, including 0, without compiler specific constructs

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

是否有解决方案可以计算__VA_ARGS__中的参数个数,包括0,并且可以在任何符合标准的C编译器中工作?

经过一些搜索,我找到了 post 作者 Jens Gustedt 的博客,名为“Detect empty macro arguments" (which I found as a comment by him in this answer). Together with the by H Walters (which is similar to this one) 我们可以构建一个可以在任何 C99 中运行的解决方案编译器。下面的代码是这两种方法的统一。

我所做的一个值得注意的更改是添加了额外的 EXPAND 宏。正如 this question 中所讨论的,MSVC 不像大多数其他编译器那样扩展 __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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ,1, \
   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0))

#define _TRIGGER_PARENTHESIS_(...) ,
#define _PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
#define _IS_EMPTY_CASE_0001 ,
#define _IS_EMPTY(_0, _1, _2, _3) HAS_COMMA(_PASTE5(_IS_EMPTY_CASE_, _0, _1, _2, _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__)

这些是一些示例输出:

#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, b, c, d)               // 4
VAR_COUNT(a, b, c, d, e)            // 5
VAR_COUNT((a, b, c, d, e))          // 1
VAR_COUNT((void))                   // 1
VAR_COUNT((void), c, d)             // 3
VAR_COUNT((a, b), c, 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

正如 Jens Gustedt 在其博客 post 中指出的那样,此解决方案存在缺陷。引用:

In fact ISEMPTY should work when it is called with macros as argument that expect 0, 1 or a variable list of arguments. If called with a macro X as an argument that itself expects more than one argument (such as MAC2) the expansion leads to an invalid use of that macro X.

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

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

感谢任何修复上述缺陷的修改。