Generic Programming in C
C has no templates and no generics — there is no built-in way to write one function that works correctly and type-safely across many different types the way C++ templates or Java/C# generics do. Instead C programmers reach for a small set of long-standing patterns that approximate genericity, each with real trade-offs.
Pattern 1: void* and Function Pointers
qsort() is the canonical example of this pattern: it sorts an array of any type by operating on raw bytes through a void*, and defers the type-specific comparison logic to a caller-supplied function pointer.#include <stdlib.h>
int compare_ints(const void *a, const void *b) {
int arg1 = *(const int *)a;
int arg2 = *(const int *)b;
return (arg1 > arg2) - (arg1 < arg2);
}
int main(void) {
int data[] = {5, 2, 9, 1, 5, 6};
size_t n = sizeof(data) / sizeof(data[0]);
qsort(data, n, sizeof(int), compare_ints);
return 0;
}This works for any element type — structs, strings, doubles — as long as you write a matching comparison function. The cost is that all type safety is gone at the interface: the compiler cannot check that your comparison function actually matches the array's real element type, and every access requires an explicit, unchecked cast.
Pattern 2: Macros
Preprocessor macros can generate type-specific code at compile time, giving something closer to true generic code — but with the preprocessor's well-known sharp edges (no type checking, no scoping, and multiple evaluation of arguments).
#define MAX(a, b) ((a) > (b) ? (a) : (b)) int x = MAX(3, 7); // works for int double y = MAX(2.5, 1.1); // "works" for double too -- but see the warning below
MAX(i++, j++) silently increments whichever variable is larger twice. See the Function-Like Macros page for the full set of pitfalls (missing parentheses, multiple evaluation, and more).Pattern 3: _Generic (C11)
_Generic keyword, which performs compile-time dispatch based on the type of an expression — the closest thing standard C has to real generic dispatch. It is most useful hidden behind a macro, so callers get ordinary-looking function-style syntax:#include <stdio.h>
// _Generic picks the branch matching the type of 'x' at compile time.
#define type_name(x) _Generic((x), \
int: "int", \
float: "float", \
double: "double", \
char *: "char *", \
default: "unknown type" \
)
// A type-generic "max" that dispatches to the right comparison per type.
#define generic_max(a, b) _Generic((a), \
int: max_int, \
double: max_double \
)(a, b)
int max_int(int a, int b) { return a > b ? a : b; }
double max_double(double a, double b) { return a > b ? a : b; }
int main(void) {
printf("%s\n", type_name(42)); // "int"
printf("%s\n", type_name(3.14)); // "double"
printf("%d\n", generic_max(3, 7)); // dispatches to max_int
printf("%f\n", generic_max(2.5, 1.1)); // dispatches to max_double
return 0;
}_Generic selects between real, separately type-checked functions based on the argument's type — each branch is ordinary, fully type-safe C code. The dispatch itself happens entirely at compile time with zero runtime cost._Generic — each trade away either type safety, evaluation safety, or flexibility. If you find yourself wanting extensive generic containers and algorithms, this is one of the areas where C genuinely asks more of the programmer than C++ does.void*plus function pointers (theqsortpattern) trades type safety for real runtime genericityMacros can generate type-generic code textually, but carry classic preprocessor hazards -- see Function-Like Macros
_Generic(C11) dispatches to different, fully type-checked code paths based on an expression's type, at compile timeNone of these fully replicate C++ templates -- this is a known, accepted limitation of standard C