模板为C++提供了鸭子类型(Duck typing)的特性。所谓鸭子类型,指的是代码关注的不是对象的类型本身,而是它被如何使用的。例如,在使用鸭子类型的语言中,我们编写一个函数可以接受一个任意类型的对象,只要它有走、游泳和嘎嘎叫方法。至于客户给它传入的是一只真正的鸭子,或是也能走、游泳和嘎嘎叫的其它类型对象,都没有关系。但是如果传入的对象中没有这些需要被调用的方法,就将引发一个错误。
" When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck. - James Whitcomb Riley, 1849-1916"
我们看下面这个例子:
template<typename T>
T max(const T& t1, const T& t2)
{
return (t1 > t2)? t1 : t2;
}
对max
,只约束入参类型T支持>
比较运算,而不关心它的具体类型。例如我们可以为max
传入int
、float
或者是实现了了opertator >
的任何对象。
直到现在,C++中模板对入参的约束都是通过对入参的使用方式来隐式体现的。而有的语言却可以显示约束。例如对于如下Haskell代码:
max' :: (Ord a) => a -> a -> a
max' x y
| x > y = x
| otherwise = y
如上max
的定义前面通过函数声明:max' :: (Ord a) => a -> a -> a
约束了入参的类型a
必须满足Ord
类型类的约束。类型类用于规范一组类型应该满足的特征,例如Ord
要求满足它的类型必须能够进行标准的比较操作,如<
、>
、<=
、>=
等。
C++17标准有可能会引入concept特性用来支持上述haskell中对类型特征进行显示约束的能力。显示化类型约束可以让代码更容易被理解,让编译器可以更准确的报告错误或者对代码更好地做出优化,同时也可以让IDE对语言更好地支持。
鸭子类型为程序的书写带来了很多便利性,基本上动态语言(Python、Ruby)以及拥有类型推导的静态语言(C++、Haskell、Scala)都有这个特性。区别在于动态语言一般是在运行期发现类型不满足约束,而静态语言通过强大的类型推导可以在编译期就发现错误。
回到最后,我们思考下,如果把C++模板元编程当做一门独立的语言,它自身是否支持鸭子类型呢?
答案很明确,虽然模板为“运行时C++”提供了鸭子类型的能力,但模板元编程自身却不支持鸭子类型。
例如下面的元函数明确要求其入参的型别为类型,所以你可以这样使用SizeOf<int>
。但是一旦你传入一个数值SizeOf<5>
,它就会报错。
template<typename T>
struct SizeOf
{
using Result = __int(sizeof(T));
};
我们之前总结过,模板可以操作的计算对象大体可以分为数值和类型两大类,一旦我们把模板当做编译期函数来看,就会发现它是强类型的。一个模板声明其入参是数值型,就不能接收类型作为入参,反之亦然!这就是为何我们为了提高元函数的组合复用能力,将所有的数值也封装成了类型。我们统一模板元编程的计算对象类型,就相当于把一切都变成了鸭子,间接地也得到了鸭子类型的好处。
最后,我们单独看待模板元编程的时候,它相当是一门解释型语言!C++编译器直接面对模板元编程的源代码进行编译期计算,这时我们可以将C++编译器看做是模板元编程的解释器,它一边解释一边执行,解释结束之时也是程序执行完毕之时。有趣吧?在这个角度看模板元编程反而更像是一门脚本语言。
网友评论