138

Simple question for which I could not find answer on the net. In variadic argument macros, how to find the number of arguments? I am okay with boost preprocessor, if it has the solution.

If it makes a difference, I am trying to convert variable number of macro arguments to boost preprocessor sequence, list, or array for further reprocessing.

5
  • Just to be clear - you are asking about variadic macros, and not the macros used to create variadic C functions? Commented Jan 23, 2010 at 19:23
  • 2
    are the arguments of the same type? if so, and if the type is known, there's a standard C solution via compound literals; if it's unknown, you could use __typeof__ to get it to work at least on some compilers Commented Jan 23, 2010 at 19:24
  • 1
    Since the discussion is about the Boost preprocessor sequence etc, it has to be C++ (which is why I retagged the Q - but failed to change the question title)...Oops; I'll fix that. Commented Jan 23, 2010 at 19:25
  • @JonathanLeffler True, Boost is a C++ library. However, Boost.Preprocessor can be used with C. AFAIK, nothing it uses is C++ specific. Commented Dec 20, 2018 at 16:13
  • Related: stackoverflow.com/questions/11761703/… Commented Mar 5, 2020 at 20:41

13 Answers 13

133

I usually use this macro to find a number of params:

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))

Full example:

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))
#define SUM(...)  (sum(NUMARGS(__VA_ARGS__), __VA_ARGS__))

void sum(int numargs, ...);

int main(int argc, char *argv[]) {

    SUM(1);
    SUM(1, 2);
    SUM(1, 2, 3);
    SUM(1, 2, 3, 4);

    return 1;
}

void sum(int numargs, ...) {
    int     total = 0;
    va_list ap;

    printf("sum() called with %d params:", numargs);
    va_start(ap, numargs);
    while (numargs--)
        total += va_arg(ap, int);
    va_end(ap);

    printf(" %d\n", total);

    return;
}

It is completely valid C99 code. It has one drawback, though - you cannot invoke the macro SUM() without params, but GCC has a solution to it - see here.

So in case of GCC you need to define macros like this:

#define       NUMARGS(...)  (sizeof((int[]){0, ##__VA_ARGS__})/sizeof(int)-1)
#define       SUM(...)  sum(NUMARGS(__VA_ARGS__), ##__VA_ARGS__)

and it will work even with empty parameter list

Sign up to request clarification or add additional context in comments.

33 Comments

UM, it won't work for the OP, he needs the size for BOOST_PP which runs on compile time.
Clever! Does it also work when sizeof(int) != sizeof(void *) ?
@Kornel Like any macro, it is evaluated at compile time. I have no idea about Boost, but anyway Boost isn't needed.
@Adam Because I cast {__VA_ARGS__} to int[], it is just int[], regardless of actual content of __VA_ARGS__
Elegant solution! Works in VS2017. The ## isn't needed in VS2017 as an empty __VA_ARGS__ will automatically remove any preceding comma.
|
120

This is actually compiler dependent, and not supported by any standard.

Here however you have a macro implementation that does the count:

#define PP_NARG(...) \
         PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
         PP_ARG_N(__VA_ARGS__)
#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

/* Some test cases */


PP_NARG(A) -> 1
PP_NARG(A,B) -> 2
PP_NARG(A,B,C) -> 3
PP_NARG(A,B,C,D) -> 4
PP_NARG(A,B,C,D,E) -> 5
PP_NARG(1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3) -> 63

15 Comments

....but now is standard in C++0x and should've been longer ago because it allows a great way to guard varadic functions from corrupted calls (ie, you can pass values after the varadic items. This is actually a way of getting the count i used to use, but i guess sizeof could work too..
Thanks! this worked in Visual Studio 2013 for me: #define EXPAND(x) x #define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,_9,N,...) N #define PP_NARG(...) EXPAND(PP_ARG_N(__VA_ARGS__, 9,8,7,6,5,4,3,2,1,0)) ```
PP_NARG() fails to return 0. The GET_ARG_COUNT() & Y_TUPLE_SIZE() solutions work.
"PP_NARG() fails to return 0" ...isn't necessarily a problem. One can say that PP_NARG() should return 1 for the same reason PP_NARG(,) should return 2. Detecting 0 may indeed be handy in some cases, but the solutions seem to either be less general (requiring that first token to be pasteable; which may or may not be okay depending on what you're using it for), or implementation specific (such as requiring gnu's comma-removing-paste trick).
Duplicate/related answers: 1) stackoverflow.com/questions/11761703/… and 2) (this one helps me see what's happening easier initially since it's a shorter macro): stackoverflow.com/questions/11761703/…
|
73

