🚀 Modern C (C11 → C23)
_Generic: compile-time dispatch on type
▶ Open the interactive lesson — free, no signupCall abs(-2.5) and C quietly answers 2 — wrong — because C keeps a separate absolute-value function for every kind of number, and abs is the whole-number one. Here you learn the switch-on-type trick that lets a single name pick the right function automatically at compile time. It's the exact machinery the standard library uses so that sqrt just works on whatever number you hand it.
In C, abs, labs, fabs, and fabsf are four different functions for one idea, because C has no function overloading (the trick other languages use to let one name cover several types) — in C, every function name means exactly one thing. C11 didn't add overloading, but it added something sneakier: _Generic, an expression that selects other expressions based on the type of its argument, at compile time.
The syntax
#include <stdio.h>
#define type_name(x) _Generic((x), \
int: "int", \
unsigned int: "unsigned int", \
double: "double", \
float: "float", \
char: "char", \
char *: "char *", \
default: "something else")
int main(void) {
printf("42 -> %s\n", type_name(42));
printf("42u -> %s\n", type_name(42u));
printf("3.14 -> %s\n", type_name(3.14));
printf("3.14f -> %s\n", type_name(3.14f));
printf("\"hi\" -> %s\n", type_name("hi"));
printf("'a' -> %s\n", type_name('a')); /* surprise! */
return 0;
}$ gcc -std=c17 typename.c -o typename && ./typename 42 -> int 42u -> unsigned int 3.14 -> double 3.14f -> float "hi" -> char * 'a' -> int # 'a' is an int in C (not char!), and "hi" decayed from char[3] to char *
Read it as a switch on types: the controlling expression (x) is examined (but never evaluated — only its type matters), the association list maps types to result expressions, and the whole _Generic(...) collapses to the one that matches. default catches everything else; with no match and no default, it's a compile error.
▶ This spot has an interactive flow widget — open the interactive lesson to play with it.
🧠 Checkpoint: In _Generic(f(), int: a, default: b), is f() called at runtime?
- Yes, once
- Yes, once per matching branch
- No — only its type is used
- Only if the int branch is selected
Show answer
No — only its type is used — The controlling expression is NEVER evaluated — the compiler only inspects its type. This means _Generic(x++, ...) does not increment x, another classic gotcha.
Building a real type-generic macro
_Generic is almost always wrapped in a macro — that's the whole design. Here's the classic: one my_abs that works for every arithmetic type:
#include <stdio.h>
#include <stdlib.h> /* abs, labs, llabs */
#include <math.h> /* fabs, fabsf, fabsl */
#define my_abs(x) _Generic((x), \
int: abs, \
long: labs, \
long long: llabs, \
float: fabsf, \
double: fabs, \
long double: fabsl \
)(x) /* <- select the FUNCTION, then call it */
int main(void) {
printf("my_abs(-5) = %d\n", my_abs(-5));
printf("my_abs(-5L) = %ld\n", my_abs(-5L));
printf("my_abs(-2.5f) = %f\n", my_abs(-2.5f));
printf("my_abs(-2.5) = %f\n", my_abs(-2.5));
return 0;
}$ gcc -std=c17 myabs.c -o myabs -lm && ./myabs my_abs(-5) = 5 my_abs(-5L) = 5 my_abs(-2.5f) = 2.500000 my_abs(-2.5) = 2.500000 # one macro, four different functions actually called — picked per call site
Note the trick on the last line: _Generic selects the function itself (abs, fabs, …), and only then do we call it with (x). This is exactly how <tgmath.h> works — since C11 it's implementable as ordinary macros: sqrt(2.0f) quietly calls sqrtf, sqrt(2.0) calls the double version, sqrt(z) the complex one. Type-generic math, zero runtime cost.
🧠 Checkpoint: How does <tgmath.h> make sqrt work on float, double, and complex?
- Runtime type tags on every number
- The linker picks a version
- Type-generic macros — since C11, buildable with
_Generic - sqrt secretly takes void *
Show answer
Type-generic macros — since C11, buildable with _Generic — tgmath.h existed since C99 using compiler magic; C11’s _Generic made the magic expressible in the language itself. Each call site expands to a direct call of sqrtf/sqrt/sqrtl/csqrt — no runtime dispatch at all.
The fine print (a.k.a. the traps)
- Lvalue conversion first. The controlling expression's type is taken after dropping qualifiers and decaying arrays: a
const intmatchesint, and achar[10]matcheschar *. (C11 was fuzzy here; C17 nailed it down.) - No partial matching. You can't write "any pointer type" or "any integer" — every type is spelled out exactly. Want 6 integer types? Write 6 associations.
- Character literals are
int._Generic('a', char: 1, int: 2)gives 2 in C — a perennial interview gotcha. - Every branch must be valid code. Unselected branches aren't evaluated, but they are still parsed and type-checked — which is why the "select the function, then call" pattern beats putting full calls in each branch.
Two types that look the same can collide: listing both int and signed int is an error (same type twice), yet char, signed char, and unsigned char are three distinct types and may all appear. When a generic association list misbehaves, suspect the type system's fine print.
🧠 Checkpoint: What does _Generic((const int){0}, int: "plain", default: "other") select?
- "other" — const int is distinct
- "plain" — qualifiers are dropped first
- Compile error — duplicate types
- Undefined behavior
Show answer
"plain" — qualifiers are dropped first — The controlling expression undergoes lvalue conversion, which strips top-level qualifiers (and decays arrays to pointers). const int therefore matches the int association. C17 spelled this out explicitly.
Where it shines
Beyond math wrappers: type-safe print helpers (pick the right printf format automatically), debug macros that show a value and its type, and library APIs that accept several types without void *'s type-erasure. Try one yourself:
▶ This spot has an interactive editor widget — open the interactive lesson to play with it.
So far, everything in this part happened at compile time — next, the hardest runtime problem in modern C: multiple threads touching the same memory.
▶ Practice this lesson interactively (with live gcc)