Back to: C Tutorials For Beginners and Professionals
Generic Selection in C Language with Examples
In this article, I will discuss Generic Selection in C Language with Examples. Please read our previous article discussing Multithreading in C Language with Examples. At the end of this article, you will understand the following pointers:
- What is Generic Selection in C?
- Type-Specific Math Operations Example Using Generic Selection in C
- Logging Different Data Types Example Using Generic Selection in C
- Min Function for Different Types Example Using Generic Selection in C
- Type-Generic Square Macro Example Using Generic Selection in C
- When to Use Generic Selection in C?
- Advantages and Advantages of Using Generic Selection in C
What is Generic Selection in C?
Generic selection, introduced in C11 (the 2011 version of the C standard), is a feature that allows the writing of type-generic expressions in C. It is especially useful in creating macros that work with different types of arguments without losing type safety. The generic selection is not a function or a preprocessor directive but a special kind of expression. This is done using the _Generic keyword.
Syntax of Generic Selection
The syntax for a generic selection expression is as follows:
_Generic( expression, type1: expression1, type2: expression2, …, default: expressionN )
- expression: This is the expression whose type is to be evaluated.
- type1, type2, …, default: These are the types against which the type of expression is compared.
- expression1, expression2, …, expressionN: These are the expressions that get evaluated depending on the type of expression.
The type of the entire _Generic expression is the type of the expression that gets selected.
Points to Note:
- Generic selection is evaluated at compile time, not at runtime.
- It does not evaluate to a value but to an expression, which is then evaluated.
- This feature is particularly useful for creating type-generic macros, reducing the need for multiple macro versions for different types.
- It enhances type safety in macros by ensuring that the macro behaves correctly according to the type of argument.
Let us proceed and see more examples to understand this concept in a better manner.
Example: Type-Specific Math Operations Using Generic Selection in C
Creating a type-specific macro for the absolute value using generic selection in C11 allows you to handle different data types with a single macro. This approach is particularly useful when working with various numeric types such as int, float, and double. Let’s consider an example where we create a macro for calculating the absolute value:
#include <stdio.h> #include <stdlib.h> // For abs() #include <math.h> // For fabs() and fabsf() // Define a generic macro for absolute value #define absValue(x) _Generic((x), \ int: abs, /* Standard function for integers */ \ float: fabsf, /* Standard function for floats */ \ double: fabs /* Standard function for doubles */ \ )(x) int main() { int i = -10; float f = -5.5f; double d = -12.3; printf("Absolute value of %d (int): %d\n", i, absValue(i)); printf("Absolute value of %f (float): %f\n", f, absValue(f)); printf("Absolute value of %lf (double): %lf\n", d, absValue(d)); return 0; }
When you run the above code, you will get the following output:
Explanation
- The absValue macro uses _Generic to determine the type of its argument. It then calls the appropriate function based on whether the argument is an int, float, or double.
- For int types, it calls abs, which is a standard function in stdlib.h.
- For float and double types, it uses fabsf and fabs, respectively, both of which are standard functions in math.h.
- In the main function, absValue is used with different types of arguments to demonstrate its type-generic behavior.
Example: Logging Different Data Types Using Generic Selection in C
Generic selection can be handy when you have to log variables of different data types. For instance, you might want to log integers, floats, and strings differently.
#include <stdio.h> #define logVar(x) _Generic((x), \ int: logInt, \ float: logFloat, \ char*: logString \ )(x) void logInt(int x) { printf("Integer: %d\n", x); } void logFloat(float x) { printf("Float: %f\n", x); } void logString(char* x) { printf("String: %s\n", x); } int main() { int i = 10; float f = 10.5f; char* s = "Hello, World!"; logVar(i); logVar(f); logVar(s); return 0; }
In this case, logVar selects the correct logging function based on the type of the provided variable. When you run the above code, you will get the following output:
Example: Min Function for Different Types Using Generic Selection in C
Creating a type-generic min function that works with different numeric types is another common use case:
#include <stdio.h> #define min(x, y) _Generic((x), \ int: minInt, \ long: minLong, \ double: minDouble \ )(x, y) int minInt(int x, int y) { return (x < y) ? x : y; } long minLong(long x, long y) { return (x < y) ? x : y; } double minDouble(double x, double y) { return (x < y) ? x : y; } int main() { printf("Min (int): %d\n", min(10, 20)); printf("Min (long): %ld\n", min(10L, 20L)); printf("Min (double): %f\n", min(10.5, 20.5)); return 0; }
Here, min uses generic selection to call the right version of the min function based on whether x is an int, long, or double. When you run the above code, you will get the following output:
Example: Type-Generic Square Macro Using Generic Selection in C
Creating a type-generic macro for calculating the square of a number is a great example to demonstrate the use of generic selection in C. This macro can automatically choose the appropriate calculation based on whether the input is an integer, a float, or a double. Here’s how you can implement it:
#include <stdio.h> // Define the square macro using generic selection #define square(x) _Generic((x), \ int: squareInt, \ float: squareFloat, \ double: squareDouble \ )(x) // Functions for different types int squareInt(int x) { return x * x; } float squareFloat(float x) { return x * x; } double squareDouble(double x) { return x * x; } int main() { int i = 4; float f = 5.5f; double d = 6.5; printf("Square of %d (int): %d\n", i, square(i)); printf("Square of %f (float): %f\n", f, square(f)); printf("Square of %lf (double): %lf\n", d, square(d)); return 0; }
When you run the above code, you will get the following output:
Explanation:
- The square macro uses _Generic to determine the type of its argument. It then calls the appropriate function (squareInt, squareFloat, or squareDouble) based on whether the argument is an int, float, or double.
- Each of these functions takes an argument of a specific type and returns the square of that value.
- In the main function, the square macro is used with different types of arguments to demonstrate its type-generic nature.
Important Notes on _Generic keyword:
- The _Generic keyword provides a way to implement function overloading in C, which is not natively supported as in C++.
- The selected expression is evaluated only once, and the selection is made at compile time, not run time.
- Ensuring that the expressions in _Generic cover all the types you expect the macro to handle is important. The default label can catch any unspecified types.
- _Generic can only discriminate types based on runtime conditions, not values or expressions.
When to Use Generic Selection in C?
Generic selection in C, introduced in the C11 standard, is particularly useful in scenarios where you want to create type-generic code. It allows you to write macros or expressions that behave differently based on the type of argument provided. Here are some common situations where you might use generic selection:
- Creating Type-Generic Macros: When you want a macro to work with different data types and perform type-specific operations. For instance, you might have a macro that needs to handle both float and double types differently.
- Simplifying Function Overloading: C doesn’t support function overloading as C++ does, but generic selection can be used to mimic this behavior to some extent. You can create macros that behave like overloaded functions, choosing different functions or code paths based on the argument’s type.
- Avoiding Type Casting Errors: There’s a risk of type-casting errors when dealing with multiple data types. Generic selection can help prevent these errors by ensuring the correct code is executed for each type.
- Enhancing Code Readability and Maintainability: Instead of writing separate code blocks for each data type, you can use generic selection to consolidate these into a single, more readable macro. This makes your code cleaner and easier to maintain.
- Implementing Type-Specific Safety Checks: In situations where certain operations are only valid for specific types, generic selection can ensure that the correct checks are performed based on the input type.
- Developing Libraries and APIs: If you’re developing libraries or APIs that are meant to be used with different data types, generic selection can be an effective way to make your functions more versatile and user-friendly.
- Optimizing Performance for Different Types: In some cases, you might have different optimization strategies for different types, like using specific hardware instructions for certain data types. Generic selection allows you to select the most efficient approach based on the type.
Advantages of Using Generic Selection in C:
- Type Safety: Generic selection helps ensure type safety by selecting the appropriate function or expression based on the type of the argument. This reduces the risk of type-related errors.
- Code Reusability and Maintainability: It enables writing more generic, reusable code. You can write a single macro or function that works with different data types, reducing code duplication.
- Function Overloading-like Behavior: C doesn’t natively support function overloading like C++. Generic selection provides a way to mimic this behavior, allowing a single function name to handle different data types.
- Compile-Time Evaluation: The selection of the appropriate expression or function is done at compile time, leading to efficient code without runtime overhead.
- Improved Readability: When used wisely, the code can be more readable and concise, as it abstracts the type-specific details.
Disadvantages of Using Generic Selection in C:
- Complexity: It can make the code more complex, especially for unfamiliar with this feature. Understanding and maintaining such code can be more challenging.
- Limited Flexibility: While it allows for type-based selection, it’s still limited compared to templates in C++ or generics in other languages, as it cannot handle new types that weren’t explicitly defined in the _Generic expression.
- Compile-Time Only: It only works at compile time and is based purely on types, not values or runtime conditions. This limits its usefulness in scenarios where runtime decision-making is needed.
- Verbose for Large Sets of Types: If you have a large set of types to handle, the _Generic expression can become unwieldy and hard to manage.
- Misuse: Improper use can lead to code that’s hard to understand and maintain. It can encourage writing overly generic code where more straightforward, type-specific code would be more appropriate.
- Limited Compiler Support: While C11 is widely supported, some compilers might not fully support all features, including _Generic. This can be a concern for cross-platform or legacy system compatibility.
In the next article, I will discuss Static Assertions in C Language. In this article, I explain Generic Selection in C Language with Examples. I hope you enjoy this Generic Selection in C Language with Examples article. I would like to have your feedback. Please post your feedback, questions, or comments about this article.