The generic selection is an expression that was added to the C programming language in the C11 standard to facilitates compile-time choices based on the type of an expression. This feature was added to standardize the compiler magic that was required for C99's type-generic math (<tgmath.h>).
Contents
Overview
C99 introduced a new set of math macros called type-generic math which were provided in the <tgmath.h> header. These macros expanded to their appropriate functions based on the type of the arguments that were provided. For example, the cos() macro would expand to cosl(), cosf(), or ccos() depending if the argument is a double, a long double, or a complex double.
The C99 standard made no attempt at explaining how the functionality must be accomplished making each compiler do its own thing. C11 introduced the generic selection expression which attempts to provide a standard mechanism for performing compile-time choices based on a type of an expression.
Syntax
The generic selection expression is implemented with a new _Generic keyword:
_Generic( control-expression , generic-assoc-list );
Where the generic-assoc-list consists of a comma-delimited list of: type-name: assignment-expression pairs and an optional default generic association that takes the form default: assignment-expression.
Inside the generic selection expression, the control-expression does not get evaluated. Its type is used to select one of the pairs from the generic associate list that has a type compatible with the one from the control-expression. Otherwise, the result expression of the generic selection is the expression in the default generic association.
The type and value of the generic selection are identical to those of its result expression.
Examples
Consider the following get_type macro which returns a string representation of the type:
#define get_type(expr) \
_Generic((expr), \
int: "int", \
char: "char", \
double: "double" \
)
We can use it in the following way:
printf("Type of 234: %s\n", get_type(234));
printf("Type of 'A': %s\n", get_type('A'));
printf("Type of (char)'A': %s\n", get_type((char)'A'));
printf("Type of 3.14: %s\n", get_type(3.14));
Which will produce the following result:
Type of 234: int Type of 'A': int Type of (char)'A': char Type of 3.14: double
Uses in <tgmath.h>
The <tgmath.h> header that was added in C99 added support for type-generic math using some compiler magic unspecified by the standard. C11 now provides a standardized facility for achieving this magic. For example, one could implement the cbrt generic function that computes the cube-root like this:
#define cbrt(X) \
_Generic((X), \
long double: cbrtl, \
default: cbrt, \
float: cbrtf \
)(X)
Implementing some of the functions with multiple arguments becomes far more lengthly with a selection for each of the original cases. Consider the pow type-generic function. One could implement it as:
#define tgm_select(expr, ldc, dc, fc, ld, d, f) \
_Generic(expr, \
long double _Complex: ldc, \
double _Complex: dc, \
float _Complex: fc, \
long double: ld, \
default: d, \
float: f)
#define pow(x, y) \
_Generic((y), \
long double _Complex: tgm_select(x, cpowl, cpowl, cpowl, cpowl, cpowl, cpowl), \
double _Complex: tgm_select(x, cpowl, cpow , cpow , cpowl, cpow , cpow ), \
float _Complex: tgm_select(x, cpowl, cpow , cpowf, cpowl, cpow , cpowf), \
long double: tgm_select(x, cpowl, cpowl, cpowl, powl, powl, powl), \
default: tgm_select(x, cpowl, cpow , cpow , powl, pow , pow ), \
float: tgm_select(x, cpowl, cpow , cpowf, powl, pow , powf), \
)(x, y)