If you are using C++11, and you need the value as a C++ compile-time constant, a very elegant solution is this:

#include <tuple>

#define MACRO(...) \
    std::cout << "num args: " \
    << std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value \
    << std::endl;

Please note: the counting happens entirely at compile time, and the value can be used whenever compile-time integer is required, for instance as a template parameter to std::array.

7 Comments

Great solution! And unlike sizeof((int[]){__VA_ARGS__})/sizeof(int) suggested above, it works even when the arguments cannot all be cast to int.
Doesn't work with templates, i.e. NUMARGS( sum<1,2> ); see godbolt.org/z/_AAxmL
I... think that might actually be a point in favour of it, @jorgbrown, at least in most cases where it'd come up. Since it relies on the compiler instead of the preprocessor to do the counting, it gives the number of arguments as seen by the compiler, which will likely match up with what most programmers expect. It will cause trouble if you expect it to take preprocessor greediness into account, though.
Superb answer. You can put it into a macro #define NUM_ARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
Sadly using std functions or templates requires your arguments being counted to be valid (defined and well formed in syntax), the PP_NARG(...) solution works perfect when auto blah = tensor_type<float, PP_NARG(batch size, num adj, 2(f0,f1))>(foo, bar);
|
32

For convenience, here's an implementation that works for 0 to 70 arguments, and works in Visual Studio, GCC, and Clang. I believe it will work in Visual Studio 2010 and later, but have only tested it in VS2013-2022.

Update January 2023: I have tested and confirmed that the non-Microsoft variant will now work in Visual Studio 2019/2022 when using the /Zc:preprocessor compiler option, which provides a fully conformant C11 and C17 preprocessor. You can use the _MSVC_TRADITIONAL preprocessor symbol to test if this newer compliant preprocessor is being used (I have not updated the example below to do this, however).

#ifdef _MSC_VER // Microsoft compilers

#   define GET_ARG_COUNT(...)  INTERNAL_EXPAND_ARGS_PRIVATE(INTERNAL_ARGS_AUGMENTER(__VA_ARGS__))

#   define INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__
#   define INTERNAL_EXPAND(x) x
#   define INTERNAL_EXPAND_ARGS_PRIVATE(...) INTERNAL_EXPAND(INTERNAL_GET_ARG_COUNT_PRIVATE(__VA_ARGS__, 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, 0))
#   define INTERNAL_GET_ARG_COUNT_PRIVATE(_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, _64, _65, _66, _67, _68, _69, _70, count, ...) count

#else // Non-Microsoft compilers

#   define GET_ARG_COUNT(...) INTERNAL_GET_ARG_COUNT_PRIVATE(0, ## __VA_ARGS__, 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, 0)
#   define INTERNAL_GET_ARG_COUNT_PRIVATE(_0, _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, _64, _65, _66, _67, _68, _69, _70, count, ...) count

#endif

static_assert(GET_ARG_COUNT() == 0, "GET_ARG_COUNT() failed for 0 arguments");
static_assert(GET_ARG_COUNT(1) == 1, "GET_ARG_COUNT() failed for 1 argument");
static_assert(GET_ARG_COUNT(1,2) == 2, "GET_ARG_COUNT() failed for 2 arguments");
static_assert(GET_ARG_COUNT(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, 64, 65, 66, 67, 68, 69, 70) == 70, "GET_ARG_COUNT() failed for 70 arguments");

8 Comments

