模板是C++的重要特性枚抵,是C++標準模板庫的基礎。模板可以根據(jù)數(shù)據(jù)類型自動生成代碼,大大減少重復代碼退盯。模板實例化的時候編譯器需要根據(jù)具體變量推導數(shù)據(jù)類型,模板推導出的類型很多時候是顯而易見的泻肯,有些時候卻不太明顯渊迁,本文詳細闡述一下C++模板的類型推導機制。
在C++中聲明一個模板函數(shù)的偽代碼如下:
template<typename T>
void f(ParamType param);
上面的ParamType
與T
不一定相同灶挟,比如:
template<typename T>
void f(const T& param);
此時ParamType
的類型是const T&
琉朽。
調(diào)用模板函數(shù)方式如下:
f(expr);
編譯的時候,編譯器通過表達式expr
推導出兩個類型ParamType
與T
稚铣。
模板的類型推導與ParamType
密切相關(guān)箱叁,根據(jù)ParamType
的類型可以分為三種情形:
- ParamType 既不是指針也不是引用。
- ParamType 是指針或引用惕医,不是通用引用耕漱。
- ParamType 是通用引用。
下面分別討論一下三種情形:
ParamType 既不是指針也不是引用
即ParamType
與T
相同抬伺,此時模板如下:
template<typename T>
void f(T param);
這種情況下參數(shù)param
的類型T
可以理解為值傳遞(param
會復制一份expr
)時的類型螟够。這意味著:
- 如果
expr
是一個引用,忽略引用部分峡钓。 - 如果
expr
帶有const
或volatile
妓笙,忽略const
、volatile
能岩。
舉個例子:
int x = 27;
const int cx = x;
const int& rx = x;
f(x);
f(cx);
f(rx);
上面的三種調(diào)用方式寞宫,T
的類型都是int
。
需要注意下面一種情況:
const char* const ptr = "Fun with pointers";
f(ptr);
ptr
是指向字符串常量的常量指針拉鹃,此時進行值傳遞T
的類型應為const char*
辈赋。因為:
- 開頭的
const
修飾的是指向的對象鲫忍,表示指向的字符串不可變,不可忽略钥屈,否則指向的類型就不對了饲窿。 -
*
右邊的const
修飾的是指針,表示指針本身不可變焕蹄,在值傳遞的情況下指針是被復制一份,該const
沒有意義阀溶。
ParamType 是指針或引用腻脏,不是通用引用
這種情況下,推導步驟如下:
- 如果
expr
是一個引用银锻,忽略引用部分永品。 - 將
expr
類型與ParamType
進行模式匹配,先確定ParamType击纬,再根據(jù)ParamType
推導T
鼎姐。
舉個例子:
template<typename T>
void f(T& param); // 參數(shù)是引用類型
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // x是int類型,ParamType類型是int&更振,所以T是int類型
f(cx); // cx是const int類型炕桨,ParamType類型是const int&,所以T是const int類型
f(rx); // rx是const int&類型肯腕,忽略引用部分献宫,同cx,ParamType類型是const int&实撒,所以T是const int類型
如果把參數(shù)類型改為const T&
姊途,相應的T
的類型也會有一點變化:
template<typename T>
void f(const T& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // x是int類型,ParamType類型是const int&知态,所以T是int類型
f(cx); // cx是const int類型捷兰,ParamType類型是const int&,所以T是int類型
f(rx); // rx是const int&類型负敏,忽略引用部分贡茅,同cx,ParamType類型是const int&其做,所以T是int類型
如果參數(shù)是指針類型友扰,也類似:
template<typename T>
void f(T* param); // 參數(shù)是指針類型
int x = 27;
const int *px = &x;
f(&x); // &x是int*類型,ParamType類型是int*庶柿,所以T是int類型
f(px); // px是const int*類型村怪,ParamType類型是const int*,所以T是const int類型
ParamType 是通用引用
這種情況下浮庐,推導規(guī)則如下:
- 如果
expr
是左值甚负,那么T
和ParamType
都是左值引用柬焕。這條規(guī)則很特殊:首先,這是唯一一種T
被推導為引用類型的情形梭域;其次斑举,ParamType
的聲明形式是右值引用的語法,但是實際類型為左值引用病涨。 - 如果
expr
是右值富玷,推導規(guī)則與普通引用一致。
舉例:
template<typename T>
void f(T&& param); // 參數(shù)是通用引用
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // x 是左值既穆,所以T和ParamType都是int&類型
f(cx); // cx 是左值赎懦,所以T和ParamType都是const int&類型
f(rx); // rx 是左值,所以T和ParamType都是const int&類型
f(27); // 27 是右值幻工,ParamType是int&&類型励两,所以T是int類型
以上三種情形就是C++模板類型推導的全部規(guī)則了。下面簡單補充說明一下C++里兩種特殊的參數(shù)類型:數(shù)組參數(shù)和函數(shù)參數(shù)囊颅。
數(shù)組參數(shù)
在C語言和C++里当悔,如下的兩個函數(shù)聲明是完全等價的:
void myFunc(int param[]);
void myFunc(int* param);
即函數(shù)的數(shù)組參數(shù)會被當成指針參數(shù)。
如果要使用真正的數(shù)組參數(shù)踢代,在C++中可以使用數(shù)組引用類型:
void myFunc(int (¶m)[5]); // 使用數(shù)組引用時必須指定數(shù)組大小盲憎,同定義數(shù)組一樣
int (¶m)[5]
表示一個大小為5的整數(shù)數(shù)組引用類型。
了解數(shù)組參數(shù)的以上特點后胳挎,可以很容易的理解模板如何推導數(shù)組類型的參數(shù)焙畔。舉兩個例子:
template<typename T>
void f(T param);
const char name[] = "J. P. Briggs";
f(name); // name是數(shù)組,param是值傳遞, 數(shù)組參數(shù)當成指針參數(shù)串远,因此param的類型是const char*
template<typename T>
void f(T& param);
const char name[] = "J. P. Briggs";
f(name); // name是數(shù)組宏多,param是引用類型,因此param的類型是const char(&)[13]
函數(shù)參數(shù)
除了數(shù)組參數(shù)澡罚,函數(shù)參數(shù)也會被當成函數(shù)指針伸但。函數(shù)參數(shù)的類型推導規(guī)則跟數(shù)組參數(shù)完全一樣。
void someFunc(int, double);
template<typename T>
void f1(T param);
template<typename T>
void f2(T& param);
f1(someFunc); // param是值傳遞, 函數(shù)參數(shù)當成函數(shù)指針留搔,因此param的類型是void (*)(int, double)
f2(someFunc); // param是引用類型更胖,因此param的類型是void (&)(int, double)
實際使用中函數(shù)指針和函數(shù)引用基本沒有區(qū)別。兩者調(diào)用時都可以解引用或直接調(diào)用隔显,唯一的區(qū)別是引用初始化時只能用函數(shù)名稱却妨,不能在前面加&。
void (*pf)(int) = someFunc; // 也可以寫成 void (*pf)(int) = &someFunc;
void (&rf)(int) = someFunc;
pf(8, 1.2); // 也可以寫成 (*pf)(8, 1.2);
rf(8, 1.2); // 也可以寫成 (*rf)(8, 1.2);
總結(jié):
- ParamType 既不是指針也不是引用時括眠,采用值傳遞模式彪标,忽略表達式的引用部分、const掷豺、volatile捞烟。
- ParamType 是指針或引用薄声,不是通用引用時,忽略引用部分题画,進行模式匹配默辨,先確定ParamType,再推導T苍息。
- ParamType 是通用引用時缩幸,左值特殊對待(T和ParamType都是左值引用)。
- 數(shù)組參數(shù)竞思、函數(shù)參數(shù)非引用傳遞時當作指針表谊,引用類型不會。