Hi,

I've occasionally wished for a typesafe version of pg_printf() and other
varargs functions. The compiler warnings are nice, but also far from
complete.

Here's a somewhat crazy hack/prototype for how printf could get actual
argument types.  I'm far from certain it's worth pursuing this
further... Nor the contrary.

Note that this requires removing the parentheses from VA_ARGS_NARGS_'s
result (i.e. (N) -> N). To me those parens don't make much sense, we're
pretty much guaranteed to only ever have a number there.

With the following example e.g.

        myprintf("boring fmt", 1, 0.1, (char)'c', (void*)0, "crazy stuff");
        myprintf("empty argument fmt");

yield

format string "boring fmt", 5 args
        arg number 0 is of type int: 1
        arg number 1 is of type double: 0.100000
        arg number 2 is of type bool: true
        arg number 3 is of type void*: (nil)
        arg number 4 is of type char*: crazy stuff
format string "empty argument fmt", 0 args


which'd obviously allow for error checking inside myprintf.


#include "c.h"

// hack pg version out of the way
#undef printf

// FIXME: This doesn't correctly work with zero arguments
#define VA_ARGS_EACH(wrap, ...) \
        VA_ARGS_EACH_EXPAND(VA_ARGS_NARGS(__VA_ARGS__)) (wrap, __VA_ARGS__)
#define VA_ARGS_EACH_EXPAND(count) 
VA_ARGS_EACH_EXPAND_REALLY(VA_ARGS_EACH_INT_, count)
#define VA_ARGS_EACH_EXPAND_REALLY(prefix, count) prefix##count

#define VA_ARGS_EACH_INT_1(wrap, el1) wrap(el1)
#define VA_ARGS_EACH_INT_2(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_1(wrap, 
__VA_ARGS__)
#define VA_ARGS_EACH_INT_3(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_2(wrap, 
__VA_ARGS__)
#define VA_ARGS_EACH_INT_4(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_3(wrap, 
__VA_ARGS__)
#define VA_ARGS_EACH_INT_5(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_4(wrap, 
__VA_ARGS__)
#define VA_ARGS_EACH_INT_6(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_5(wrap, 
__VA_ARGS__)
#define VA_ARGS_EACH_INT_7(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_6(wrap, 
__VA_ARGS__)


typedef enum printf_arg_type
{
        PRINTF_ARG_BOOL,
        PRINTF_ARG_CHAR,
        PRINTF_ARG_INT,
        PRINTF_ARG_DOUBLE,
        PRINTF_ARG_CHARP,
        PRINTF_ARG_VOIDP,
} printf_arg_type;

typedef struct arginfo
{
        printf_arg_type tp;
} arginfo;

// hackfix empty argument case
#define myprintf(...) myprintf_wrap(__VA_ARGS__, "dummy")
#define myprintf_wrap(fmt, ... ) \
        myprintf_impl(fmt, VA_ARGS_NARGS(__VA_ARGS__) - 1, ((arginfo[]){ 
VA_ARGS_EACH(blurttype, __VA_ARGS__) }), __VA_ARGS__)

// FIXME: Obviously not enough types
#define blurttype(x) ((arginfo){_Generic(x, char: PRINTF_ARG_BOOL, int: 
PRINTF_ARG_INT, double: PRINTF_ARG_DOUBLE, char *: PRINTF_ARG_CHARP, void *: 
PRINTF_ARG_VOIDP)})

static const char*
printf_arg_typename(printf_arg_type tp)
{
        switch (tp)
        {
                case PRINTF_ARG_BOOL:
                        return "bool";
                case PRINTF_ARG_CHAR:
                        return "char";
                case PRINTF_ARG_INT:
                        return "int";
                case PRINTF_ARG_DOUBLE:
                        return "double";
                case PRINTF_ARG_CHARP:
                        return "char*";
                case PRINTF_ARG_VOIDP:
                        return "void*";
        }

        return "";
}

static void
myprintf_impl(char *fmt, size_t nargs, arginfo arg[], ...)
{
        va_list args;
        va_start(args, arg);

        printf("format string \"%s\", %zu args\n", fmt, nargs);
        for (int argno = 0; argno < nargs; argno++)
        {
                printf("\targ number %d is of type %s: ",
                           argno,
                           printf_arg_typename(arg[argno].tp));

                switch (arg[argno].tp)
                {
                        case PRINTF_ARG_BOOL:
                                printf("%s", ((bool) va_arg(args, int)) ? 
"true" : "false");
                                break;
                        case PRINTF_ARG_CHAR:
                                printf("%c", (char) va_arg(args, int));
                                break;
                        case PRINTF_ARG_INT:
                                printf("%d", va_arg(args, int));
                                break;
                        case PRINTF_ARG_DOUBLE:
                                printf("%f", va_arg(args, double));
                                break;
                        case PRINTF_ARG_CHARP:
                                printf("%s", va_arg(args, char *));
                                break;
                        case PRINTF_ARG_VOIDP:
                                printf("%p", va_arg(args, void *));
                                break;
                }

                printf("\n");
        }
}

int main(int argc, char **argv)
{
        myprintf("boring fmt", 1, 0.1, (char)'c', (void*)0, "crazy stuff");
        myprintf("empty argument fmt");
}


Greetings,

Andres Freund


Reply via email to