
A Better Varargs - Anilm3
http://codeacumen.info/post/a-better-varargs
======
ryanprichard
The example does not actually use compound literals. (i.e. _arg's initializer
would work in C89.) It does use two other C99 features, though -- variadic
macros and a for-loop-scoped variable declaration. The biggest compatibility
issue, though, are its use of GCC extensions:

\- Statement expressions: e.g.: int x = ({ int y = 0; y; });

\- If zero-args are allowed (e.g. bar()), then _args is 0-sized, which is also
non-standard.

These GCC extensions are somewhat common. Clang has them, and (I think) EDG's
frontend does too. MSVC does not have either of them, even in MSVC 2015.

That said, a compound literal might be a good way to remove the extension use,
but as long as bar() returns void, the do-while(0) trick is also sufficient
for replacing the statement expression.

I think this compound literal usage works:

    
    
      #define bar(...) (bar( \
          sizeof((const char*[]) { NULL, __VA_ARGS__ }) / sizeof(const char*) - 1, \
          (const char*[]) { NULL, __VA_ARGS__ } + 1))
    

bar() results in trailing commas. My impression is that C99 allows them in
array initializers. (I saw an example in the n1256 C draft.) I don't recall
whether C89 also had them. __VA_ARGS__ is expanded twice, but I think that's
OK, because sizeof() only evaluates its operand when it has VLA type, and it
never will. This code will work in MSVC 2013 (if not earlier).

~~~
nkurz
I'm not familiar with MSVC, but when I tried this example:

    
    
      #include <stdio.h>
    
      void bar(int n, const char *p[]) {
        for (int i = 0; i < n; i++) {
          printf("%s\t", p[i]);
        }
        putchar('\n');
      }
    
      #define bar(...) (bar( \
      sizeof((const char*[]) { NULL, __VA_ARGS__ }) / sizeof(const char*) - 1, \
      (const char*[]) { NULL, __VA_ARGS__ } + 1))
    
      int main(/* int argc, char **argv */) {
        bar();
        bar("a");
        bar("a", "b");
        bar("a", "b", "c");
        return 0;
      }
    

Using the online MSVC compiler at
[http://webcompiler.cloudapp.net/](http://webcompiler.cloudapp.net/) (which
runs Visual C++ 19.00.23720.0), it failed with these error message:

    
    
      Compiled with  /EHsc /nologo /W4 /c
      main.cpp
      main.cpp(20): error C4576: a parenthesized type followed by    an 
      initializer list is a non-standard explicit type conversion syntax
      main.cpp(21): error C4576: a parenthesized type followed by an initializer
      list is a non-standard explicit type conversion syntax
      main.cpp(22): error C4576: a parenthesized type followed by an initializer list
      is a non-standard explicit type conversion syntax
      main.cpp(23): error C4576: a parenthesized type followed by an initializer list
      is a non-standard explicit type conversion syntax

~~~
nkurz
If you don't need the return value, the do-while approach works as you
suggested:

    
    
      #define bar(...) do {		                   	 \
    	const char *_args[] = {NULL, __VA_ARGS__};       \
    	bar(sizeof(_args)/sizeof(*_args) - 1, _args + 1);\
      } while (0)
    

If for some absurd reason you really do need to emulate a "compound statement
expression" in MSVC (because you need to declare some temp variables in a
macro that can be used for assignment), it turns out that it can be done with
a "lambda expression":

    
    
      #include <stdio.h>
    
      int bar(int n, const char *p[])
      {
        int total = 0; 
        for (int i = 0; i < n; i++) {
          total += printf("%s\t", p[i]);
        }
        putchar('\n');
        return total;
      }
    
      #define bar(...) [](){                                       \
        const char *_args[] = {NULL, __VA_ARGS__};                 \
        return bar(sizeof(_args)/sizeof(*_args) - 1, _args + 1);   \
      }()
    
      int main(/* int argc, char **argv */) {
        int total = 0;
        total += bar();
        total += bar("a");
        total += bar("a", "b");
        total += bar("a", "b", "c");
        printf("total: %d\n", total);
        return 0;
      }

------
pklausler
The presented example will work only if the actual arguments are all of the
same type. For more general usage, namely printf() and its variants, the
variadic macro fails.

------
AndyKelley
This works when the arguments are all the same type. But what if you wanted to
implement, e.g. printf with this?

~~~
cfallin
In principle, it seems you could handle the heterogenous case by designing an
`Any` struct and (at least in C++) implicit constructors to take various types
(int, T*, ...). At some point this starts to feel like designing a custom ABI
and the complexity may not be worth it, but it seems there should be a way :-)

~~~
haberman
> it seems you could handle the heterogenous case by designing an `Any` struct
> and (at least in C++) implicit constructors to take various types (int, T*,
> ...)

In C11 you could also use _Generic. It will be perfect for the year 2045 when
Microsoft adds C11 support.

