CGeneric Programming in C

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
The standard library's own 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.

C
#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).

C
#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
Macro genericity comes with real hazards
Macro parameters are substituted as raw text with no type checking, and expressions with side effects can be evaluated more than once. 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)
C11 added the _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:

C
#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;
}
Unlike a plain macro, _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.
This is a genuine weak spot of C compared to C++
C++ templates generate fully type-checked code for arbitrary types at compile time, with the compiler verifying correctness for each instantiation. C's tools — void* with casts, unchecked macros, and the more disciplined but still limited _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 (the qsort pattern) trades type safety for real runtime genericity

  • Macros 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 time

  • None of these fully replicate C++ templates -- this is a known, accepted limitation of standard C