模板為C++提供了鴨子類型(Duck typing)的特性。所謂鴨子類型,指的是代碼關注的不是對象的類型本身,而是它被如何使用的。例如,在使用鴨子類型的語言中,我們編寫一個函數可以接受一個任意類型的對象,只要它有走、游泳和嘎嘎叫方法。至于客戶給它傳入的是一只真正的鴨子,或是也能走、游泳和嘎嘎叫的其它類型對象,都沒有關系。但是如果傳入的對象中沒有這些需要被調用的方法,就將引發(fā)一個錯誤。
" 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
類型類的約束。類型類用于規(guī)范一組類型應該滿足的特征,例如Ord
要求滿足它的類型必須能夠進行標準的比較操作,如<
、>
、<=
、>=
等。
C++17標準有可能會引入concept特性用來支持上述haskell中對類型特征進行顯示約束的能力。顯示化類型約束可以讓代碼更容易被理解,讓編譯器可以更準確的報告錯誤或者對代碼更好地做出優(yōu)化,同時也可以讓IDE對語言更好地支持。
鴨子類型為程序的書寫帶來了很多便利性,基本上動態(tài)語言(Python、Ruby)以及擁有類型推導的靜態(tài)語言(C++、Haskell、Scala)都有這個特性。區(qū)別在于動態(tài)語言一般是在運行期發(fā)現類型不滿足約束,而靜態(tài)語言通過強大的類型推導可以在編譯期就發(fā)現錯誤。
回到最后,我們思考下,如果把C++模板元編程當做一門獨立的語言,它自身是否支持鴨子類型呢?
答案很明確,雖然模板為“運行時C++”提供了鴨子類型的能力,但模板元編程自身卻不支持鴨子類型。
例如下面的元函數明確要求其入參的型別為類型,所以你可以這樣使用SizeOf<int>
。但是一旦你傳入一個數值SizeOf<5>
,它就會報錯。
template<typename T>
struct SizeOf
{
using Result = __int(sizeof(T));
};
我們之前總結過,模板可以操作的計算對象大體可以分為數值和類型兩大類,一旦我們把模板當做編譯期函數來看,就會發(fā)現它是強類型的。一個模板聲明其入參是數值型,就不能接收類型作為入參,反之亦然!這就是為何我們?yōu)榱颂岣咴瘮档慕M合復用能力,將所有的數值也封裝成了類型。我們統一模板元編程的計算對象類型,就相當于把一切都變成了鴨子,間接地也得到了鴨子類型的好處。
最后,我們單獨看待模板元編程的時候,它相當是一門解釋型語言!C++編譯器直接面對模板元編程的源代碼進行編譯期計算,這時我們可以將C++編譯器看做是模板元編程的解釋器,它一邊解釋一邊執(zhí)行,解釋結束之時也是程序執(zhí)行完畢之時。有趣吧?在這個角度看模板元編程反而更像是一門腳本語言。