IMHO the Microsoft variant fails for zero arguments.
@Vroomfondel the Microsoft variant does work for zero arguments. The very first static_assert in example above is a specific test for the zero-argument case, and I just compiled and ran it on Visual Studio 2017 v15.8.9.
Interesting - using the the Microsoft variant on a non-Microsoft compiler does not work - do you know what the M$ preprocessor does differently that makes code work the opposite way? BTW I tried C, not C++;
I believe that's because MSVC is a bit nicer about "zero-length __VA_ARGS__" (which in C++, is technically a (nigh-universal, de facto standard) compiler extension up until C++20). Most (all?) compilers allow zero-length, but choke on the trailing comma if the list is empty (and overload ## as a proto-__VA_OPT__, to remove the comma in this case); MSVC's version of the extension just doesn't choke on the comma (but will choke on the overloaded ##). Compare MSVC unused, __VA_ARGS__ to non-MSVC 0, ## __VA_ARGS__; neither is more correct, the problem is that they're different.
Unfortunately gcc does not accept 0 arguments without the extension: at least -std=gnu++11 option must be set.
|
24

There are some C++11 solutions for finding the number of arguments at compile-time, but I'm surprised to see that no one has suggested anything so simple as:

#define VA_COUNT(...) detail::va_count(__VA_ARGS__)

namespace detail
{
    template<typename ...Args>
    constexpr std::size_t va_count(Args&&...) { return sizeof...(Args); }
}

This doesn't require inclusion of the <tuple> header either.

6 Comments

"but why not just use a variadic template and sizeof... instead (as in my own answer)" c++ has become a monster. It has too many features and many of them, like variadic templates, are seldom used. You read about it, you write some examples and then you forget it. Therefore, it is hard to come up with the right idea at the right time. Since your solution seems to be a better option than mine, I will let the natural selection work and I will delete my solution.
@ZDF understandable, but I happen to use variadic templates constantly. My programs have become much more robust since C++11, and this is one of the main reasons why. No need to delete your answer though, I think.
It won't work with smth like VA_COUNT(&,^,%). Also, if you are counting via a funtion, I don't see any sence in making a macro.
This solution remains a question: the parameters of VA_COUNT are all identifiers that not yet defined as a variable or something, and it causes error '*** variable is not defined'. Is there any way to fix this?
@ChefGladiator I included the macro in my answer merely to parallel the standard variadic argument macros, though like @Qwertiy points out it doesn't cover some more abstract use cases. Also "in the context of standard C++", you may want to use the C++ headers <cstdlib> and <cassert> rather than the C headers that pull things into the global namespace.
|
11

this works with 0 arguments with gcc/llvm. [links are dumb]

/*
 * we need a comma at the start for ##_VA_ARGS__ to consume then
 * the arguments are pushed out in such a way that 'cnt' ends up with
 * the right count.  
 */
#define COUNT_ARGS(...) COUNT_ARGS_(,##__VA_ARGS__,6,5,4,3,2,1,0)
#define COUNT_ARGS_(z,a,b,c,d,e,f,cnt,...) cnt

#define C_ASSERT(test) \
    switch(0) {\
      case 0:\
      case test:;\
    }

int main() {
   C_ASSERT(0 ==  COUNT_ARGS());
   C_ASSERT(1 ==  COUNT_ARGS(a));
   C_ASSERT(2 ==  COUNT_ARGS(a,b));
   C_ASSERT(3 ==  COUNT_ARGS(a,b,c));
   C_ASSERT(4 ==  COUNT_ARGS(a,b,c,d));
   C_ASSERT(5 ==  COUNT_ARGS(a,b,c,d,e));
   C_ASSERT(6 ==  COUNT_ARGS(a,b,c,d,e,f));
   return 0;
}

Visual Studio seems to be ignoring the ## operator used to consume the empty argument. You can probably get around that with something like

#define CNT_ COUNT_ARGS
#define PASTE(x,y) PASTE_(x,y)
#define PASTE_(x,y) x ## y
#define CNT(...) PASTE(ARGVS,PASTE(CNT_(__VA_ARGS__),CNT_(1,##__VA_ARGS__)))
//you know its 0 if its 11 or 01
#define ARGVS11 0
#define ARGVS01 0
#define ARGVS12 1
#define ARGVS23 2
#define ARGVS34 3

