C - Generic Selection with _Generic in C

Introduction

Generic selection is a feature introduced in the C11 standard that allows a C program to select an expression based on its data type. It is implemented using the _Generic keyword.

C does not provide function overloading in the same way as languages such as C++ or Java. For example, you cannot normally create multiple functions with the same name that accept different data types. _Generic provides a way to achieve a form of compile-time type selection.

The compiler examines the type of an expression and selects the corresponding expression from a list of possible choices. This selection happens during compilation, not while the program is running.

Basic Syntax

The general syntax of _Generic is:

_Generic(
    controlling_expression,
    type1: expression1,
    type2: expression2,
    type3: expression3,
    default: default_expression
)

The controlling_expression is the expression whose type is examined.

The compiler compares its type with the types specified in the associations. When a matching type is found, the corresponding expression is selected.

For example:

#include <stdio.h>

int main() {
    int x = 10;

    printf("%s\n",
           _Generic(x,
                    int: "Integer",
                    float: "Float",
                    double: "Double",
                    default: "Other"));

    return 0;
}

Here, x is an int. Therefore, the compiler selects:

"Integer"

The output is:

Integer

How _Generic Works

Consider the following example:

double value = 25.5;

printf("%s\n",
       _Generic(value,
                int: "Integer",
                float: "Float",
                double: "Double",
                default: "Unknown"));

The compiler examines the type of value.

The type is:

double

It then checks the available associations:

int     -> "Integer"
float   -> "Float"
double  -> "Double"

Since the expression has type double, the compiler selects:

"Double"

Therefore, the output is:

Double

Using _Generic with Different Data Types

A useful application is determining the type of different variables.

#include <stdio.h>

#define TYPE_NAME(x) _Generic((x), \
    int: "int",                    \
    float: "float",                \
    double: "double",              \
    char: "char",                  \
    default: "unknown")

int main() {
    int a = 10;
    float b = 10.5f;
    double c = 20.5;
    char d = 'A';

    printf("%s\n", TYPE_NAME(a));
    printf("%s\n", TYPE_NAME(b));
    printf("%s\n", TYPE_NAME(c));
    printf("%s\n", TYPE_NAME(d));

    return 0;
}

Output:

int
float
double
char

In this example, _Generic is combined with a macro to create a convenient type-selection mechanism.

_Generic and Function Selection

One of the most useful applications of _Generic is selecting different functions according to the type of an argument.

For example:

#include <stdio.h>

void printInt(int x) {
    printf("Integer: %d\n", x);
}

void printDouble(double x) {
    printf("Double: %.2f\n", x);
}

void printChar(char x) {
    printf("Character: %c\n", x);
}

#define printValue(x) _Generic((x), \
    int: printInt,                  \
    double: printDouble,            \
    char: printChar                 \
)(x)

int main() {
    printValue(10);
    printValue(15.75);
    printValue('A');

    return 0;
}

Output:

Integer: 10
Double: 15.75
Character: A

The important part is:

#define printValue(x) _Generic((x), \
    int: printInt,                  \
    double: printDouble,            \
    char: printChar                 \
)(x)

For an int, _Generic selects printInt.

For a double, it selects printDouble.

For a char, it selects printChar.

The selected function is then called with the supplied argument.

The default Association

The default association can be used when none of the specified types matches.

#include <stdio.h>

#define TYPE_NAME(x) _Generic((x), \
    int: "int",                    \
    float: "float",                \
    double: "double",              \
    default: "other")

int main() {
    long value = 100;

    printf("%s\n", TYPE_NAME(value));

    return 0;
}

Since long is not explicitly listed, the default expression is selected.

Output:

other

Using default is useful when a program needs to handle unexpected or unsupported types.

Compile-Time Selection

A major characteristic of _Generic is that selection occurs at compile time.

For example:

int x = 10;

_Generic(x,
         int: printf("Integer"),
         double: printf("Double"));

The compiler determines that x is an int and selects the first expression.

This is different from a normal runtime decision such as:

if (condition) {
    ...
} else {
    ...
}

An if statement evaluates a condition while the program is running. _Generic instead uses the type information available to the compiler.

_Generic Is Not Runtime Type Checking

It is important to understand that _Generic does not inspect the actual value of a variable to determine its type.

For example:

int x = 10;

and:

int x = 5000;

both have exactly the same type:

int

Therefore, _Generic selects the same association regardless of the value stored in x.

It is concerned with the type of the expression, not the value contained in it.

Multiple Type Associations

A _Generic expression can contain many type associations.

#define CATEGORY(x) _Generic((x), \
    char: "Character",             \
    short: "Short Integer",        \
    int: "Integer",                \
    long: "Long Integer",          \
    float: "Floating Point",       \
    double: "Double Precision",    \
    default: "Other")

This allows a program to provide different behavior for multiple data types.

For example:

printf("%s\n", CATEGORY(10));
printf("%s\n", CATEGORY(10L));
printf("%s\n", CATEGORY(10.5));

The compiler selects the appropriate association based on the type of each expression.

Advantages of _Generic

The _Generic feature provides several benefits.

First, it enables a form of type-based selection in C without requiring runtime type information.

Second, it can be combined with macros to create reusable interfaces.

Third, it can help implement type-specific operations while maintaining a common interface.

For example, a library could provide:

printValue(10);
printValue(10.5);
printValue('A');

while internally selecting the appropriate function for each type.

It can also improve type safety compared with manually passing type identifiers to a generic function.

Limitations of _Generic

_Generic also has limitations.

It does not provide complete function overloading. Each supported type must be explicitly associated with an expression.

For example:

_Generic(x,
         int: function1,
         double: function2,
         char: function3)

Only the types specified in the selection list are handled.

Another important consideration is that C's type rules can affect the type of the controlling expression. Expressions may undergo conversions in certain contexts, so programmers need to understand C's type-conversion rules when using _Generic.

The feature is also available starting with C11, so code intended for older C standards may not support it.

Practical Example

The following program demonstrates a simple generic maximum operation:

#include <stdio.h>

int maxInt(int a, int b) {
    return (a > b) ? a : b;
}

double maxDouble(double a, double b) {
    return (a > b) ? a : b;
}

#define MAX(a, b) _Generic((a), \
    int: maxInt,                \
    double: maxDouble            \
)(a, b)

int main() {
    printf("%d\n", MAX(10, 20));
    printf("%.2f\n", MAX(12.5, 8.5));

    return 0;
}

Output:

20
12.50

When MAX(10, 20) is used, _Generic selects maxInt.

When MAX(12.5, 8.5) is used, it selects maxDouble.

This demonstrates how _Generic can provide a common interface while selecting type-specific implementations.

Applications of _Generic

Generic selection can be useful in several areas of C programming, including:

  1. Type-specific utility functions

  2. Generic mathematical operations

  3. Type-aware debugging utilities

  4. Library interface design

  5. Type-dependent formatting

  6. Generic macros

  7. Compile-time type selection

  8. Creating safer reusable APIs

It is particularly useful when a programmer wants one interface to work with several known data types while still maintaining separate implementations internally.

Conclusion

_Generic is a C11 feature that enables compile-time selection based on the type of an expression. It provides a practical way to implement type-dependent behavior in a language that does not traditionally support function overloading.

Its general structure is:

_Generic(
    expression,
    type1: result1,
    type2: result2,
    default: resultDefault
)

The compiler examines the type of the controlling expression and selects the corresponding expression. When combined with macros and functions, _Generic can be used to build flexible, reusable, and type-aware C programs.