Writing Type‑Generic Macros in C with _Generic
Learn how C11’s _Generic keyword lets you write a single, type‑safe macro that expands to the correct abs‑family function for int, long, float, or double, with zero runtime cost.
12 Jun 2026, 23:30 UTC

The problem: one abs for many types
When you need an absolute‑value routine that works for int, long, float and double, the usual C approaches are either:
- Write a separate macro or function for each type (
abs_int,abs_long, …) – lots of boilerplate. - Use a runtime
ifchain that checks the type viasizeofor a union – adds overhead and defeats compile‑time optimization.
Both solutions hurt readability or performance. C11 introduced the _Generic keyword to solve exactly this dilemma: it lets the compiler pick an expression based solely on the type of a controlling expression, with zero runtime cost.
How _Generic works
The syntax looks like a switch statement but operates on types:
_Generic( controlling-expression ,
type1 : expression1,
type2 : expression2,
default : expression-default )
The compiler evaluates the controlling expression only to determine its type; the expression itself is not executed. It then selects the association whose type matches (or the default if provided). If no match exists and there is no default, the program is ill‑formed and fails to compile.
Worked example: a type‑generic abs macro
We can wrap the above in a macro so callers write ABS(x) and get the right library function:
#include <stdlib.h> /* abs, labs */
#include <math.h> /* fabs */
#define ABS(X) _Generic((X), \
signed char: abs, \
unsigned char: abs, \
short: abs, \
unsigned short: abs, \
int: abs, \
unsigned int: abs, \
long: labs, \
unsigned long: labs, \
long long: llabs, \
unsigned long long: llabs, \
float: fabsf, \
double: fabs, \
long double: fabsl \
)(X)
Explanation:
- The controlling expression is
(X); we cast it tovoid*? No cast is needed – the parentheses preserve the type. - Each arm names a function (
abs,labs, …) that matches the corresponding type. - The macro expands to a call to that function, passing the original argument
X.
Because the selection happens at compile time, the generated code is identical to writing the specific function call by hand.
Using the macro
Here’s a tiny driver program that demonstrates the macro with four different types:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define ABS(X) _Generic((X), \
signed char: abs, \
unsigned char: abs, \
short: abs, \
unsigned short: abs, \
int: abs, \
unsigned int: abs, \
long: labs, \
unsigned long: labs, \
long long: llabs, \
unsigned long long: llabs, \
float: fabsf, \
double: fabs, \
long double: fabsl \
)(X)
int main(void) {
int i = -42;
long l = -123456L;
float f = -3.14f;
double d = -2.71828;
printf("abs(%d) = %d\n", i, ABS(i));
printf("labs(%ld) = %ld\n", l, ABS(l));
printf("fabsf(%f) = %f\n", f, ABS(f));
printf("fabs(%lf) = %lf\n", d, ABS(d));
return 0;
}
Compile with a C11‑conforming compiler:
gcc -std=c11 -Wall -Wextra -o abs_demo abs_demo.c
The output will show the correct absolute values for each type, confirming that the macro selected the appropriate underlying function.
Trade‑offs and limitations
- Readability: A long list of type‑association pairs can become hard to scan, especially when the macro is used for many different operations. Breaking the list into multiple lines or using helper macros can mitigate this.
- Compiler support:
_Genericis part of C11. Older compilers (e.g., MSVC before version 2015, or any toolchain invoked with-std=c99) will reject the syntax with an error like "unknown type name '_Generic'". Always verify__STDC_VERSION__ >= 201112Lbefore relying on it. - No match leads to a hard error: If you call
ABSwith a type not listed (say, a pointer or a struct), compilation fails. This is safer than a silent wrong choice, but it means the macro must be kept in sync with the types you intend to support. - Side‑effects in the controlling expression: Because the expression is evaluated only for type determination, any side effects occur exactly once. However, if you write something like
ABS(get_next())whereget_next()modifies state, the call happens once, which is usually what you want.
Actionable checklist
- Confirm your compiler targets C11 or later:
printf("%ld\n", (long)__STDC_VERSION__);should print201112Lor higher. - Copy the
ABSmacro definition into a header that is included wherever you need a generic absolute value. - Test with the four basic types (int, long, float, double) as shown; add more arms if you need to support additional types (e.g.,
_Float16on newer GCC). - If you encounter a compilation error for a new type, simply add another arm to the
_Genericlist. - For projects that must remain portable to pre‑C11 compilers, provide a fallback macro that uses the
#if __STDC_VERSION__ >= 201112Lguard and defines a less‑optimal version (e.g., a series of#ifchecks) for older toolchains.
By using _Generic you get the convenience of a single macro name, the type safety of compile‑time dispatch, and the performance of an inline function—all without runtime overhead.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.