~~~
ryanprichard
_Generic might not work so well to handle int vs double vs pointer-to-T.
Apparently, even unselected associations must type-check.

e.g.: Here's a simple implementation:

    
    
      enum AnyKind { AnyInt, AnyDouble, AnyStr };
      
      struct Any {
          enum AnyKind kind;
          union {
              int i;
              double d;
              const char *s;
          } u;
      };
      
      #define ANY(x) \
          _Generic((x),                                               \
              int:          (struct Any){ .kind = AnyInt, .u.i = (x) },      \
              double:       (struct Any){ .kind = AnyDouble, .u.d = (x) },   \
              const char *: (struct Any){ .kind = AnyStr, .u.s = (x) })
      
      int main() {
          ANY(1);
          ANY(1.0);
          ANY("abc");
      }
    

Clang gives errors like:

    
    
      error: initializing 'const char *' with an expression of incompatible type 'double'
    

This issue was discussed a bit on the GCC bugtracker:
[https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64509](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64509)

Qualifiers might also be an issue. My impression so far is that every
combination of differently qualified type is supposed to require a different
_Generic association. (GCC 5.2 and Clang 3.7 differ here.) That would require
a huge number of _Generic associations, but it might be possible to strip the
qualifiers using a comma operator, _Generic( (0,(x)), ... ). A comma
expression is not an lvalue, so perhaps it cannot be qualified. DR423[1] askes
that question.

[1] [http://www.open-
std.org/jtc1/sc22/wg14/www/docs/dr_423.htm](http://www.open-
std.org/jtc1/sc22/wg14/www/docs/dr_423.htm)

~~~
haberman
What a fantastically informative reply, thanks for this! Having not used
_Generic myself, it's good to know what kind of limitations to expect.

------
looneysquash
For what it's worth, C++ has a better way to do this.

[http://en.cppreference.com/w/cpp/language/parameter_pack](http://en.cppreference.com/w/cpp/language/parameter_pack)

You might want to skip down to the example, a type safe printf.

~~~
anon4
Yeah, but then you have to compile the function in every single cpp file and
your linking time increases and you're at risk of violating ODR and you have
to give people the full definition of your function if you're making a library
rather than just the declaration and a .a/.obj/.so.

~~~
gpderetta
You provide a type safe variadic template interface which forwards to a type
erased out of line layer. It would compile down to the same code as the macro.

------
hlandau
I should note that if you just want to get the number of arguments for a call
to a varargs function rather than trust people to terminate the arguments with
NULL properly, you can do this with the C99 preprocessor:

#define foo(...) foo(PP_NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

where PP_NUM_ARGS is a macro to determine the number of arguments; you can
find such a macro easily by searching. Of course you could just do #define
foo(...) foo(__VA_ARGS__, NULL), but this lets you include NULL values in the
list.

Since the count takes up the first argument to the actual varargs function,
you can call it with zero arguments. No type checking, though as mentioned
above you might be able to do something interesting about that with _Generic.

~~~
ori_b
You can also get this effect more simply by just adding a NULL in:

    
    
        #define foo(...) foo(__VA_ARGS__, NULL);

------
shiro
Using macro fails if I happen to pass a variable named _args as one of the
arguments to bar(). Is there any clever trick to simulate gensym in C macro?

~~~
__david__
Usually for those situations I just try to pick something that's a little more
unique (maybe __bar_args). For things that end up defining globals I've used
this technique before:

    
    
        #define CAT2(a,b) a##b
        #define CAT(a,b) CAT2(a,b)
        #define Unique(symbol) CAT(symbol, __LINE__)
    

and then use

    
    
        #define my_useless_macro(value) \
            static int Unique(__macro_var_name) = value
    

…with the (large?) caveat that it won't work if you put 2 on the same line:

    
    
        my_useless_macro(0); // works
        my_useless_macro(1); my_useless_macro(2); // Fails
    

If you expect to call my_usless_macro() multiple times on a line (hint, macro
expansions are always on the same line), then you might put an extra optional
parameter in there with __VA_ARGS__, like this:

    
    
        #define my_useless_macro2(value,...) \
            static int Unique(CAT(__macro_var_name, __VA_ARGS__)) = value
    

and then this will work:

    
    
        my_useless_macro2(1,a); my_useless_macro2(2,b); // works
        #define doubly_useless(a,b)    \
            my_useless_macro2(a, __1); \
            my_useless_macro2(b, __2)
        doubly_useless(3,4);
    

The C preprocessor is a bit of big hacky mess, but you can do some fairly
powerful (and often type safe) things with it.

~~~
shiro
This is clever. I agree that usually just picking some obscure name would
suffice. Fun to know nevertheless.

------
stonogo
I strongly appreciate the irony of having "acumen" defined for me on the same
page as this idea of "better".

