Macro to count number of arguments

送分小仙女□ 提交于 2019-11-27 13:20:38

Another possibility, which does not use sizeof nor a GCC extension is to add the following to your code

#define PP_COMMASEQ_N()                                    \
          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,  0

#define PP_COMMA(...)    ,

#define PP_HASCOMMA(...)                                   \
          PP_NARG_(__VA_ARGS__, PP_COMMASEQ_N())

#define PP_NARG(...)                                       \
          PP_NARG_HELPER1(                                 \
              PP_HASCOMMA(__VA_ARGS__),                    \
              PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()),        \
              PP_NARG_(__VA_ARGS__, PP_RSEQ_N()))

#define PP_NARG_HELPER1(a, b, N)    PP_NARG_HELPER2(a, b, N)
#define PP_NARG_HELPER2(a, b, N)    PP_NARG_HELPER3_ ## a ## b(N)
#define PP_NARG_HELPER3_01(N)    0
#define PP_NARG_HELPER3_00(N)    1
#define PP_NARG_HELPER3_11(N)    N

The result is

PP_NARG()       // expands to 0
PP_NARG(x)      // expands to 1
PP_NARG(x, 2)   // expands to 2

Explanation:

The trick in these macros is that PP_HASCOMMA(...) expands to 0 when called with zero or one argument and to 1 when called with at least two arguments. To distinguish between these two cases, I used PP_COMMA __VA_ARGS__ (), which returns a comma when __VA_ARGS__ is empty and returns nothing when __VA_ARGS__ is non-empty.

Now there are three possible cases:

  1. __VA_ARGS__ is empty: PP_HASCOMMA(__VA_ARGS__) returns 0 and PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()) returns 1.

  2. __VA_ARGS__ contains one argument: PP_HASCOMMA(__VA_ARGS__) returns 0 and PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()) returns 0.

  3. __VA_ARGS__ contains two or more arguments: PP_HASCOMMA(__VA_ARGS__) returns 1 and PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()) returns 1.

The PP_NARG_HELPERx macros are just needed to resolve these cases.

Edit:

In order to fix the func(0, ) problem, we need to test whether we have supplied zero or more arguments. The PP_ISZERO macro comes into play here.

#define PP_ISZERO(x)    PP_HASCOMMA(PP_ISZERO_HELPER_ ## x)
#define PP_ISZERO_HELPER_0    ,

Now let's define another macro which prepends the number of arguments to an argument list:

#define PP_PREPEND_NARG(...)                               \
          PP_PREPEND_NARG_HELPER1(PP_NARG(__VA_ARGS__), __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER1(N, ...)                    \
          PP_PREPEND_NARG_HELPER2(PP_ISZERO(N), N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER2(z, N, ...)                 \
          PP_PREPEND_NARG_HELPER3(z, N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER3(z, N, ...)                 \
          PP_PREPEND_NARG_HELPER4_ ## z (N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER4_1(N, ...)  0
#define PP_PREPEND_NARG_HELPER4_0(N, ...)  N, __VA_ARGS__

The many helpers are again needed to expand the macros to numeric values. Finally test it:

#define my_func(...)  func(PP_PREPEND_NARG(__VA_ARGS__))

my_func()          // expands to func(0)
my_func(x)         // expands to func(1, x)
my_func(x, y)      // expands to func(2, x, y)
my_func(x, y, z)   // expands to func(3, x, y, z)

Online example:

http://coliru.stacked-crooked.com/a/73b4b6d75d45a1c8

See also:

Please have also a look at the P99 project, which has much more advanced preprocessor solutions, like these.

I came up with the following workaround for PP_NARG:

#define PP_NARG(...)     (sizeof(#__VA_ARGS__) - 1 ?       \
    PP_NARG_(__VA_ARGS__, PP_RSEQ_N()) : 0)

It stringifies __VA_ARGS__, so if it's empty its length equals 1 (because #__VA_ARGS__ == '\0').
It works with -std=c99 -pedantic.

I still have problems with wrapping the variadic function though. When __VA_ARGS__ is empty, my_func expands to func(0, ) which triggers a compilation error.

vitaut

It is possible to do in GCC using the ##VA_ARGS extension:

#define PP_ARG_N( \
          _1,  _2,  _3,  _4,  _5,  _6,  _7,  _8,  _9, _10, \
         _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \
         _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \
         _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \
         _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \
         _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \
         _61, _62, _63, N, ...) N

/* Note 63 is removed */
#define PP_RSEQ_N()                                        \
         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,  0

#define PP_NARG_(...)    PP_ARG_N(__VA_ARGS__)    

/* Note dummy first argument _ and ##__VA_ARGS__ instead of __VA_ARGS__ */
#define PP_NARG(...)     PP_NARG_(_, ##__VA_ARGS__, PP_RSEQ_N())

#define my_func(...)     func(PP_NARG(__VA_ARGS__), __VA_ARGS__)

Now PP_NARG(a, b, c) gives 3 and PP_NARG() gives 0.

Unfortunately I don't see a way to make it work in general.

ceztko

The full example on Mehrwolf's answer unfortunately doesn't compile on VS2010 nor it does on VS2015 (i tried it also on this online VS compiler). A different approach on this problem that allows to produce cross-platform code that compiles on a wide range of compilers is using non-standard extensions that are also available in MS compilers as in my answer to a similar question. Also, differently form the cited answer, the result of PP_NARG((a, b, c)) using the extensions (in both MS and gcc/clang compilers) is 1 and not 0.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!