第1課 理解模板類型推導
一、函數模板
(一) 函數模板及調用形式
template<typename T> void f(ParamType param); //注意這里是ParamType而不是T。這兩者可能不一樣! f(expr); //調用形式,以實參expr調用f
(二)討論:
①T和ParamType的類型往往不一樣。因為ParamType常包含一些修飾詞,如const或引用符號等限定詞。
②T的類型,不僅僅依賴於實參expr的類型,還依賴於ParamType的類型。
③ParamType的形式可分為三種情況:A. ParamType是個指針或引用類型(非萬能引用)。B. ParamType是一個萬能引用。C. ParamType既非指針也非引用。
二、函數模板的推導規則
(一)規則1:ParamType是個指針或引用(但非萬能引用),即T*或T&等。
1、兩條規則:
①若expr是個引用類型,先將其引用忽略
②然后對expr的類型和ParamType的類型進行模式匹配,來決定T的類型。
2、注意事項:
①ParamType是個引用類型。由於引用的特點,即形參代表實參本身,所以實參expr的CV屬性會被T保留下來。(注意,如果傳進來的實參是個指針,則param會將實參(指針)的頂層和底層const的都保留下來)。
②當ParamType為T*指針,傳參時param指針是實參的副本,因此實參和形參是兩個不同的指針。T在推導中,會保留實參中的底層const,而舍棄頂層const。因為底層const修飾的是指針所向對象,表示該對象不可更改,所以const應保留,而頂層const表示指針本身,從實參到形參傳遞時復制的是指針,因此形參的指針是個副本,無須保留const屬性。(如,const char* const ptr中,頂層const指修飾ptr的const,即*號右側const,而底層const指*號左側的const)
(二)規則2:ParamType為萬能引用,即T&&
1、兩條規則:
①如果實參expr是個左值,T和ParamType會被推導為左值引用。(注意,這是模板類型推導中,T唯一被推導為引用(T&)的情形)
②如果要實參expr是個右值,則應用“規則1”來推導。 此時的T被推導為T。
2、注意事項:
①當實參為左值時T被推導為T&(不是T類型),表示形參是實參的引用,即代表實參本身。因此指針的頂層和底層const屬性均會被保留。
②形如const T&&或vector<T>&&)均屬於右值引用,因為const的引用會剝奪引用成為萬能引用的資格,因為由其定義的變量再也不能成為非const類型,所以不是“萬能”的類型,而后者己經確定是個vector<T>類型,不可能成為“萬能”的類型,如不會再是int型,因此也不是萬能引用。(詳見《萬能引用》一節)
③在推導過程中會發生引用折疊(詳見《引用折疊》一節)。
(三)規則3:ParamType是個非指針也非引用類型(即按值傳遞)
1、推導規則:
①若實參是個引用類型,則忽略其引用部分,同時cv屬性被忽略。(因為按值傳遞,采用復制手段,形參是個副本,與實參是兩個不同的參數)。
②如果實參是指針類型,從實參到形參傳遞時,傳用的是按比特位復制,因此形參也是個副本,只保留指針的底層const,而舍棄頂層const)
③其他情況也是按值傳遞,形參同樣也是一個副本。
【編程實驗】函數模板的推導規則
#include <iostream> #include <boost/type_index.hpp> using namespace std; using boost::typeindex::type_id_with_cvr; //輔助類模板,用於打印T和param的類型 template <typename T, typename Param> void printType() { cout << "T = " << type_id_with_cvr<T>().pretty_name() << ", "; cout << "param = " << type_id_with_cvr<Param>().pretty_name() << endl; } /***************************規則1:當ParamType引用或指針時*****************************/ //(1)當ParamType為T&時 template <typename T> void func_r(T& param) //由於T是個引用,操作的是變量/對象本身,當如果實參帶有const或volatile屬性,會被T保留下來 { //。如果實參是個指針,param代表實參本身,會將頂層和底層const全部保存一下。 printType<T, decltype(param)>(); } //(2)當ParamType為const T&時 template <typename T> void func_cr(const T& param) //由於假定param是個const引用,為避免重復,T的推導結果就沒必要包含const了。 { printType<T, decltype(param)>(); } //(3)當ParamType為右值引用時 template <typename T> void func_rr(const T&& param) //注意由於T&&被const修飾,為右值引用(而非萬能引用) { printType<T, decltype(param)>(); } //(4)當ParamType為T*指針時(注意const char* const ptr) //頂層const:指ptr指針變量本身的const屬性(T推導時被舍棄) //底層const:指ptr指針所指向對象/變量的const屬性(被T保留) template <typename T> void func_p(T* param) //當param是個指針時,如果實參帶const,則表示所指對象具有const屬性,因此其底層const會被T保留 { printType<T,decltype(param)>(); } //(3)當ParamType為const T*指針時 template <typename T> void func_cp(const T* param) //如果實參帶有const,為避免重復,T的推導就沒有必要包含const { printType<T, decltype(param)>(); } /***************************規則2:當ParamType為萬能引用時,即T&& *****************************/ template<typename T> void func_ur(T&& param) { printType<T, decltype(std::forward<T>(param))>(); } template<typename T> void func_cr2(const T&& param) //注意這是右值引用 { printType<T, decltype(param)>(); } /***************************規則3:當ParamType為非指針和引用類型時 *****************************/ template<typename T> void func_v(T param) { printType<T, decltype(param)>(); } int main() { int x = 27; const int cx = x; const int& rx = x; const int* const ptr = &x; cout << "****************************規則1*****************************" << endl; //ParamType為引用: void func_r(T& param) func_r(x); //T = int, param = int& func_r(cx); //T = const int, param = const int& func_r(rx); //T = const int, param = const int& func_r(ptr); //T = const int*, param = const int* const; //這里表示實參(ptr)的引用, 即代表實參本身,因此ptr的頂、底層const均保留
//Param為const T&: void func_cr(const T& param) func_cr(x); //T = int, param = const int& func_cr(cx); //T = int, param = const int& func_cr(rx); //T = int, param = const int& //Param為指針: void func_p(T* param) func_p(&x); //T = int, param = int* func_p(&cx); //T = const int, param = const int* func_p(ptr); //T = const int, param = const int* //只保留底層const。(注意形參param是實參的副本,即將ptr按比特位復制給param, //因為是副本,所以只保留底層const) //Param為const T* : void func_cp(const T* param) func_cp(&x); //T = int, param = const int* func_cp(&cx); //T = int, param = const int* func_cp(ptr); //T = int, param = const int* //只保留底層const //ParamType為右值引用: void func_rr(T&& param) func_rr(10); //T = int, param = int&& func_rr(std::move(cx)); //T = int, param = const int&& func_rr(std::move(rx)); //T = int, param = const int&& cout << "****************************規則2*****************************" << endl; //void func_ur(T&& param); 注意,當實參為左值時,T一定是一個引用類型(左值引用),這是T被推導為引用的唯一情形 func_ur(x); // T = int&, param = int& func_ur(cx); // T = const int&, param = const int& //T為引用,會保留cv屬性 func_ur(rx); // T = const int&, param = const int& func_ur(ptr); // T = const int* const&, param = const int* const& //頂層和底層const均保留 func_ur(10); // T = int, param = int&& 實參為右值,T被推導為int,注意不是int&& func_ur((int&&)10); // T = int, param = int&& 實參為右值,T被推導為int,注意不是int&& //func_cr2(x); // 編譯不通過,因為const T&&代表右值引用(而非萬能引用),必須傳入右值。 func_cr2(10); // T = int, param = const int&& 實參為右值,T被推導為int cout << "****************************規則3*****************************" << endl;
//按值形參: void func_v(T param);
func_v(ptr); //T = const int*, param = const int* (注意只保留底層const,即實參本身的const舍棄)
return 0;
}
三、數組和函數實參的推導規則
(一)推導規則:
(1)當函數模板為按值形參時(如T param):數組和函數類型均退化成指針類型。(如char*、void(*)(int, double)。
//由於數組到指針的退化規則,以下兩個函數等價的,所以不能同時聲明這兩個同名函數。
void myFunc(int param[]);
void myFunc(int* param);
(2)當函數模板為引用類型形參時(如T& param):則數組和函數分別被推導為數組引用和函數引用類型。特別值得注意,數組引用類型會包含數組元素類型及大小信息,如const char(&)[13]。而函數引用類型如void(&)(int, double)
(二)數組引用的妙用:用於推導數組元素個數
【編程實驗】數組實參與函數實參的推導
#include <iostream> #include <boost/type_index.hpp> using namespace std; using boost::typeindex::type_id_with_cvr; //輔助類模板,用於打印T和param的類型 template <typename T, typename Param> void printType() { cout << "T = " << type_id_with_cvr<T>().pretty_name() << ", "; cout << "param = " << type_id_with_cvr<Param>().pretty_name() << endl; } //數組和函數實參的推導 template<typename T> void func_v(T param) //按值形參 { printType<T, decltype(param)>(); } template<typename T> void func_r(T& param) //按引用形參 { printType<T, decltype(param)>(); } void test(int x, double y) { } //數組引用的妙用:獲得數組元素的個數 template<typename T, std::size_t N> constexpr std::size_t arraySize(T (&)[N]) noexcept //注意形參是個數組引用 { return N; } int main() { const char name[] = "SantaClaus"; //name的類型為const char[11];(含\0) const char* pName = name; //數組退化為指針 /********************************數組和函數實參的推導****************************************/ func_v(name); //T = const char*, param = const char*(數組退化為指針,因此T是指針類型) func_r(name); //T = const char[11], param = const char(&)[11] func_v(test); //T = void(*)(int, double), param = void(*)(int, double) (函數名退化為指針,因此T是指針類型) func_r(test); //T = void(int, double), param = void(&)(int, double) (注意T的類型) /***********************************數組引用的妙用******************************************/ int keyValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int mapVals[arraySize(keyValues)]; //arraySize返回值是編譯期常量,可用於定義數組大小 cout << arraySize(keyValues) << endl; //9 cout << arraySize(mapVals) << endl; //9 cout << arraySize(name) << endl; //11 }
