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

标签 c macros c-preprocessor variadic-macros

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

有没有办法可以统计 __VA_ARGS__ 中的参数个数,包括0,可以在任何符合标准的C编译器中工作吗?

最佳答案

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

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

/* NOTE: In these macros, "1" means true, and "0" means false. */

#define EXPAND(x) x

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

/* Returns the 100th argument. */
#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_

/* Returns whether __VA_ARGS__ has a comma (up to 100 arguments). */
#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))

/* Produces a comma if followed by a parenthesis. */
#define _TRIGGER_PARENTHESIS_(...) ,
#define _PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
#define _IS_EMPTY_CASE_0001 ,
/* Returns true if inputs expand to (false, false, false, true) */
#define _IS_EMPTY(_0, _1, _2, _3) HAS_COMMA(_PASTE5(_IS_EMPTY_CASE_, _0, _1, _2, _3))
/* Returns whether __VA_ARGS__ is empty. */
#define IS_EMPTY(...)                                               \
   _IS_EMPTY(                                                       \
      /* Testing for an argument with a comma                       \
         e.g. "ARG1, ARG2", "ARG1, ...", or "," */                  \
      HAS_COMMA(__VA_ARGS__),                                       \
      /* Testing for an argument around parenthesis                 \
         e.g. "(ARG1)", "(...)", or "()" */                         \
      HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__),                 \
      /* Testing for a macro as an argument, which will             \
         expand the parenthesis, possibly generating a comma. */    \
      HAS_COMMA(__VA_ARGS__ (/*empty*/)),                           \
      /* If all previous checks are false, __VA_ARGS__ does not     \
         generate a comma by itself, nor with _TRIGGER_PARENTHESIS_ \
         behind it, nor with () after it.                           \
         Therefore, "_TRIGGER_PARENTHESIS_ __VA_ARGS__ ()"          \
         only generates a comma if __VA_ARGS__ is empty.            \
         So, this tests for an empty __VA_ARGS__ (given the         \
         previous conditionals are false). */                       \
      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
/* But only if it's at the end spot. */
VAR_COUNT(MACV, MAC1, MAC2)         // error
VAR_COUNT(MAC2, MAC1, MACV)         // 3

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

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.

因此,如果传递的列表末尾包含一个类似函数的宏,需要两个或多个参数(例如 MAC2 ), VAR_COUNT将会失败。

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

任何修复上述缺陷的修改都会受到赞赏。

关于c - 一种计算 __VA_ARGS__ 参数数量(包括 0)的方法,无需编译器特定的构造,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66556552/

相关文章:

c - 为什么我可以从另一个文件调用一个函数(有警告),但不能在没有声明的情况下使用另一个文件中的变量?

c - 宏替换赋值

c++ - 您可以在 C++ 中制作自定义运算符吗?

c++ - 我们可以有递归宏吗?

C++ 和 Swift 彼此不喜欢

c - 如何修复 'Segmentation fault (core dumped)'

c - Endianness 操作 - 是否有用于此的 C 库?

c - 跳入 C 中的一个 block

c - 像宏一样的函数返回一个值

macros - 在 [expr*]、path[tt*] 和 ident[tt*] 分支之后匹配单个 expr 的宏