Search code examples
cmacrosc-preprocessorvariadic-macros

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


There are plenty of questions discussing how to count __VA_ARGS__ and the problem of zero arguments (e.g. [1] and [2]). However, answers to these questions usually are either not portable, since they use the GCC specific ##__VA_ARGS__ to account for the "0 arguments" case, or they are portable, but cannot account for 0 arguments (both COUNT_ARGS() and COUNT_ARGS(something) are evaluated to 1).

Is there a solution that can count the number of arguments in __VA_ARGS__, including 0, that can work in any C compiler complying to the standard?


Solution

  • After some research, I found a blog post by Jens Gustedt named "Detect empty macro arguments" (which I found as a comment by him in this answer). Together with the counting solution found in another answer by H Walters (which is similar to this one) we can build a solution that should work in any C99 compiler. The code below is a unification of these two methods.

    One noteworthy change I made was adding extra EXPAND macros. As discussed in this question, MSVC does not expand __VA_ARGS__ like most other compilers, so an extra step of expansion is necessary.

    /* 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__)
    

    These are some example outputs:

    #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
    

    As pointed out by Jens Gustedt in his blog post, this solution has a flaw. Quote:

    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.

    So, if the list passed contains a function-like macro at the end that requires two or more arguments (such as MAC2), VAR_COUNT will fail.

    Other than that, I've tested the macros on GCC 9.3.0 and Visual Studio 2019, and it should also work with any C99 (or more recent) compiler.

    Any modification that fixes the flaw mentioned above is appreciated.