I need to write a C macro that checks to ensure all parameters passed to it are unsigned
and of the same integer type. Ex: all input params are uint8_t
If the most important aspect here is that you want it to fail to compile if a
and b
are different types, you can make use of C11's _Generic
along with GCC's __typeof__
extension to manage this.
A generic example:
#include <stdio.h>
#define TYPE_ASSERT(X,Y) _Generic ((Y), \
__typeof__(X): _Generic ((X), \
__typeof__(Y): (void)NULL \
) \
)
int main(void)
{
int a = 1;
int b = 2;
TYPE_ASSERT(a,b);
printf("a = %d, b = %d\n", a, b);
}
Now if we try to compile this code, it will compile fine and everybody is happy.
If we change the type of b
to unsigned int
, however, it will fail to compile.
This works because _Generic
selection uses the type of a controlling expression ((Y)
in this case) to select a rule to follow and insert code corresponding to the rule. In this case, we only provided a rule for __typeof__(X)
, thus if (X)
is not a compatible type for (Y)
, there is no suitable rule to select and therefore cannot compile. To handle arrays, which have a controlling expression that will decay to a pointer, I added another _Generic
that goes the other way ensuring they must both be compatible with one another rather than accepting one-way compatibility. And since--as far as I particularly cared--we only wanted to make sure it would fail to compile on a mismatch, rather than execute something particular upon a match, I gave the corresponding rule the task of doing nothing: (void)NULL
There is a corner case where this technique stumbles: _Generic
does not handle Variably Modifiable types since it is handled at compile time. So if you attempt to do this with a Variable Length Array, it will fail to compile.
To handle your specific use-case for fixed-width unsigned types, we can modify the nested _Generic
to handle that rather than handling the pecularities of an array:
#define TYPE_ASSERT(X,Y) _Generic ((Y), \
__typeof__(X): _Generic ((Y), \
uint8_t: (void)NULL, \
uint16_t: (void)NULL, \
uint32_t: (void)NULL, \
uint64_t: (void)NULL \
) \
)
Example GCC error when passing non-compatible types:
main.c: In function 'main':
main.c:7:34: error: '_Generic' selector of type 'signed char' is not compatible with any association
7 | __typeof__(X): _Generic ((Y), \
| ^
It is worth mentioning that __typeof__
, being a GCC extension, will not be a solution that is portable to all compilers. It does seem to work with Clang, though, so that's another major compiler supporting it.
We'll build up to the final answer, so all can follow.
According to the C11 standard [6.7.10], static_assert-declaration: _Static_assert( constant-expression , string-literal )
is a Declaration. Thus if we are going to use a macro, we had best provide a scope for a declaration, to keep things tidy. Typically of the usual form:
#define MY_AMAZING_MACRO() do {_Static_assert(...some magic...);} while(0)
Next, so that our _Static_assert within the macro at least repeats via stdio the actual issue if the assert fails, well use familiar stringification setup:
#define STATIC_ASSERT_H(x) _Static_assert(x, #x)
#define STATIC_ASSERT(x) STATIC_ASSERT_H(x)
Next, we'll use C11's Generic selection feature to declare a macro that returns a constant 1 if the object is of the type we're looking for, and zero otherwise:
#define OBJ_IS_OF_TYPE(Type, Obj) _Generic(Obj, Type: 1, default: 0)
Next we''l make a macro to test if all four of your inputs are of the same type:
#define ALL_OBJS_ARE_OF_TYPE(Type, Obj_0, Obj_1, Obj_2, Obj_3) \
(OBJ_IS_OF_TYPE(Type, Obj_0) && \
OBJ_IS_OF_TYPE(Type, Obj_1) && \
OBJ_IS_OF_TYPE(Type, Obj_2) && \
OBJ_IS_OF_TYPE(Type, Obj_3))
Next, using the above, well make a macro to test if all four of your inputs are further one of the four types:
#define IS_ACCEPTABLE(Type_0, Type_1, Type_2, Type_3, Obj_0, Obj_1, Obj_2, Obj_3) \
(ALL_OBJS_ARE_OF_TYPE(Type_0, Obj_0, Obj_1, Obj_2, Obj_3) || \
ALL_OBJS_ARE_OF_TYPE(Type_1, Obj_0, Obj_1, Obj_2, Obj_3) || \
ALL_OBJS_ARE_OF_TYPE(Type_2, Obj_0, Obj_1, Obj_2, Obj_3) || \
ALL_OBJS_ARE_OF_TYPE(Type_3, Obj_0, Obj_1, Obj_2, Obj_3))
#define TEST_FUNC(a,b,c,d) \
do \
{ \
STATIC_ASSERT(IS_ACCEPTABLE(uint8_t, uint16_t, uint32_t, uint64_t, \
a, b, c, d)); \
} while(0)
Of course, you could separate the above into more distinct, individual STATIC_ASSERTs, as you wish, if you want more verbose error output if any of the _Static_assert
s fail.