Reputation: 1485
In C, I'd like to make a function or a macro that looks like this:
void Log(char* what, ...)
where the ... must be key-value pairs of const char*'s. I would really like code that doesn't follow this to blow up at compile time. I tried looking for an __attribute((..)) directive that did this, to no avail. Any ideas?
Upvotes: 3
Views: 506
Reputation: 3736
#define NICER_VARIADIC(type, ...) \
(type[]){__VA_ARGS__}, \
(sizeof((type[]){__VA_ARGS__}) / sizeof(type))
void Log(char * message,
char * * key_value_pairs,
size_t key_value_pair_count);
#define LOG(message, ...) \
Log(message, NICER_VARIADIC(char * *, __VA_ARGS__))
Now let's unpack that a bit.
First, like the other answers already suggested, start by writing your function itself to take an array instead of a variadic argument list (this isn't strictly necessary for this trick, but the trick makes more sense this way, and I think it's better design - it's also usually nicer for interop with other languages and at the ABI level, in my limited experience):
void Log(char * message,
char * * key_value_pairs,
size_t key_value_pair_count);
Second, we need something that will turn an argument list like foo, bar, qux
into a compound literal like (char * * []){foo, bar, qux}
so that we can pass it as an array. Easy enough with variadic macros:
#define NICER_VARIADIC_ARGUMENT_LIST(type, ...) (type[]){__VA_ARGS__}
Now, instead of the compiler comparing foo, bar, qux
against the type-less variadic argument list, it compares it against the typed array literal.
Third, once we've captured the arguments in an array literal, we can use sizeof
to count them (by getting the size of the array literal and dividing it by the size of the type)
#define NICER_VARIADIC_ARGUMENT_COUNT(type, ...) (sizeof((type[]){__VA_ARGS__}) / sizeof(type))
This argument count will be computed at compile-time in any serious compiler because it is a constant expression, and we don't get any multiple-evaluation problems like macros sometimes have, because sizeof
just figures out the type information of its argument without "evaluating" it.
Then we can combine the above two macros into one, which expands to both the array literal argument and the size argument, for better DX:
#define NICER_VARIADIC(type, ...) \
(type[]){__VA_ARGS__}, \
(sizeof((type[]){__VA_ARGS__}) / sizeof(type))
So now you can just do stuff like
LOG("hooray!");
LOG("hooray!", my_key_value_pair);
LOG("hooray!", my_key_value_pair, my_other_k_v_pair);
and it just works, with type-safety (and without boilerplate to pass a separate size or terminating value in the argument list).
Upvotes: 1
Reputation: 1485
Okay, I figured out a workaround:
#define ASSERT_LIST_TYPE(t, l...) do{t _t[] = {l};}while(0)
void _Log(char* what, ...);
#define Log(what, args...) do{\
ASSERT_LIST_TYPE(char*, args);\
_Log(what, args);\
}while(0)
That at least generates a warning if the args aren't of the right type, because the dummy array initialization isn't of the right type.
I plan on #ifdef'ing the ASSERT_LIST_TYPE out if this isn't a debug build just in case..
** Edit **
Based on the feedback in here, I changed the code to look like this:
void _Log(char* what, const char** args, size_t args_len);
#define Log(what, args...) do{\
const char* a[] = {args};\
_Log(what, a, (sizeof a)/sizeof(char*));
}while(0)
This appears to work even if args is empty, and it catches somebody passing an obj-c literal string instead (@"..." instead of "...") which is what bit me before.
Upvotes: 1
Reputation: 124642
where the ... must be key-value pairs of const char*'s
Then you don't want variadic arguments at all. If you know the type(s) you are expecting beforehand then you don't need it and you're just making things more difficult than they need to be.
Just pass in an array.
void Log(const char *what, const char **args, size_t size);
Upvotes: 3
Reputation: 138051
C has no way to enforce type safety over arbitrary variadic arguments. GCC and Clang have __attribute__
s for some fixed cases (when a variadic function expects the last argument to be NULL
, you can use __sentinel__
; or when it's a format string, you can use format
), but there is no generic solution.
On the other hand, C++11 has variadic templates to solve this problem, but since you mentioned that you're working in C, it's not gonna work for you.
Upvotes: 3