5 Comments

I tested this for Visual Studio 2008 and it did not work for 0 arguments COUNT_ARGS() = 1.
The link seems broken.
fixed link. VS must be doing something different as usual :). I don't think they are going to support C99 fully any time soon.
Er, ##__VA_ARGS__ eating the comma before if __VA_ARGS__ is empty is a GCC extension. It's not the standard behavior.
It will not work with GCC if you use -std=c99.
6

With msvc extension:

#define Y_TUPLE_SIZE(...) Y_TUPLE_SIZE_II((Y_TUPLE_SIZE_PREFIX_ ## __VA_ARGS__ ## _Y_TUPLE_SIZE_POSTFIX,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 Y_TUPLE_SIZE_II(__args) Y_TUPLE_SIZE_I __args

#define Y_TUPLE_SIZE_PREFIX__Y_TUPLE_SIZE_POSTFIX ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0

#define Y_TUPLE_SIZE_I(__p0,__p1,__p2,__p3,__p4,__p5,__p6,__p7,__p8,__p9,__p10,__p11,__p12,__p13,__p14,__p15,__p16,__p17,__p18,__p19,__p20,__p21,__p22,__p23,__p24,__p25,__p26,__p27,__p28,__p29,__p30,__p31,__n,...) __n

Works for 0 - 32 arguments. This limit can be easily extended.

EDIT: Simplified version (works in VS2015 14.0.25431.01 Update 3 & gcc 7.4.0) up to 100 arguments to copy & paste:

#define COUNTOF(...) _COUNTOF_CAT( _COUNTOF_A, ( 0, ##__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, 0 ) )
#define _COUNTOF_CAT( a, b ) a b
#define _COUNTOF_A( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9,\
    a10, a11, a12, a13, a14, a15, a16, a17, a18, a19,\
    a20, a21, a22, a23, a24, a25, a26, a27, a28, a29,\
    a30, a31, a32, a33, a34, a35, a36, a37, a38, a39,\
    a40, a41, a42, a43, a44, a45, a46, a47, a48, a49,\
    a50, a51, a52, a53, a54, a55, a56, a57, a58, a59,\
    a60, a61, a62, a63, a64, a65, a66, a67, a68, a69,\
    a70, a71, a72, a73, a74, a75, a76, a77, a78, a79,\
    a80, a81, a82, a83, a84, a85, a86, a87, a88, a89,\
    a90, a91, a92, a93, a94, a95, a96, a97, a98, a99,\
    a100, n, ... ) n

6 Comments

is it just me or does this kinda break the code smell rules..?
It works for me with VC++ up to at least VS2012, and GCC and clang as well in my basic testing.
@osirisgothra, exactly why it smells?
While this macro has wide compilers support, it doesn't work with macro arguments such a string, like Y_TUPLE_SIZE("Hello"), making it quite infeasible. I agree with @osirisgothra.
This macro can work for you but has serious defects. I did a lot of research and found cleaner approaches that work in GCC and VS. You can find them in my answer to a similar question.
|
5

I'm assuming that each argument to __VA_ARGS__ will be comma separated. If so I think this should work as a pretty clean way to do this.

#include <cstring>

constexpr int CountOccurances(const char* str, char c) {
    return str[0] == char(0) ? 0 : (str[0] == c) + CountOccurances(str+1, c);
}

#define NUMARGS(...) (CountOccurances(#__VA_ARGS__, ',') + 1)

int main(){
    static_assert(NUMARGS(hello, world) == 2, ":(")  ;
    return 0;
}

Worked for me on godbolt for clang 4 and GCC 5.1. This will compute at compile time, but won't evaluate for the preprocessor. So if you are trying to do something like making a FOR_EACH, then this won't work.

3 Comments

This answer is underrated. It'll work even for NUMARGS(hello, world = 2, ohmy42, !@#$%^&*()-+=) !!! Each arg string can't have some other symbols like ',' though
Needs to be tweaked for parens, because int count = NUMARGS( foo(1, 2) ); produces 2 rather than 1. godbolt.org/z/kpBuOm
This won't work as expected with lambdas, function calls or anything else that may contain extra commas in the parameters.
5

