芝士就是菜

芝士就是菜

公众号 芝士就是菜
zhihu
bilibili
youtube
twitter
github
email

Shocking!!! Can C language also achieve generic programming??

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:

image.png

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:

image.png

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:

image.png

Now it looks more like C++ generic programming, right? It's quite interesting, although C++ generics are still more powerful.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.