Writing generic code in C
In this article, we’ll tackle one of C’s major limitations: the lack of built-in generics. While C lacks the elegant template systems of C++ or Java, there are ways to achieve generic behavior, and understanding them can dramatically improve your code’s flexibility and maintainability. For years, macros were the go-to solution, but they have significant drawbacks, primarily their lack of type safety.
Table of Contents
Introduction
Let’s face it: writing the same function multiple times for different data types is tedious and error-prone. You end up with almost identical code blocks scattered throughout your project, increasing the risk of inconsistencies and making maintenance a nightmare. This is where the desire for generics (a way to write code that works with various data types without needing to rewrite it each time) becomes much appreciated.
Why not use Macros
Before diving into modern techniques, let’s acknowledge the elephant in the room: macros. While they might seem like a simple solution initially, using macros to create “generic” functions is essentially creating a text substitution. This means that the type checking happens after the preprocessor expands the macro, potentially leading to hard-to-find errors during compilation or even runtime.
Not that the solution that I am proposing here doesn’t involve macros. The difference as you will see though would be that the macros are just an aliasing mechanism but in the background the _Generic
keyword will be handled correctly with the right safety checks.
The _Generic keyword
The _Generic
keyword, introduced in C11, provides a powerful mechanism for creating type-based conditional expressions. It allows you to select different expressions to evaluate based on the type of a given input, offering a more type-safe and readable alternative to complex macro-based solutions for achieving generic behavior.
This keyword will provide you with a way to call different expressions based on a variable type. So it splits into two parts:
- Value and type checks – work essentially like a switch statement
- Resulting expressions
The syntax is as follows:
_Generic (expression, type1 : expression1, type2 : expression2, ..., default : defaultexpression)
Let’s look at an example of a macro using a generic:
#include <stdio.h>
#define print_value(v) _Generic(v, float: printf("%f", v), int: printf("%d", v), default: printf("anything else"))
int main(int argc, char** argv) {
int value1 = 5;
float value2 = 5.0f;
print_value(value1);
print_value(value2);
}
We’ve essentially created a templated function called print_value which can take multiple types of arguments and the expressions will differ based on the type.
A templating trick
This feature might have been an overload mechanism but it actually opens up a few more possibilities. One of them is that we can actually create a constant expression if much like in C++:
#define CONSTEXPR_IF(condition, then, otherwise) \
_Generic(&(char[1 + !!(EXPR)]){0}, \
char(*)[2]: (then), \
char(*)[1]: (otherwise))
Here if the condition can be evaluated by the compiler it will produce either a char[2]
or char[1]
. We can use this to conditionally switch between two statements at compile time without using predefined macros. This means that we can use variable constants for this.
Conclusion
The _Generic
keyword is a very strong tool in modern C that allows programmers to write more efficient and checked code without having to worry about the variable types. There is even a header called tgmath.h
which implements most the math functions in a type generic way.
For anyone wondering, the code needs to be compiled using : gcc -std=c11
This is not generic, it is poor man's overloading.
Leave a comment