?? 自C++98開始,C++就具備了類型自動推導(dǎo)的能力。在11版本發(fā)布之前,C++只有一種類型推導(dǎo)方法,即模板類型推導(dǎo)。在C++11中額外增加了2種類型推導(dǎo)方法——使用auto、decltype關(guān)鍵字。
?? “C++型別(類型)推導(dǎo)”系列文章會結(jié)合代碼和實(shí)際編譯環(huán)境的輸出來分別說明以上三種情況的型別推導(dǎo)規(guī)則,由于內(nèi)容,篇幅有些多,準(zhǔn)備分為3篇文章分別對C++的3種型別推導(dǎo)進(jìn)行記錄,本文先完整介紹template對型別的推導(dǎo)規(guī)則。
??書上將測試推導(dǎo)的工具和方法放在了最后講,我這里提到最開始來介紹以方便大家測試:
?? 作者首先提倡讀者應(yīng)該在腦海中牢記這些推導(dǎo)規(guī)則,其次也推薦了利用IDE編寫讓編譯器報(bào)錯的一些代碼等方式來查看類型推導(dǎo)結(jié)果,如:
template<typename T>
class TD;//只聲明一個模板類,不定義它,一旦該類被使用,編譯器就會報(bào)錯。
int main(){
const int theAnswer = 42;
auto x = theAnswer;
auto y = &theAnswer;
TD<decltype(x)> xType;//error C2079: “xType”使用未定義的 class“TD<int>”
TD<decltype(y)> yType;//error C2079: “yType”使用未定義的 class“TD<const int *>”
return 0;
}
??從編譯器的報(bào)錯中,我們能清楚地看到類型推導(dǎo)的結(jié)果。
下面所有推導(dǎo)過程都以以下代碼為大致模型:
template <typename T>
void f(ParamType param);
……
f(expr);
一、模板型別推導(dǎo)
這種方式分為三種情況:
1.ParamType是指針或者引用類型,但不是萬能引用。
2.ParamType是一個萬能引用。
3.ParamType既不是指針也不是引用。
(注意,ParamType是形參,而不是指的實(shí)參)
??這里提到的“萬能引用”書中沒有立即細(xì)致說明,這里我簡單介紹一下:
??所謂“萬能引用”,就是既不是左值引用也不是右值引用的引用,并且它最終既可以做左值引用也可以做右值引用。形如:T&&,形狀看起來和右值引用沒啥區(qū)別,但有一個簡單的區(qū)分:長這個樣子的,只要涉及型別推導(dǎo),那么它就是一個萬能引用,否則就是右值引用,舉例:
void f(Widget&& param);//param是右值引用,因?yàn)樗念愋鸵呀?jīng)確定,不涉及型別推導(dǎo)
Widget&& var1 = Widget();//同上
auto&& var2 = var1;//萬能引用
template<typename T>
void f(std::vector<T>&& param);//右值引用
template<typename T>
void f(T&& param);//萬能引用
??萬能引用的作用就是當(dāng)你傳入左值,那么它最終就是左值引用,傳入右值,那么它就是右值引用。
??接著第一種情況說起,如果ParamType(形參)是一個指針或者引用(非萬能引用),那么模板型別推導(dǎo)的最終結(jié)果會忽略掉實(shí)參的引用部分(如果有)。當(dāng)param是一個引用,如:
template<typename T>
void f(T& param);
int x = 10;
const int cx = x;
const int& rx = x;
f(x);//param為int&
f(cx);//param為cosnt int&
f(rx);//param為cosnt int&(實(shí)參的&被忽略了)
//以上的形參是左值引用,當(dāng)改成右值引用時,情況一樣
??如果把param加一個const修飾: const T& 如:
template<typename T>
void f(const T& param);//加了const
int x = 10;//同上
const int cx = x;//同上
const int& rx = x;//同上
f(x);//param為const int&
f(cx);//param為cosnt int&
f(rx);//param為cosnt int&(實(shí)參的&被忽略了) 同上
//以上的形參是左值引用,當(dāng)改成右值引用時,情況一樣
??結(jié)果平淡無奇,而當(dāng)param是一個指針時,和引用的推導(dǎo)是一模一樣的:
template<typename T>
void f(T* param);//param是一個指針了
int x = 10;
const int cx = x;
const int* px = &x;
f(x);//param為int*
f(px);//param為cosnt int*
??沒有什么特別的地方……
??第二種情況,當(dāng)ParamType是萬能引用T&&
template<typename T>
void f(T&& param);//param現(xiàn)在是個萬能引用
int x = 10; //同前
const int cx = x;//同前
const int& rx = x;//同前
f(x);//因?yàn)閤是左值,所以param也為左值,int&
f(cx);//因?yàn)閤是左值,所以param也為左值,cosnt int&
f(rx);/因?yàn)閤是左值,所以param也為左值 ,cosnt int&(實(shí)參的&被忽略了)
f(10);//因?yàn)?0是右值,所以param也為右值,int&&
??第三種情況,當(dāng)ParamType既不是引用也不是指針時(pass by value):
??首先,模板型別推導(dǎo)會像之前的情況一樣,忽略掉實(shí)參的引用部分。
??其次,按值傳遞意味著param對象將是實(shí)參的一個副本,由于對副本的修改并不會影響原有對象,所以如果原有對象(實(shí)參)具有const、valatile的修飾,也會一并忽略掉。
template<typename T>
void f(T param);//param是實(shí)參的一個副本
int x = 10; //同前
const int cx = x;//同前
const int& rx = x;//同前
f(x);//param是int
f(cx);//param是int,忽略掉了const
f(rx);//param是int,忽略掉了const和&
??這里有一個復(fù)雜一點(diǎn),但并不會產(chǎn)生違背以上規(guī)則的例子:
??如果實(shí)參是一個指向const的const指針,如:
template<typename T>
void f(T param);//param是實(shí)參的一個副本
int x = 10; //同前
const int* const px = &x;
f(px);//param是const int*
?? px的第二個const 是指 px本身不允許再發(fā)生任何更改,即不能再指向其它任何變量。它的第一個const是指,它指向的這個變量不可以再被修改。由于是按值傳遞,根據(jù)規(guī)則,型別的推導(dǎo)會忽略掉實(shí)參的const屬性,而實(shí)參的類型是一個 const的指向const int的指針,所以px自己的const(右邊的)會被忽略,于是param的類型變成了 const int。
??這里要補(bǔ)充一下另外的特殊情況,那就是當(dāng)實(shí)參類型為數(shù)組和函數(shù)的時候,需要注意:
?? C++中,數(shù)組名可以退化成指針,函數(shù)也一樣,一個函數(shù)類型是可以退化成函數(shù)指針的。如下:
template<typename T>
void f1(T param);
template <typename T>
void f2(T& param);
int arr = {0,1,2,3};
void someFunc(int,double);
f1(arr);//arr會退化成指針,即param的類型為int*
f2(arr);//這里很有意思,param會被推導(dǎo)為 int (&)[4]
//和上面的情況相比好處是param包含了數(shù)組的元素?cái)?shù)量信息,可以獲取到數(shù)組的成員數(shù)。
f1(someFunc);//param類型為函數(shù)指針 void (*)(int,double)
f2(someFunc);param類型為函數(shù)引用 void (&)(int,double)
??到這里,template的型別推導(dǎo)規(guī)則就說完了,下一篇文章將會介紹auto的型別推導(dǎo)規(guī)則。