3

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?

asked Mar 10, 2021 at 0:11
13
  • 1
    This strikes me as a bit of an XY problem: meta.stackexchange.com/questions/66377/what-is-the-xy-problem (e.g.) For a varargs function (i.e. callee) there is no way to know how many args with just that. (e.g.) For printf, the format string details the arg count by the number of % in it. Or, the use of a sentinel value at the end (e.g. NULL if passing pointers). Assuming you could do this, what is the usage for it? I'm sure there's a better way that doesn't involve the massive contortions found in your answer. I love obscure macros but I'd never do this one. Commented Mar 10, 2021 at 0:47
  • @CraigEstey "there is no way to know how many args with just that". I don't think that is true, as my solution (along with other solutions with ##__VA_ARGS__) can find it. The use can be found here. If you want to create a "foreach" macro, you need this AFAIK. Commented Mar 10, 2021 at 1:34
  • Show the usage you intend for this. For the moment, forget about the implementation of the macro. Assume you've the the macro, show the example program that uses it. I'll bet anything you show me, I can come up with a cleaner/alternate solution. In fact, forget CPP altogether. Write a metaprogram [in (e.g.) perl/python] that scans [and modifies] the source. The macro is a solution to a problem. What is the problem? It is not "I need to count the number of arguments in a varargs macro"--that is [still] a solution [to a need]. Commented Mar 10, 2021 at 1:39
  • @CraigEstey There are plenty of problems in the same vein and none have a XY problem. The macro is the point. It just happens that the answers provided did not qualify on criteria that I found important (i.e. compiling in any compiler, and counting independent of the number), so I created a stricter question. Commented Mar 10, 2021 at 2:43
  • @CraigEstey However, if you are interested in why I need the macro, I want to transform a variadic function-wannabe macro like foo('a', 1, 1.2) into foo('a', CHAR_SIGNATURE, 1, INT_SIGNATURE, 1.2, FLOAT_SIGNATURE), independent of the number of arguments passed. Why you may ask? Because I want to dynamically typecheck variadic functions. Why you may ask? Because I'm working on a object oriented system with polymorphic methods in pure C (so metaprogramming will not suffice). Why? cuz I got mad at c++ ;). Commented Mar 10, 2021 at 2:44

3 Answers 3

9

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.

answered Mar 10, 2021 at 0:11
Sign up to request clarification or add additional context in comments.

1 Comment

That's ingenious and also horrifying. The code should really be in a spoiler box, with a warning that anyone who has just eaten should not click.
0

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;
}
answered May 16, 2024 at 3:19

Comments

0

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
// Microsoft compilers using the traditional preprocessor
#if defined(_MSC_VER) && (!defined(_MSVC_TRADITIONAL) || (_MSVC_TRADITIONAL == 1))
 #define DUTHOMHAS_NARGS_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_NARGS_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.

answered May 16, 2024 at 5:20

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.