Skip to content

_Generic

简单介绍

C语言的_Generic表达式是C11标准引入的一个特性,它提供了一种在编译时基于表达式类型选择函数或值的方法。这种机制与C++中的模板元编程相似,但更为简单和受限。

_Generic表达式的基本形式如下

c
_Generic(expr, type1: value1, type2: value2, ..., default: default_value)

其中expr是我们要判断的表达式,type1, type2, ...是可能的类型,value1, value2, ...是对应的类型为真时返回的值,default_value是默认返回的值(当没有任何类型匹配时)。


_Generic表达式的基本思想是,根据expr的类型,从多个候选值中选择一个。这种选择是在编译时进行的,因此_Generic表达式是一种编译时多态。

_Generic表达式可以用于多种场景,比如根据不同的类型选择不同的处理函数,实现类型转换等。它的一个重要特点是,它仅适用于类型,而不是特定的值。因此,我们不能用它来做数值的比较或者复杂的类型检查。此外,__Generic表达式在编译时求值,因此它不能用于运行时类型检查。

在使用_Generic表达式时,我们需要注意的是,它的类型匹配是基于expr的静态类型,而不是它的实际值。这意味着,如果expr是一个指针,那么_Generic表达式将根据指针的类型进行匹配,而不是指针指向的对象的类型。

此外,_Generic表达式的类型匹配是按照它在表达式中的顺序进行的。如果多个类型匹配expr,那么将选择第一个匹配的类型。因此,我们在编写_Generic表达式时,应该将最具体的类型放在前面,将最一般的类型放在后面。

_Generic表达式的一个常见用途是实现泛型宏。在C语言中,宏是类型无关的,这意味着我们不能编写一个宏,它可以接受任何类型的参数并正确地进行类型转换。但是,通过使用_Generic表达式,我们可以编写一个宏,它在编译时根据参数的类型选择正确的表达式。

总的来说,_Generic表达式为C语言提供了一种简单的、编译时的类型选择机制。尽管它的功能有限,但在某些情况下,它仍然能够提供便利和代码的清晰度。

代码实例

简单的类型选择(类型判断)

c
#include <stdio.h>
#include <stdbool.h>

void print_schar(signed char n);
void print_short(short n);
void print_int(int n);
void print_long(long n);
void print_longlong(long long n);
void print_Bool(_Bool n);
void print_uschar(unsigned char n);
void print_ushort(unsigned short n);
void print_uint(unsigned int n);
void print_ulong(unsigned long n);
void print_ulonglong(unsigned long long n);
void print_float(float n);
void print_double(double n);
void print_ldouble(long double n);
void print_char(char n);
void print_charX(char *n);

#define print(n) _Generic(n,             \
    signed char: print_schar,            \
    short: print_short,                  \
    int: print_int,                      \
    long: print_long,                    \
    long long: print_longlong,           \
    _Bool: print_Bool,                   \
    unsigned char: print_uschar,         \
    unsigned short: print_ushort,        \
    unsigned int: print_uint,            \
    unsigned long: print_ulong,          \
    unsigned long long: print_ulonglong, \
    float: print_float,                  \
    double: print_double,                \
    long double: print_ldouble,          \
    char: print_char,                    \
    char *: print_charX)(n)

int main(void)
{
    signed char a = 2;
    short b = 20;
    int c = 5;
    long d = 10;
    long long e = 12;
    _Bool f = true;
    unsigned char g = 4;
    unsigned short h = 6;
    unsigned int i = 8;
    unsigned long j = 10;
    unsigned long long k = 16;
    float l = 2.5;
    double m = 3.5;
    long double n = 4.5;
    char o = 'a';
    char *p = "hello";

    print(a);
    print(b);
    print(c);
    print(d);
    print(e);
    print(f);
    print(g);
    print(h);
    print(i);
    print(j);
    print(k);
    print(l);
    print(m);
    print(n);
    print(o);
    print(p);

    return 0;
}

void print_schar(signed char n) { printf("%d is signed char\n", n); }

void print_short(short n) { printf("%d is short\n", n); }

void print_int(int n) { printf("%d is int\n", n); }

void print_long(long n) { printf("%ld is long\n", n); }

void print_longlong(long long n) { printf("%lld is long long\n", n); }

void print_Bool(_Bool n) { printf("%d is _Bool\n", n); }

void print_uschar(unsigned char n) { printf("%d is unsigned char\n", n); }

void print_ushort(unsigned short n) { printf("%d is unsigned short\n", n); }

void print_uint(unsigned int n) { printf("%u is unsigned int\n", n); }

void print_ulong(unsigned long n) { printf("%lu is unsigned long\n", n); }

void print_ulonglong(unsigned long long n) { printf("%llu is unsigned long long\n", n); }

void print_float(float n) { printf("%f is float\n", n); }

void print_double(double n) { printf("%lf is double\n", n); }

void print_ldouble(long double n) { printf("%Lf is long double\n", n); }

void print_char(char n) { printf("%c is char\n", n); }

void print_charX(char *n) { printf("%s is string\n", n); }

程序输出

shell
$ gcc generic01.c -o main
$ ./main
2 is signed char
20 is short
5 is int
10 is long
12 is long long
1 is _Bool
4 is unsigned char
6 is unsigned short
8 is unsigned int
10 is unsigned long
16 is unsigned long long
2.500000 is float
3.500000 is double
4.500000 is long double
a is char
hello is string
$

类型转换宏

c
#include <stdio.h>


#define TO_INT(x) _Generic((x), \
                           int: (x), \
                           float: (int)(x), \
                           default: (int)(x))

int main(void) {
    int i = 42;
    float f = 3.14f;
    char c = 'A';

    printf("ToInt of i: %d\n", TO_INT(i));
    printf("ToInt of f: %d\n", TO_INT(f));
    printf("ToInt of c: %d\n", TO_INT(c));

    return 0;
}

程序输出

shell
$ gcc generic02.c -o main
$ ./main
ToInt of i: 42
ToInt of f: 3
ToInt of c: 65
$

泛型函数

c
#include <stdio.h>


#define ADD(x, y) _Generic((x), \
                           int: _Generic((y), \
                                          int: (x) + (y), \
                                          default: (x) + (int)(y)), \
                           float: _Generic((y), \
                                            float: (x) + (y), \
                                            default: (x) + (float)(y)), \
                           default: _Generic((y), \
                                              default: (int)(x) + (int)(y)) \
                          )

int main(void) {
    int i = 42;
    float f = 3.14f;
    char c = 'A';

    printf("ADD(i, i): %d\n", ADD(i, i));
    printf("ADD(f, f): %f\n", ADD(f, f));
    printf("ADD(i, f): %d\n", ADD(i, f));
    printf("ADD(c, i): %d\n", ADD(c, i));

    return 0;
}

程序输出

shell
$ gcc generic03.c -o main
$ ./main
ADD(i, i): 84
ADD(f, f): 6.280000
ADD(i, f): 45
ADD(c, i): 107
$