08. typeof和typeof_unqual
如果让我用一句话来评价typeof和typeof_unqual,那必须是“期望越大,失望越大”。
n3054对typeof和typeof_unqual的描述如下:


从已有类型声明出变量这种事情我们是司空见惯了,typeof则是反过来,从表达式得到一个类型(也可以从类型到类型,我变我自己),typeof_unqual更进一步,它可以脱掉顶层的cvr限定符和原子属性。
typeof本该是一个很好的新特性,它总让我不由得想到另一个东西:_Generic。
_Generic绝对是一个气死强迫症的半吊子设计,_Generic这个丑不拉几的名字就不说了,它的整个语法也是莫名其妙,跟C++的函数重载和模板函数比起来,_Generic简直就是一个(无法用语言形容的)乐色。要不是预处理指令做不到_Generic能做的事情,鬼都不会去用这破玩意儿。
我不知道有多少人会把_Generic当成是一种预处理特性,毕竟在_Generic的大本营<tgmath.h>里面,_Generic就总是和#define出现在一起,也难怪,_Generic的诞生就是为了给<tgmath.h>的泛型宏提供一种标准化的实现方法,也让程序员具有写泛型宏的能力,弄得_Generic好像必须和宏出现在一起似的。直到我仔细看了标准对_Generic的描述,才惊讶地发现:哦,原来这玩意儿是一个表达式啊?它居然是一个表达式?
_Generic就像是类型版本的switch。假设T只能是int, long, float和double其中之一,有如下代码:

当然一般情况下会把这团_Generic做成一个宏:

如果typeof具有我想象中的特性……好吧,如果真的有如果的话,上面那段代码可以写成这样:

直接利用已有的运算符,岂不美哉?
“不对!”有人可能会说,“条件运算符的后两个操作数不是随便什么类型都能组合在一起的。”嗯,很有道理,不过只要加上两条限制就可以解决这个小小的bug:
1st:条件运算符的条件要么是类型判断的组合,要么是正常的表达式;
2nd:如果条件运算符的条件是类型判断的组合,那么条件运算符的结果是可以在编译时确定的,这时条件运算符的类型和值就是被选择的分支的类型和值,未被选择的分支则完全不生成代码。
也许上述abs的例子看不出来后一种写法有什么优势,然而如果需要判断类型的参数不止一个,情况就不一样了。
<math.h>的fmax函数有两个double类型的参数x和y,它有对应的float和long double版本,以及一个同名的泛型宏。因为有两个参数,所以这个泛型宏通常的做法是把x和y加起来,利用隐式转换找到x和y的公共类型,然后再用_Generic判断这个公共类型。把x和y加起来再判断typeof自然也能做到,typeof甚至能做到单独判断x和y的类型,这点让_Generic来做可就不是那么容易了。
如果说x和y都是整数或浮点数还能用x+y来投机取巧的话,那么把参数类型换成指针可就算是把_Generic逼到墙角了。假设有四个函数:
char *sscat(char *s1, const char *s2);
char *swcat(char *s1, const wchar_t *s2);
wchar_t *wscat(wchar_t *s1, const char *s2);
wchar_t *wwcat(wchar_t *s1, const wchar_t *s2);
现在要设计泛型宏cat涵盖以上四个函数,怎么做?(请问_Generic,你不会想把s1和s2这两个指针加起来找公共类型吧?)
typeof拍着胸脯表示:“掌握之中,岂可逃之?”几行代码顺势而出:

(最后的nullptr是没有问题的,毕竟没有default的_Generic在匹配失败的情况下也会产生编译错误)
_Generic当然也能实现同样的功能,只要把两重_Generic套娃在一起就可以了,不过这样子的话,写出来的代码可能会不太好看。
可惜以上的一切都只是虚幻的想象,typeof不过是一个类型说明符,它根本就不是表达式,所以自然不能写typeof(x)==int这种代码。唉,说了这么多,不过是一场梦罢了,typeof终究不是炫酷的变形金刚,它只想安静地做一辆普普通通的玩具车。