herein a simple way to count 0 or more arguments of __VA_ARGS__, my example assumes a maximum of 5 variables, but you can add more if you want.

#define VA_ARGS_NUM_PRIV(P1, P2, P3, P4, P5, P6, Pn, ...) Pn
#define VA_ARGS_NUM(...) VA_ARGS_NUM_PRIV(-1, ##__VA_ARGS__, 5, 4, 3, 2, 1, 0)


VA_ARGS_NUM()      ==> 0
VA_ARGS_NUM(19)    ==> 1
VA_ARGS_NUM(9, 10) ==> 2
         ...

2 Comments

Unfortunately the approach works incorrectly when VA_ARGS_NUM is used with macro: if I have #define TEST (i.e. empty TEST) and VA_ARGS_NUM(TEST) doesn't return 0 (zero) when used in #if :(
@AntonK can you post what you have done exactly please?
3

I've found answers here still are incomplete.

The most closest portable implementation I've found from here is: C++ preprocessor __VA_ARGS__ number of arguments

But it doen't work with the zero arguments in the GCC without at least -std=gnu++11 command line parameter.

So I decided to merge this solution with that: https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

#define UTILITY_PP_CONCAT_(v1, v2) v1 ## v2
#define UTILITY_PP_CONCAT(v1, v2) UTILITY_PP_CONCAT_(v1, v2)

#define UTILITY_PP_CONCAT5_(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4

#define UTILITY_PP_IDENTITY_(x) x
#define UTILITY_PP_IDENTITY(x) UTILITY_PP_IDENTITY_(x)

#define UTILITY_PP_VA_ARGS_(...) __VA_ARGS__
#define UTILITY_PP_VA_ARGS(...) UTILITY_PP_VA_ARGS_(__VA_ARGS__)

#define UTILITY_PP_IDENTITY_VA_ARGS_(x, ...) x, __VA_ARGS__
#define UTILITY_PP_IDENTITY_VA_ARGS(x, ...) UTILITY_PP_IDENTITY_VA_ARGS_(x, __VA_ARGS__)

#define UTILITY_PP_IIF_0(x, ...) __VA_ARGS__
#define UTILITY_PP_IIF_1(x, ...) x
#define UTILITY_PP_IIF(c) UTILITY_PP_CONCAT_(UTILITY_PP_IIF_, c)

#define UTILITY_PP_HAS_COMMA(...) UTILITY_PP_IDENTITY(UTILITY_PP_VA_ARGS_TAIL(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0))
#define UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_(...) ,

#define UTILITY_PP_IS_EMPTY(...) UTILITY_PP_IS_EMPTY_( \
    /* test if there is just one argument, eventually an empty one */ \
    UTILITY_PP_HAS_COMMA(__VA_ARGS__),                                \
    /* test if _TRIGGER_PARENTHESIS_ together with the argument adds a comma */ \
    UTILITY_PP_HAS_COMMA(UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_ __VA_ARGS__), \
    /* test if the argument together with a parenthesis adds a comma */ \
    UTILITY_PP_HAS_COMMA(__VA_ARGS__ ()),                             \
    /* test if placing it between _TRIGGER_PARENTHESIS_ and the parenthesis adds a comma */ \
    UTILITY_PP_HAS_COMMA(UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_ __VA_ARGS__ ()))

#define UTILITY_PP_IS_EMPTY_(_0, _1, _2, _3) UTILITY_PP_HAS_COMMA(UTILITY_PP_CONCAT5_(UTILITY_PP_IS_EMPTY_IS_EMPTY_CASE_, _0, _1, _2, _3))
#define UTILITY_PP_IS_EMPTY_IS_EMPTY_CASE_0001 ,

