Search code examples
cvariadic-functions

(GCC/Clang) Enforce type on variadic argument list?


This might be kind of a weird question, but I am a novice with __builtin_va_list and how it works.

I am creating a function with the following signature:

typedef signed char             i8;
typedef long long signed int   i64;
typedef long long unsigned int u64;
i64 f ( u64 arg_count , ... );

Ideally, the function is supposed to work like this under the hood:

i64 f ( u64 arg_count , u64* args );

I like that the ... syntax lets me take any number of arguments without explicitly defining an array first, but for the purposes of the function, I really want to explicitly cast each of the them all to u64 before parsing them in my function, and the __builtin_va_list does not allow me to do that without explicitly casting each argument .

For example, if I call:

i8 c = 27;
u64 i = 27;
f ( 2 , c , i );

and within the function pop an argument with __builtin_va_arg, I have to correctly predict the size of the incoming type in order to get the correct value. For example,

i64 f ( u64 arg_count , ... )
{
    __builtin_va_list args;
    __builtin_va_start ( args , arg_count );
    u64 c = __builtin_va_arg ( args , u64 ); // This will be wrong because the first parameter was 8 bits, not 64
    ...
}

Explicit casting like this would work I suppose:

i8 c = 27;
u64 i = 27;
f ( 2 , ( u64 ) c , ( u64 ) i );

...but it is annoying to type for long lists.

Is there any way to accomplish the desired syntax using plain C / preprocessor? I usually just compile with Clang, but a fairly cross-platform solution would be ideal.


Solution

  • Instead of passing the values as arguments in a variable argument list, you can pass them as an array, using a compound literal:

    #include <inttypes.h>
    #include <stdint.h>
    #include <stdio.h>
    
    
    /*  Define a macro to convert its variable arguments to a compound literal
        array and to pass that array to f.
    */
    #define F(n,...)    f((n), (uint64_t []) { __VA_ARGS__ })
    
    
    //  Print the n values pointed to by p.
    static void f(uint64_t n, uint64_t *p)
    {
        printf("f(%" PRIu64, n);
        for (uint64_t i = 0; i < n; ++i)
            printf(", %" PRIu64, p[i]);
        printf(")\n");
    }
    
    
    int main(void)
    {
        F(3, 0, 1, 2);
        F(2, 9, 8);
    }
    

    (Additionally, I changed the code to use the standard type uint64_t. There is no need to create user-defined types such as u64. It adds clutter and opportunity for confusion and bugs.)

    You can also have the macro count the arguments:

    /*  Define a macro to convert its variable arguments to a compound literal
        array and to pass that array to f.
    */
    #define F(...)  f(sizeof (uint64_t []) { __VA_ARGS__ } / sizeof (uint64_t), (uint64_t []) { __VA_ARGS__ })
    
    …
    
        F(0, 1, 2);
        F(9, 8);