_Generic 选择


C11 提供了_Generic选择,用来模拟泛型程序,其本质是类似switch的选择陈述,不过是编译时期根据类型来选择展开的对象。例如:

#define V_TYPE 0
#define WAT _Generic(V_TYPE, float: 2.0,     \
                             char *: "XD",   \
                             int: 10,        \
                             default: 'a')

根据V_TYPE的类型,WAT会展开为 2.0、"XD"、10 等,如果没有符合的类型,就使用default'a'

其应用之一,就是用来模拟 C 语言本身不支持的函数重载(function overloading),例如,根据参数类型的不同,选择真正对应的函数,

像是 math.h 中定义有cbrtcbrtlcbrtf等函数,可用来求得doublelong doublefloat等实参的立方根,基本上可以如下使用:

#include <stdio.h>
#include <math.h>

int main(void) {
    double x = 8.0;
    const float y = 3.375;
    printf("cbrt(8.0) = %f\n", cbrt(x));  
    printf("cbrtf(3.375) = %f\n", cbrtf(y));  

    return 0;
}

然而,如果想以同一个名称来调用,可以定义_Generic选择:

#include <stdio.h>
#include <math.h>

#define cbrt(X) _Generic((X), long double: cbrtl, \
                              float: cbrtf,       \
                              default: cbrt       \
                        )(X)

int main(void){
    double x = 8.0;
    const float y = 3.375;
    printf("cbrt(8.0) = %f\n", cbrt(x));  
    printf("cbrtf(3.375) = %f\n", cbrt(y));  

    return 0;            
}

cbrtf(3.375)为例,xfloat类型,_Generic透过第一个(X)展开后的(3.375)比对后选择默认的cbrtf,之后结合第二个(X)展开后的(3.375)成为cbrtf(3.375)

当然,只要选择有依据,也可以是多个参数,例如:

#include <stdio.h>

#define foo(a, b) _Generic((a), int: foo1,     \
                                default: foo2  \       
                          )(a, b)

void foo1(int a, int b) {
    printf("%d %d\n", a, b);
}

void foo2(double a, int b) {
    printf("%f %d\n", a, b);
}

int main(void){
    foo(1, 10);
    foo(1.0, 10);

    return 0;            
}

在上面的范例中,选择的依据是第一个参数的类型,在更复杂的范例中,可能要根据第二个参数的类型来选择,这时可以如下:

#include <stdio.h>
#include <math.h>

#define foo(a, b)                \
    _Generic((a),                \
        int: foo1,               \
        double: _Generic((b),    \
                    int : foo2,  \
                    double: foo3 \
                )                \
    )(a, b)

void foo1(int a, int b) {
    printf("%d %d\n", a, b);
}

void foo2(double a, int b) {
    printf("%f %d\n", a, b);
}

void foo3(double a, double b) {
    printf("%f %f\n", a, b);
}

int main(void){
    foo(1, 5);
    foo(1.0, 10);
    foo(1.0, 3.14);

    return 0;            
}

当然,还可以结合...__VA_ARGS__等,编写更复杂的宏,只不过很难编写与维护,该用在哪些场合,还是得在可读性等方面评估一下。


展开阅读全文