_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__ 等,撰寫更複雜的巨集,只不過很難撰寫與維護,該用在哪些場合,還是得在可讀性等方面評估一下。