Function Overloading in C Using _Generic Macros
Traditional C does not natively support function overloading,
requiring developers to write distinct function names for operations
that perform the same logic on different data types. However, starting
with the C11 standard, the _Generic keyword enables
compile-time type association. By wrapping _Generic
expressions inside preprocessor macros, developers can map a single
function call identifier to type-specific implementations, achieving
true compile-time function overloading with zero runtime overhead.
The Mechanics of
_Generic
The _Generic selection expression evaluates the type of
a controlling expression at compile time and resolves to a specific
replacement expression based on an association list. Its general syntax
is:
_Generic(controlling_expression, \
type1: result1, \
type2: result2, \
default: default_result \
)Because the evaluation occurs entirely during compilation, the
controlling expression is not evaluated at runtime, meaning expressions
with side effects (such as i++) will not execute.
Implementing Single-Argument Function Overloading
To overload a function for multiple types, define the distinct,
type-specific functions first. Then, define a macro that uses
_Generic on the incoming argument to select and invoke the
corresponding function.
#include <stdio.h>
// Type-specific implementations
void print_int(int val) {
printf("Integer: %d\n", val);
}
void print_double(double val) {
printf("Double: %f\n", val);
}
void print_string(const char *val) {
printf("String: %s\n", val);
}
// Overloaded macro
#define print(X) _Generic((X), \
int: print_int, \
double: print_double, \
char*: print_string, \
const char*: print_string \
)(X)
int main(void) {
print(42); // Resolves to print_int(42)
print(3.14159); // Resolves to print_double(3.14159)
print("Hello, World!"); // Resolves to print_string("Hello, World!")
return 0;
}In this implementation, the _Generic expression resolves
directly to the function pointer matching the type of X.
The outer (X) then executes the selected function with the
provided argument.
Handling Multiple Arguments
While _Generic inspects only one controlling expression
per statement, you can achieve multi-argument overloading by nesting
_Generic expressions.
#include <stdio.h>
void add_int_int(int a, int b) { printf("int + int = %d\n", a + b); }
void add_double_double(double a, double b) { printf("double + double = %f\n", a + b); }
void add_int_double(int a, double b) { printf("int + double = %f\n", a + b); }
#define add(A, B) _Generic((A), \
int: _Generic((B), \
int: add_int_int, \
double: add_int_double \
), \
double: _Generic((B), \
double: add_double_double \
) \
)(A, B)Key Considerations and Best Practices
- Default Associations: Always consider providing a
defaultbranch to prevent cryptic compiler errors or to invoke a safe fallback implementation. - Lvalue Decay: Arguments passed into
_Genericundergo standard type conversions: array names decay into pointers, and functions decay into function pointers. Qualifiers likeconston non-pointer values are dropped during type selection. - Strict Type Safety: All branches in a
_Genericexpression must be syntactically valid code, even if they are not selected. If you resolve to expressions rather than function identifiers, ensure that every branch compiles against the argument context. - Performance: Because dispatch happens during the
compilation phase,
_Genericintroduces no dynamic dispatch tables, branching instructions, or cache misses. The generated assembly is identical to calling the underlying specialized functions directly.