Introduced in C11, generic selections are a powerful feature in the C programming language that enable you to write more flexible and type-safe code. By allowing you to choose an expression based on the type of another expression, they essentially bring a form of type polymorphism to C. This article will explain what generic selections are, how they work, and provide several practical examples to help you master this feature.
What is a Generic Selection?
In C, a generic selection allows you to evaluate an expression based on its type at compile-time. This is particularly useful when writing functions or macros that need to behave differently depending on the type of their arguments. The syntax for a generic selection is as follows:
_Generic(expression, type1: result1, type2: result2, ..., default: default_result)
expression: The value whose type you want to inspect.
type1, type2, ...: Possible types of the expression.
result1, result2, ...: Corresponding values or expressions to evaluate for each type.
default: Optional. The value to evaluate if no type matches.
Basic Example
Let's start with a simple example to understand the basics of _Generic
. This example prints the type of a given variable:
#include <stdio.h>
#define TYPE_OF(x) _Generic((x), \
int: "int", \
float: "float", \
double: "double", \
char*: "string", \
default: "unknown type" \
)
int main() {
int i = 42;
float f = 3.14f;
double d = 2.71828;
char *s = "Hello, World!";
printf("Type of i: %s\n", TYPE_OF(i));
printf("Type of f: %s\n", TYPE_OF(f));
printf("Type of d: %s\n", TYPE_OF(d));
printf("Type of s: %s\n", TYPE_OF(s));
return 0;
}
Output:
Type of i: int
Type of f: float
Type of d: double
Type of s: string
This macro checks the type of the argument and returns a string literal representing its type. If none of the types match, it defaults to "unknown type"
.
Generic Math Function
One common use of generic selections is writing type-generic math functions. For example, consider the following macro that computes the absolute value of a number, regardless of whether it's an int
, float
, or double
:
#include <stdio.h>
#include <math.h>
#define ABS(x) _Generic((x), \
int: abs((x)), \
float: fabsf((x)), \
double: fabs((x)), \
default: (x) \
)
int main() {
int i = -10;
float f = -3.14f;
double d = -2.71828;
printf("Absolute value of i: %d\n", ABS(i));
printf("Absolute value of f: %.2f\n", ABS(f));
printf("Absolute value of d: %.5f\n", ABS(d));
return 0;
}
Output:
Absolute value of i: 10
Absolute value of f: 3.14
Absolute value of d: 2.71828
This macro chooses the correct absolute value function (abs
, fabsf
, or fabs
) depending on the type of its argument.
Type-Specific Operations
Generic selections can be useful when implementing type-specific operations. Here's an example that performs addition but ensures type consistency:
#include <stdio.h>
#define ADD(x, y) _Generic((x), \
int: (x) + (y), \
float: (x) + (y), \
double: (x) + (y), \
default: 0 \
)
int main() {
int a = 10, b = 20;
float x = 1.5f, y = 2.5f;
double p = 3.14, q = 2.71;
printf("Sum of integers: %d\n", ADD(a, b));
printf("Sum of floats: %.2f\n", ADD(x, y));
printf("Sum of doubles: %.2f\n", ADD(p, q));
return 0;
}
Output:
Sum of integers: 30
Sum of floats: 4.00
Sum of doubles: 5.85
This example shows how the same macro can handle multiple data types while maintaining type safety.
Why Use Generic Selections?
Type Safety: Ensures the correct function or operation is used based on the argument's type.
Code Reusability: Write once, use across multiple types.
Maintainability: Reduces the need for type-specific function overloads.
Limitations and Considerations
No Type Conversion:
_Generic
does not perform type conversion. It matches exactly, sofloat
anddouble
are distinct.No Runtime Decisions: Generic selections are evaluated at compile-time, meaning no runtime overhead but also no dynamic type checks.
Readability: Overusing
_Generic
can make the code harder to read, so use it judiciously.
Conclusion
C's _Generic
feature brings a form of type polymorphism to the language, allowing developers to write more flexible and reusable code. Whether it's type checking, writing type-generic functions, or ensuring type safety, generic selections provide a powerful tool for modern C programming.