13

I have a variadic function from a third-party C library:

int func(int argc, ...);

argc indicates the number of passed optional arguments. I'm wrapping it with a macro that counts the number of arguments, as suggested here. For reading convenience, here's the macro:

#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

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

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

#define PP_NARG(...)     PP_NARG_(__VA_ARGS__, PP_RSEQ_N())

and I'm wrapping it like so:

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

The PP_NARG macro works great for functions accepting one or more arguments. For instance, PP_NARG("Hello", "World") evaluates to 2.

The problem is that when no arguments are passed, PP_NARG() evaluates to 1 instead of 0.
I understand how this macro works, but I can't come up with an idea to modify it so that it behaves correctly for this case as well.

Any ideas?


EDIT:
I have found a workaround for PP_NARG, and posted it as an answer.
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.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Eitan T
  • 32,660
  • 14
  • 72
  • 109

4 Answers4

21

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.

Mehrwolf
  • 8,208
  • 2
  • 26
  • 38
  • +1: This is neat. I like it because it doesn't involve run-time evaluation. Can you think about the rest of the problem which remains unsolved? It is described in my answer. – Eitan T Jul 31 '12 at 14:35
  • Hm, I missed that part. Edited the answer to provide a complete solution. – Mehrwolf Jul 31 '12 at 15:11
  • I've [compiled your code](http://ideone.com/m15OG), works great. Thanks for your answer, too bad I cannot vote up for this question once again. Always happy to learn new techniques with the C preprocessor :) – Eitan T Jul 31 '12 at 15:31
  • Your welcome. I also learned a lot when writing this code :-) – Mehrwolf Jul 31 '12 at 15:35
  • Good stuff. However, `PP_HASCOMMA(PP_COMMA __VA_ARGS__ ())` trick won't work for cases like: func((a,b,c)) it will tell that there is a comma: `PP_HASCOMMA(PP_COMMA(a,b,c)()) ==>> PP_HASCOMMA(,())` – Pavel P Oct 29 '12 at 22:00
  • @Pavel: Your construct fails on my machine (GCC 4.5.2) because PP_COMMA(a,b,c) can only be called with zero arguments. – Mehrwolf Nov 06 '12 at 08:39
  • @Mehrwolf Thanks for this! But I am not able to build this on MSDN. It throws me the error: error LNK2019: unresolved external symbol _PP_NARG_HELPER3_ – Kosha Jun 10 '13 at 06:10
  • 1
    @GregoryPakosz My answer must be used together with the OP's original code. Please find a working example here: http://pastebin.com/r1vJYvc8 – Mehrwolf Jun 19 '15 at 09:00
  • @Mehrwolf any resolution to the issue raised by Pavel? I'm having this problem as well. I need a solution which is robust against parentheses. – Nir Friedman Nov 25 '15 at 21:43
  • @NirFriedman: Sorry for the late answer. You can change `PP_COMMA` to `#define PP_COMMA(...) ,` so that it accepts the arguments of the form `(a,b,c)`. But then this counts as zero arguments... – Mehrwolf Dec 03 '15 at 13:48
  • The full example doesn't compile on VS2010 nor it does on VS2015 (i tried it also on [this](http://webcompiler.cloudapp.net/) online VS compiler ). I struggled a lot last year on this problem and it's strange I missed this question and answer. I ended with a couple of [macros](http://stackoverflow.com/questions/26682812/argument-counting-macro-with-zero-arguments-for-visualstudio-2010/26685339#26685339) that work on MS and gcc/clang compilers respectively, using their proprietary extensions. Also the result of `PP_NARG((a, b, c))` using these extensions is 1, not 0. – ceztko Dec 03 '15 at 23:02
  • 1
    @ceztko Thanks for the feedback. Unfortunately, I do not have access to a VS compiler right now. The preprocessor is a quite nasty thing with lots of undefined behaviour as can be seen in [this proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4220.pdf) for example. – Mehrwolf Dec 04 '15 at 13:25
3

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.

Eitan T
  • 32,660
  • 14
  • 72
  • 109
vitaut
  • 49,672
  • 25
  • 199
  • 336
3

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.

Eitan T
  • 32,660
  • 14
  • 72
  • 109
0

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.

Community
  • 1
  • 1
ceztko
  • 14,736
  • 5
  • 58
  • 73
  • I'm not sure about VS2015 (version 19.00), because this answer was accepted back in 2012... but think it compiled on VS2010 when I tried it. Also, `(a, b, c)` with parentheses counts as one argument for the `PP_NARG` macro... so 1 is the correct answer. – Eitan T Dec 05 '15 at 19:38
  • Eltan, I'm talking about Mehrwolf's answer, I'm editing my answer accordingly. That solution give 0 when tested with `(a, b, c)` and I'm sure it doesn't compile on VS 2010. I haven't tried other solutions, is yours the one with the `sizeof`? That solution unfortunately is not very suitable when you need the arguments count in the preprocessor step as it produces code that needs to be compiled and executed. My approach gives arguments count in the preprocessor step and is effectively tested with a wide range of compilers. – ceztko Dec 05 '15 at 22:37
  • I was also talking about Mehrwolf's answer. I will check it again... if you claim to have a better solution, please share it in your answer (links tend to get broken... :]) – Eitan T Dec 06 '15 at 08:43
  • @EitanT I'm not claiming it's better: I think that it is less hacky at the cost of using non standard extensions (that were added precisely to address problems like these). This choice may not fit some users. To avoid duplicating answers I prefer keeping the full code in the other post: I don't think the link will ever break as it's a permalink to another SO answer. If Stack Exchange decides to change the linking layout they will offer backward compatibility or migrate existing ones. – ceztko Dec 06 '15 at 22:34
  • You're right, but the other linked question/answer may be counted as duplicate... it basically addresses the same issue. – Eitan T Dec 07 '15 at 09:54