1. 程式人生 > >c++11-17 模板核心知識(五)—— 理解模板引數推導規則

c++11-17 模板核心知識(五)—— 理解模板引數推導規則

- [Case 1 : ParamType是一個指標或者引用,但不是universal reference](#case-1--paramtype是一個指標或者引用但不是universal-reference) - [T&](#t) - [const T&](#const-t) - [T*](#t-1) - [Case 2 : ParamType是Universal Reference](#case-2--paramtype是universal-reference) - [注意區別Universal Reference與右值引用](#注意區別universal-reference與右值引用) - [Case 3 : ParamType既不是指標也不是引用](#case-3--paramtype既不是指標也不是引用) - [陣列作為引數](#陣列作為引數) - [ParamType按值傳遞](#paramtype按值傳遞) - [ParamType為引用型別](#paramtype為引用型別) - [函式作為引數](#函式作為引數) 首先我們定義一下本文通用的模板定義與呼叫: ```c++ template void f(ParamType param); ...... f(expr); // call f with some expression ``` 在編譯階段使用`expr`來推斷`ParamType`和`T`這兩個型別。這兩個型別通常不同,因為`ParamType`會有`const`和引用等修飾。例如: ```c++ template void f(const T& param); // ParamType is const T& ``` ```c++ int x = 0; f(x); // call f with an int ``` 這裡,T被推斷成`int`,但是`ParamType`的型別是`const T&`。 直覺下`T`的型別應該和`expr`的一樣,比如上面的例子中,`expr`和`T`的型別都是`int`。但是會有一些例外情況:`T`的型別不僅依賴`expr`,還依賴`ParamType`。總共分為三大類: * `ParamType`是一個指標或者引用,但不是`universal reference`(或者叫`forwarding references`). * `ParamType`是一個`universal reference`。 * `ParamType`既不是指標也不是引用。 ## Case 1 : ParamType是一個指標或者引用,但不是universal reference * 如果`expr`是一個引用,忽略其引用部分。 * 比較`expr`與`ParamType`的型別來決定`T`的型別。 ### T& ```c++ template void f(T& param); // param is a reference ...... int x = 27; // x is an int const int cx = x; // cx is a const int const int& rx = x; // rx is a reference to x as a const int // call f f(x); // T is int, param's type is int& f(cx); // T is const int, param's type is const int& f(rx); // T is const int, param's type is const int& ``` 上面例子是左值引用,但是這點對右值引用也適用。 **注意第三點,`const`修飾符依舊保留。** 這和普通函式的類似呼叫有區別: ```c++ void f(int &x){ } ... const int x = 10; f(x); // error ``` ### const T& 如果給`ParamType`加上`const`,情況也沒有太大變化: ```c++ template void f(const T& param); // param is now a ref-to-const ...... int x = 27; // as before const int cx = x; // as before const int& rx = x; // as before ...... f(x); // T is int, param's type is const int& f(cx); // T is int, param's type is const int& f(rx); // T is int, param's type is const int& ``` ### T* 改為指標也一樣: ```c++ template void f(T* param); // param is now a pointer ...... int x = 27; const int *px = &x; f(&x); // T is int, param's type is int* f(px); // T is const int, param's type is const int* ``` ## Case 2 : ParamType是Universal Reference * 如果`expr`是左值,那麼`T`和`ParamType`會被推斷為左值引用。 * 如果`expr`是右值,那麼就是Case 1的情況。 ```c++ template void f(T&& param); // param is now a universal reference ...... int x = 27; const int cx = x; const int& rx = x; ``` 呼叫: ```c++ f(x); // x is lvalue, so T is int&, param's type is also int& f(cx); // cx is lvalue, so T is const int&, param's type is also const int& f(rx); // rx is lvalue, so T is const int&, param's type is also const int& f(27); // 27 is rvalue, so T is int, param's type is therefore int&& ``` 如果之前瞭解過完美轉發和摺疊引用的概念,結合Case1,這一個規則還是比較好理解的。 ### 注意區別Universal Reference與右值引用 這兩點需要區分清楚,比如: ```c++ template void f(T&& param); // universal reference template void f(std::vector&& param); // rvalue reference ``` 有一個通用規則 : `universal reference`會有型別推斷的過程。具體在後面的單獨文章會講,跟這篇文章的主題關係不大,這裡稍微提一下 : ) ## Case 3 : ParamType既不是指標也不是引用 這種情況就是pass-by-value的情況: ```c++ template void f(T param); // param is now passed by value ``` 這意味著,param是一個被拷貝的全新物件,也就是param決定著T的型別: * 如果`expr`是引用型別,忽略。 * 如果`expr`帶有const、volatile,忽略。 ```c++ int x = 27; const int cx = x; const int& rx = x; f(x); // T's and param's types are both int f(cx); // T's and param's types are again both int f(rx); // T's and param's types are still both int ``` 忽略const和volatile也比較好理解:引數是值拷貝,所以形參和實參其實是互相獨立的。正如下面程式碼可以將`const int`傳遞給`int`,但是宣告為引用則不行: ```c++ void f(int x){ } int main() { const int x = 10; f(x); } ``` 注意忽略的const是針對引數本身的,而不針對指標指向的const物件: ```c++ template void f(T param); ...... const char* const ptr = "Fun with pointers"; // ptr is const pointer to const object f(ptr); // pass arg of type const char * const ``` 這個按照值傳遞的是ptr,所以ptr的const會被忽略,但是ptr指向的物件依然是const。 ## 陣列作為引數 陣列型別和指標型別是兩種型別,但是有時候他們是可以互換的,比如在下面這種情況下,陣列會decay成指標: ```c++ const char name[] = "J. P. Briggs"; // name's type is const char[13] const char * ptrToName = name; // array decays to pointer ``` 在普通函式中,函式形參為陣列型別和指標型別是等價的: ```c++ void myFunc(int param[]); void myFunc1(int* param); // same function as above ``` 但是陣列作為模板引數是比較特殊的一種情況。 ### ParamType按值傳遞 ```c++ template void f(T param); // template with by-value parameter ...... const char name[] = "J. P. Briggs"; // name's type is const char[13] f(name); // name is array, but T deduced as const char* ``` 這種情況下,`T`被推斷為指標型別`const char*`. ### ParamType為引用型別 ```c++ template void f(T& param); ...... const char name[] = "J. P. Briggs"; // name's type is const char[13] f(name); // pass array to f ``` 現在`T`被推斷為陣列型別`const char [13]`,`ParamType`為`const char (&)[13]`,這種情況是很特殊的,要與`ParamType`按值傳遞區分開。 我們可以利用上面這種特性定義一個模板來推斷陣列的大小,這種用法還蠻常見的: ```c++ template constexpr std::size_t arraySize(T (&)[N]) noexcept { return N; } ...... int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; std::array mappedVals; ``` ![image](https://user-images.githubusercontent.com/14103319/99179355-d559c400-2757-11eb-926b-7ebef4719d8b.png) ## 函式作為引數 上面討論的關於陣列的情況同樣適用於函式作為引數,函式型別同樣也可以`decay`成函式指標: ```c++ void someFunc(int, double); // someFunc is a function;type is void(int, double) template void f1(T param); // in f1, param passed by value template void f2(T ¶m); // in f2, param passed by ref f1(someFunc); // param deduced as ptr-to-func; type is void (*)(int, double) f2(someFunc); // param deduced as ref-to-func; type is void (&)(int, double) ``` 不過這在平時應用中也沒有太大差別。 (完) 朋友們可以關注下我的公眾號,獲得最及時的更新: ![](https://img2020.cnblogs.com/blog/1298604/202011/1298604-20201103132150036-885052