#define UTILITY_PP_VA_ARGS_SIZE(...) UTILITY_PP_IIF(UTILITY_PP_IS_EMPTY(__VA_ARGS__))(0, UTILITY_PP_VA_ARGS_SIZE_(__VA_ARGS__, UTILITY_PP_VA_ARGS_SEQ64()))
#define UTILITY_PP_VA_ARGS_SIZE_(...) UTILITY_PP_IDENTITY(UTILITY_PP_VA_ARGS_TAIL(__VA_ARGS__))

#define UTILITY_PP_VA_ARGS_TAIL(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14, x, ...) x
#define UTILITY_PP_VA_ARGS_SEQ64() 15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0

#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

static_assert(UTILITY_PP_VA_ARGS_SIZE() == 0, "1");
static_assert(UTILITY_PP_VA_ARGS_SIZE(/*comment*/) == 0, "2");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a) == 1, "3");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b) == 2, "4");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c) == 3, "5");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c, d) == 4, "6");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c, d, e) == 5, "7");
static_assert(UTILITY_PP_VA_ARGS_SIZE((void)) == 1, "8");
static_assert(UTILITY_PP_VA_ARGS_SIZE((void), b, c, d) == 4, "9");
static_assert(UTILITY_PP_VA_ARGS_SIZE(UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_) == 1, "10");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER0) == 1, "11");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER1) == 1, "12");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER2) == 1, "13");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER3) == 1, "14");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER4) == 1, "15");
static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC0) == 1, "16");
// a warning in msvc
static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC1) == 1, "17");
static_assert(UTILITY_PP_VA_ARGS_SIZE(MACV) == 1, "18");
// This one will fail because MAC2 is not called correctly
//static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC2) == 1, "19");

https://godbolt.org/z/3idaKd

  • c++11, msvc 2015, gcc 4.7.1, clang 3.0

1 Comment

Thanks for pointing out the gnu standard. It solved my problem :D, unlike other answers...
2

Boost Preprocessor actually has this as of Boost 1.49, as BOOST_PP_VARIADIC_SIZE(...). It works up to size 64.

Under the hood, it's basically the same as Kornel Kisielewicz's answer.

1 Comment

@CarloWood Indeed. The preprocessor doesn't really have the concept of "zero arguments". What we think of as "zero arguments" is "one empty argument" in the preprocessor. But it's fixable using C++20's __VA_OPT__ or the compiler extensions for ##__VA_ARGS__ removing the previous comma, e.g.: godbolt.org/z/X7OvnK
0

Edit: HolyBlackCat and Ted Lyngmo made this code a lot simpler

Try this

#include <iostream>

template<typename ...TArgs>
constexpr size_t GetArgsCount(TArgs&&...)
{
    return sizeof...(TArgs);
}

#define GET_ARGS_COUNT(...)\
    GetArgsCount(__VA_ARGS__)

int main()
{
    std::cout << GET_ARGS_COUNT() << '\n';                // 0
    std::cout << GET_ARGS_COUNT(false) << '\n';           // 1
    std::cout << GET_ARGS_COUNT(1, 2, 3) << '\n';         // 3
    std::cout << GET_ARGS_COUNT(1, "2", 3, 'c') << '\n';  // 4

    return 0;
}

3 Comments

The second version can be greatly simplified to return (0 + ... + (void(args), 1)); or something similar.
That is some beautiful code, thank you HolyBlackCat
What about return sizeof...(args);?
-1

You can stringfy and count tokens:

int countArgs(char *args)
{
    int result = 0;
    int i = 0;
         
    while (isspace(args[i])) 
        ++i;
    if (args[i]) 
        ++result;
     
    while (args[i++]) 
    {
        if (args[i] == ',') 
            ++result;
        else if (args[i] == '\'') 
            i += 2;
        else if (args[i] == '\"') 
            while (args[i++]) 
                if (args[i + 1] == '\"' && args[i] != '\\') 
                    break;
    }
 
    return result;
}

#define MACRO(...) \
{ \
    int count = countArgs(#__VA_ARGS__); \
    printf("NUM ARGS: %d\n",count); \
}

1 Comment

Just had a look at the edit pending on this answer - it appears you might have got two accounts. If you stick to one, you'll be able to edit your own posts without it going for approval.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.