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 中定義有 cbrt
、cbrtl
、cbrtf
等函式,可用來求得 double
、long double
、float
等引數的立方根,基本上可以如下使用:
#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)
為例,x
是 float
型態,_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__
等,撰寫更複雜的巨集,只不過很難撰寫與維護,該用在哪些場合,還是得在可讀性等方面評估一下。