Today I came across an interesting concept and wanted to record it. As we all know, C language does not have generics, but C11 introduced a new expression called generic selection expression (_Generic). What does it do? Its function is to select a value based on the type of the expression. Let's take a look at its specific syntax.
Introduction to Generic Selection#
Let's start with a code snippet:
int main()
{
int x = 1;
double y = 2.0;
char z = 'c';
printf("%d\n", _Generic(x, int:0, double : 1, default:3));
printf("%d\n", _Generic(y, int:0, double : 1, default:3));
printf("%d\n",_Generic(z, int:0, double : 1, default:3));
return 0;
}
_Generic
is a C11 keyword. The parentheses following it contain multiple items separated by commas. The first item is an expression, and each subsequent item consists of a type, a colon, and a value, such as double: 1
. The value of the entire expression is determined by the type that matches the first item.
The output is as follows:
As you can see, the output varies based on the type of the variable passed in. In the first printf
, the first item x
is of type int
, so the result of the expression is 0. In the second printf
, the first item is of type double
, so the result is 1. The third printf
prints 3 because the type char
does not match any label, so the default value is used.
In fact, this is similar to a switch statement, but with _Generic
, the labels are matched based on the type of the expression, while with switch, the labels are matched based on the value of the expression.
Combination with Macros#
We can see that the usage above is a bit cumbersome, but we can combine it with macros to make it more convenient. Let's look at an example:
#define MYTYPE(X) _Generic((X),int:"int", double:"double", default:"other")
int main()
{
int d = 2;
printf("%s\n", MYTYPE(d));
printf("%s\n", MYTYPE(1.0*d));
printf("%s\n", MYTYPE("string"));
return 0;
}
The output is as follows:
Combining it with macros makes it much easier, and it even feels like C++ generic programming, although it's still not as powerful as C++ generics.
Advanced Usage#
The value corresponding to the _Generic
label can be an integer, a string, or even a function pointer. Let's take a look at the following code:
void PrintInt(int x)
{
printf("%d\n", x);
}
void PrintDouble(double x)
{
printf("%lf\n", x);
}
void PrintString(char* x)
{
printf("%s\n", x);
}
void PrintOther(void x)
{
printf("There is a problem with the type\n");
}
#define PRINT(X) _Generic((X),\
int:PrintInt,\
double:PrintDouble,\
const char*:PrintString,\
default:PrintOther)(X)
int main()
{
int x = 1;
int y = 2.0;
const char* str = "hello _Generic";
PRINT(x);
PRINT(y);
PRINT(str);
return 0;
}
The output is as follows:
Now it looks more like C++ generic programming, right? It's quite interesting, although C++ generics are still more powerful.