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