C++模板類型推導

模板是C++的重要特性,是C++標準模板庫的基礎。模板可以根據數據類型自動生成代碼,大大減少重復代碼。模板實例化的時候編譯器需要根據具體變量推導數據類型,模板推導出的類型很多時候是顯而易見的,有些時候卻不太明顯,本文詳細闡述一下C++模板的類型推導機制。

在C++中聲明一個模板函數的偽代碼如下:

template<typename T>
void f(ParamType param);

上面的ParamTypeT不一定相同,比如:

template<typename T>
void f(const T& param);

此時ParamType的類型是const T&

調用模板函數方式如下:

f(expr);

編譯的時候,編譯器通過表達式expr推導出兩個類型ParamTypeT

模板的類型推導與ParamType密切相關,根據ParamType的類型可以分為三種情形:

  1. ParamType 既不是指針也不是引用。
  2. ParamType 是指針或引用,不是通用引用。
  3. ParamType 是通用引用。

下面分別討論一下三種情形:

ParamType 既不是指針也不是引用

ParamTypeT相同,此時模板如下:

template<typename T>
void f(T param);

這種情況下參數param的類型T可以理解為值傳遞param會復制一份expr)時的類型。這意味著:

  • 如果expr是一個引用,忽略引用部分。
  • 如果expr帶有constvolatile,忽略constvolatile

舉個例子:

int x = 27;
const int cx = x;
const int& rx = x;
f(x);
f(cx);
f(rx);

上面的三種調用方式,T的類型都是int

需要注意下面一種情況:

const char* const ptr = "Fun with pointers";
f(ptr);

ptr是指向字符串常量的常量指針,此時進行值傳遞T的類型應為const char*。因為:

  1. 開頭的const修飾的是指向的對象,表示指向的字符串不可變,不可忽略,否則指向的類型就不對了。
  2. *右邊的const修飾的是指針,表示指針本身不可變,在值傳遞的情況下指針是被復制一份,該const沒有意義。

ParamType 是指針或引用,不是通用引用

這種情況下,推導步驟如下:

  1. 如果expr是一個引用,忽略引用部分。
  2. expr類型與ParamType進行模式匹配,先確定ParamType,再根據ParamType推導T

舉個例子:

template<typename T>
void f(T& param);  // 參數是引用類型

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類型

如果把參數類型改為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類型

如果參數是指針類型,也類似:

template<typename T>
void f(T* param);  // 參數是指針類型

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 是通用引用

這種情況下,推導規則如下:

  • 如果expr是左值,那么TParamType都是左值引用。這條規則很特殊:首先,這是唯一一種T被推導為引用類型的情形;其次,ParamType的聲明形式是右值引用的語法,但是實際類型為左值引用。
  • 如果expr是右值,推導規則與普通引用一致。

舉例:

template<typename T>
void f(T&& param); // 參數是通用引用

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++模板類型推導的全部規則了。下面簡單補充說明一下C++里兩種特殊的參數類型:數組參數和函數參數。

數組參數

在C語言和C++里,如下的兩個函數聲明是完全等價的:

void myFunc(int param[]);
void myFunc(int* param);

即函數的數組參數會被當成指針參數

如果要使用真正的數組參數,在C++中可以使用數組引用類型:

void myFunc(int (&param)[5]); // 使用數組引用時必須指定數組大小,同定義數組一樣

int (&param)[5]表示一個大小為5的整數數組引用類型。

了解數組參數的以上特點后,可以很容易的理解模板如何推導數組類型的參數。舉兩個例子:

template<typename T>
void f(T param);

const char name[] = "J. P. Briggs";
f(name); // name是數組,param是值傳遞, 數組參數當成指針參數,因此param的類型是const char*
template<typename T>
void f(T& param);

const char name[] = "J. P. Briggs";
f(name); // name是數組,param是引用類型,因此param的類型是const char(&)[13]

函數參數

除了數組參數,函數參數也會被當成函數指針。函數參數的類型推導規則跟數組參數完全一樣。

void someFunc(int, double);

template<typename T>
void f1(T param);

template<typename T>
void f2(T& param);

f1(someFunc); // param是值傳遞, 函數參數當成函數指針,因此param的類型是void (*)(int, double)
f2(someFunc); // param是引用類型,因此param的類型是void (&)(int, double)

實際使用中函數指針和函數引用基本沒有區別。兩者調用時都可以解引用或直接調用,唯一的區別是引用初始化時只能用函數名稱,不能在前面加&。

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);

總結:

  • ParamType 既不是指針也不是引用時,采用值傳遞模式,忽略表達式的引用部分、const、volatile。
  • ParamType 是指針或引用,不是通用引用時,忽略引用部分,進行模式匹配,先確定ParamType,再推導T。
  • ParamType 是通用引用時,左值特殊對待(T和ParamType都是左值引用)。
  • 數組參數、函數參數非引用傳遞時當作指針,引用類型不會。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容