Question: Is there an option to detect at the compilation time if the size of the macro argument larger than, let's say, uint32_t?
The only way to do this portably, is by generating a compiler error with _Generic
. If you want the error to be pretty and readable, you feed the result of _Generic
to _Static_assert
, so that you can type out a custom string as compiler message.
Your specification seems to be this:
- Everything must be compile-time checks.
- The macro can get 1 to 5 parameters of any type.
- Only
int32_t
and uint32_t
are allowed types.
This means that you have to write a variadic macro and it must accept 1 to 5 parameters.
Such a macro can be written like this:
#define COUNT_ARGS(...) ( sizeof((uint32_t[]){__VA_ARGS__}) / sizeof(uint32_t) )
#define MY_MACRO(...) \
_Static_assert(COUNT_ARGS(__VA_ARGS__)>0 && COUNT_ARGS(__VA_ARGS__)<=5, \
"MY_MACRO: Wrong number of arguments");
COUNT_ARGS
creates a temporary compound literal of as many objects as you give the macro. If they are wildly incompatible with uint32_t
you might get compiler errors/warnings here already. If not, COUNT_ARGS
will return the number of arguments passed.
With that out of the way, we can do the actual, portable type check of each item in the variable argument list. To check the type of one single item with _Generic
:
#define CHECK(arg) _Generic((arg), uint32_t: 1, int32_t: 1, default: 0)
Then pass the result of that on to _Static_assert
. However, for 5 arguments we would need to check 1 to 5 items. We can "chain" a number of macros for this purpose:
#define CHECK(arg) _Generic((arg), uint32_t: 1, int32_t: 1, default: 0)
#define CHECK_ARGS1(arg1,...) CHECK(arg1)
#define CHECK_ARGS2(arg2,...) (CHECK(arg2) && CHECK_ARGS1(__VA_ARGS__,0))
#define CHECK_ARGS3(arg3,...) (CHECK(arg3) && CHECK_ARGS2(__VA_ARGS__,0))
#define CHECK_ARGS4(arg4,...) (CHECK(arg4) && CHECK_ARGS3(__VA_ARGS__,0))
#define CHECK_ARGS5(arg5,...) (CHECK(arg5) && CHECK_ARGS4(__VA_ARGS__,0))
Each macro checks the first argument passed to it, then forwards the rest of them, if any, to the next macro. The trailing 0 is there to shut up ISO C warnings about rest arguments required for variadic macros.
We can bake the calls to these into a _Static_assert
that calls the proper macro in the "chain" corresponding to the number of arguments:
_Static_assert(COUNT_ARGS(__VA_ARGS__) == 1 ? CHECK_ARGS1(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 2 ? CHECK_ARGS2(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 3 ? CHECK_ARGS3(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 4 ? CHECK_ARGS4(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 5 ? CHECK_ARGS5(__VA_ARGS__,0) : 0, \
"MY_MACRO: incorrect type in parameter list " #__VA_ARGS__); \
Full code with examples of use:
#include <stdint.h>
#define COUNT_ARGS(...) ( sizeof((uint32_t[]){__VA_ARGS__}) / sizeof(uint32_t) )
#define CHECK(arg) _Generic((arg), uint32_t: 1, int32_t: 1, default: 0)
#define CHECK_ARGS1(arg1,...) CHECK(arg1)
#define CHECK_ARGS2(arg2,...) (CHECK(arg2) && CHECK_ARGS1(__VA_ARGS__,0))
#define CHECK_ARGS3(arg3,...) (CHECK(arg3) && CHECK_ARGS2(__VA_ARGS__,0))
#define CHECK_ARGS4(arg4,...) (CHECK(arg4) && CHECK_ARGS3(__VA_ARGS__,0))
#define CHECK_ARGS5(arg5,...) (CHECK(arg5) && CHECK_ARGS4(__VA_ARGS__,0))
#define MY_MACRO(...) \
do { \
_Static_assert(COUNT_ARGS(__VA_ARGS__)>0 && COUNT_ARGS(__VA_ARGS__)<=5, \
"MY_MACRO: Wrong number of arguments"); \
_Static_assert(COUNT_ARGS(__VA_ARGS__) == 1 ? CHECK_ARGS1(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 2 ? CHECK_ARGS2(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 3 ? CHECK_ARGS3(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 4 ? CHECK_ARGS4(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 5 ? CHECK_ARGS5(__VA_ARGS__,0) : 0, \
"MY_MACRO: incorrect type in parameter list " #__VA_ARGS__); \
} while(0)
int main (void)
{
//MY_MACRO(); // won't compile, "empty initializer braces"
//MY_MACRO(1,2,3,4,5,6); // static assert "MY_MACRO: Wrong number of arguments"
MY_MACRO(1); // OK, all parameters int32_t or uint32_t
MY_MACRO(1,2,3,4,5); // OK, -"-
MY_MACRO(1,(uint32_t)2,3,4,5); // OK, -"-
//MY_MACRO(1,(uint64_t)2,3,4,5); // static assert "MY_MACRO: incorrect type..."
//MY_MACRO(1,(uint8_t)2,3,4,5); // static assert "MY_MACRO: incorrect type..."
}
This should be 100% portable and doesn't rely on the compiler giving extra diagnostics beyond what's required by the standard.
The old do-while(0)
trick is there to allow compatibility with icky-style brace formatting standards such as if(x) MY_MACRO(1) else
. See Why use apparently meaningless do-while and if-else statements in macros?