Luiz Martins
Luiz Martins

Reputation: 1724

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?

Upvotes: 3

Views: 2885

Answers (3)

Dúthomhas
Dúthomhas

Reputation: 10028

It is not possible to play with __VA_ARGS__ without compiler-specific feature tests.

Just to add to the collection of answers here, this is the little module I use. Requires C99, of course. Credits in the code. (I would not likely have figured this out on my own.)

c-default-arguments.h

#ifndef DUTHOMHAS_C_DEFAULT_ARGUMENTS_H
#define DUTHOMHAS_C_DEFAULT_ARGUMENTS_H

#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
    #error "Variadic Macros require C99 or better"
#endif

// Copyright 2021 Michael Thomas Greer
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
//  https://www.boost.org/LICENSE_1_0.txt )

// This is a VERY simple library to provide default argument capabilities to C functions.
// I suppose it is possible to dive very much deeper down the preprocessor rabbit hole
// and make using this really short and pretty, but the cost is enormous and this suffices
// me. Example usage:
//
//   #include <duthomhas/c-default-args.h>
//
//   int my_function( int a, int b, int c );
//   #define my_function(...) DUTHOMHAS_CALL_OVERLOAD(my_function_,__VA_ARGS__)
//   #define my_function_0()      my_function( -7, 512, 42 )
//   #define my_function_1(a)     my_function(  a, 512, 42 )
//   #define my_function_2(a,b)   my_function(  a,   b, 42 )
//   #define my_function_3(a,b,c) my_function(  a,   b,  c )
//
//   int x = my_function();            // same as: int x = my_function( -7, 512, 42 );
//   int y = my_function( 15, 4000 );  // same as: int y = my_function( 15, 4000, 42 );
//
// The equivalent construct in C++ would be:
//
//   int my_function( int a=-7, int b=512, int c=42 );
//
// Do not define my_function_N for the argument lists that are not permitted and let the
// compiler complain for you if the user did not provide a correct number of arguments.
//
//   #define my_function(...) DUTHOMHAS_CALL_OVERLOAD(my_function_,__VA_ARGS__)
//   #define my_function_1(a)     my_function( a, M_PI, 74 )
//   #define my_function_3(a,b,c) my_function( a, b,    c  )
//
//   int x = my_function();                // compile error
//   int y = my_function( 3 );             // OK
//   int y = my_function( 3, 5 );          // compile error
//   int z = my_function( 3, 5, 7 );       // OK
//
// Not only too few arguments, but too many arguments will lead to compiler error as well.
//
//   int q = my_function( 1, 2, 3, 4, 5 ); // compiler error
//
// The compile error will look something like:
//
//   "implicit declaration of function 'my_function_0' is invalid"
//   "unresolved external symbol my_function_0 referenced in function main"

#ifdef __clang__
  #pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#endif

// Francesco Pretto (ceztko) --> https://stackoverflow.com/a/26685339/2706707

#ifdef _MSC_VER // Microsoft compilers

  #define DUTHOMHAS_EXPAND(x) x
  #define DUTHOMHAS_NARGS_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, VAL, ...) VAL
  #define DUTHOMHAS_NARGS_1(...) DUTHOMHAS_EXPAND(DUTHOMHAS_NARGS_0(__VA_ARGS__,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 DUTHOMHAS_AUGMENTER(...) unused, __VA_ARGS__
  #define DUTHOMHAS_NARGS(...) DUTHOMHAS_NARGS_1(DUTHOMHAS_AUGMENTER(__VA_ARGS__))

#else // Others

  #define DUTHOMHAS_NARGS(...) DUTHOMHAS_NARGS_0(0, ## __VA_ARGS__, 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 DUTHOMHAS_NARGS_0(_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,N,...) N

#endif

// Braden Steffaniak --> https://stackoverflow.com/a/24028231/2706707

#define DUTHOMHAS_GLUE(x, y) x y

#define DUTHOMHAS_OVERLOAD_MACRO2(name, count) name##count
#define DUTHOMHAS_OVERLOAD_MACRO1(name, count) DUTHOMHAS_OVERLOAD_MACRO2(name, count)
#define DUTHOMHAS_OVERLOAD_MACRO(name, count) DUTHOMHAS_OVERLOAD_MACRO1(name, count)

#define DUTHOMHAS_CALL_OVERLOAD(name, ...) DUTHOMHAS_GLUE(DUTHOMHAS_OVERLOAD_MACRO(name, DUTHOMHAS_NARGS(__VA_ARGS__)), (__VA_ARGS__))

#endif

For just counting args, Pretto’s NARGS trick is what you are looking for.

Upvotes: 0

Eric Raible
Eric Raible

Reputation: 1

My approach is compiler-agnostic and handles any number of arguments, including 0. As far as I can tell it is completely portable. The "trailing comma" problem is handled by noting that they are allowed in compound literals.

#include <stdio.h>

#define ARGC_ARGV(type, ...)    ARGC_ARGV1(((type []){0, __VA_ARGS__})) // avoid 0-length compound literal array
#define ARGC_ARGV1(cla)         ALEN(cla) - 1, cla + (ALEN(cla) > 1)    // because implementations sadly differ
#define ALEN(array)             (sizeof(array) / sizeof(*array))

void show(char *name, int argc, int argv[])
{
    printf("%s:", name);
    for (int i = 0; i < argc; ++i)
        printf(" %d", argv[i]);
    printf("\n");
}

#define show(name, ...)     show(name, ARGC_ARGV(int, __VA_ARGS__))

int main()
{
    show("test0");
    show("test1", 1);
    show("test2", 1, 2);
    show("test3", 1, 2, 3);
    return 0;
}

Upvotes: 0

Luiz Martins
Luiz Martins

Reputation: 1724

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.

Upvotes: 9

Related Questions