Lets say I have some manner of type safety macro based on _Generic
, which I use to wrap an actual function call to make it type safe:
#include <stdio.h>
void print (int i)
{
printf("%d\n", i);
}
#define print(expr) _Generic((expr), int: print)(expr)
int main()
{
print(1);
print(1.0);
}
This makes the code type safe, but I get a very non-descriptive gibberish error message when passing the wrong type:
error: controlling expression type 'double' not compatible with any generic association type
That's not very helpful to the user who might think they were calling a function and not my little type safety wrapper macro. What "generic association" is the compiler even on about, they will ask. Very confusing.
#error
will obviously not work here since _Generic
is evaluated at compile-time but after pre-processing is done.
How can I display a meaningful, customized compiler error to the caller whenever _Generic
fails?
The first step is to move the type check to a separate macro that returns 1 or 0 whether the passed type is supported or not:
#define type_check(t) _Generic((t), int: 1, default: 0)
And then call static_assert
with the macro result as input. So far so good.
However, in this case we'll also want to execute the actual print
function. The normal way to do that in a function-like macro would be to use the comma operator, since it discards the left-most operand. And the result will be that of the right-most operand, which is very nice in case the function also returns something.
But code like this isn't valid C:
#define print(expr) ( static_assert(type_check(expr), "error message"), print(expr) )
And that's because static_assert
isn't actually an expression but a declaration. So we have to invent a way to turn a declaration into an expression. This is actually quite possible in the following way:
static_assert
inside a struct
. There might have to be some other dummy member present too.Example:
#define static_assert_expr(expr, msg) ( (void)(struct{ int dummy; static_assert(expr, msg); }){}.dummy )
Here the struct
is an anonymous declaration with no tag, declaring a struct with a dummy member and a static assert. It returns the dummy member and we cast that to void
to silence compiler messages. A void expression on the left side of a comma operator ought to keep the compiler quiet.
And so the final macro becomes this:
#define print(expr) ( static_assert_expr(type_check(expr), "Wrong argument " #expr " passed to print, expected int.") \
, \
print(expr) )
Full example:
#include <stdio.h>
void print (int i)
{
printf("%d\n", i);
}
#define type_check(t) _Generic((t), int: 1, default: 0)
#define static_assert_expr(expr, msg) ( (void)(struct{ int dummy; static_assert(expr, msg); }){}.dummy )
#define print(expr) ( static_assert_expr(type_check(expr), "Wrong argument " #expr " passed to print, expected int.") \
, \
print(expr) )
int main()
{
print(1);
print(1.0);
}
Compiler output:
error: static assertion failed: Wrong argument 1.0 passed to print, expected int.
18 | print(1.0);
| ^~~~~~~~~~