_Generic & Type-Safe Wrappers
C's compile-time overloading: _Generic picks an expression by type, and macro wrappers seal the void* seams shut.
Compile-time selection by type
C23's _Generic (available since C11) is overloading without functions:
#define type_name(x) _Generic((x), \
int: "int", double: "double", \
char *: "char *", default: "other")
The selector (x) is not evaluated โ only its type is examined after
lvalue conversion. Each association yields a different expression; the
compiler splices in exactly one. That makes _Generic the glue for
type-safe generic APIs:
#define vec_push(v, val) _Generic((val), \
int: vpush_i, \
double: vpush_d, \
default: vpush_i)((v), (val))
The macro dispatches to the right concrete function at compile time โ
zero runtime cost, full type checking, no void* at the call site.
The user of the library writes vec_push(v, 42) and the compiler
verifies 42 matches an implemented branch.
The three layers, honestly ranked
- Concretely typed functions (
vpush_i) โ the real work, fully type-checked. _Genericdispatch macro โ compile-time selection among them; documents the supported set;default:should fail loudly (a(void)0-style dead branch or a _Static_assert-friendly error).- Raw
void *+ size + callback โ the universal engine beneath, needed when the type itself is a runtime parameter.
_Generic cannot dispatch on runtime values, cannot list every type, and does not work on unresolved pointers-to-incomplete. It kills the common typos; the engine still handles the general case.
Static assertions seal the contract
_Static_assert(sizeof(int) == 4, "vec_i assumes 4-byte int");
Compile-time contracts over representation assumptions: better a failed build on an exotic platform than silent corruption shipped to users.