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

首先咱們定義一下本文通用的模板定義與調用:c++

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

......
f(expr); // call f with some expression

在編譯階段使用expr來推斷ParamTypeT這兩個類型。這兩個類型一般不一樣,由於ParamType會有const和引用等修飾。例如:express

template<typename T>
void f(const T& param);      // ParamType is const T&
int x = 0;
f(x);      // call f with an int

這裏,T被推斷成int,可是ParamType的類型是const T&數組

直覺下T的類型應該和expr的同樣,好比上面的例子中,exprT的類型都是int。可是會有一些例外狀況:T的類型不只依賴expr,還依賴ParamType。總共分爲三大類:app

  • ParamType是一個指針或者引用,但不是universal reference(或者叫forwarding references).
  • ParamType是一個universal reference
  • ParamType既不是指針也不是引用。

Case 1 : ParamType是一個指針或者引用,但不是universal reference

  • 若是expr是一個引用,忽略其引用部分。
  • 比較exprParamType的類型來決定T的類型。

T&

template<typename T>
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修飾符依舊保留。 這和普通函數的相似調用有區別:函數

void f(int &x){

}

... 
const int x  = 10;
f(x);       // error

const T&

若是給ParamType加上const,狀況也沒有太大變化:指針

template<typename T>
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*

改成指針也同樣:code

template<typename T>
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是左值,那麼TParamType會被推斷爲左值引用。
  • 若是expr是右值,那麼就是Case 1的狀況。
template<typename T>
void f(T&& param);       // param is now a universal reference

......
int x = 27;                
const int cx = x;    
const int& rx = x;

調用:對象

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,這一個規則仍是比較好理解的。it

注意區別Universal Reference與右值引用

這兩點須要區分清楚,好比:io

template<typename T>
void f(T&& param);           // universal reference


template<typename T>
void f(std::vector<T>&& param);       // rvalue reference

有一個通用規則 : universal reference會有類型推斷的過程。具體在後面的單獨文章會講,跟這篇文章的主題關係不大,這裏稍微提一下 : )

Case 3 : ParamType既不是指針也不是引用

這種狀況就是pass-by-value的狀況:

template<typename T>
void f(T param); // param is now passed by value

這意味着,param是一個被拷貝的全新對象,也就是param決定着T的類型:

  • 若是expr是引用類型,忽略。
  • 若是expr帶有const、volatile,忽略。
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,可是聲明爲引用則不行:

void f(int x){

}

int main() {
  const int x  = 10;

  f(x);       
}

注意忽略的const是針對參數自己的,而不針對指針指向的const對象:

template<typename T>
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成指針:

const char name[] = "J. P. Briggs";     // name's type is const char[13]
const char * ptrToName = name;       // array decays to pointer

在普通函數中,函數形參爲數組類型和指針類型是等價的:

void myFunc(int param[]);
void myFunc1(int* param);         // same function as above

可是數組做爲模板參數是比較特殊的一種狀況。

ParamType按值傳遞

template<typename T>
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爲引用類型

template<typename T>
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]ParamTypeconst char (&)[13],這種狀況是很特殊的,要與ParamType按值傳遞區分開。

咱們能夠利用上面這種特性定義一個模板來推斷數組的大小,這種用法還蠻常見的:

template<typename T, std::size_t N> 
constexpr std::size_t arraySize(T (&)[N]) noexcept  {
    return N;
}

......
int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 };
std::array<int, arraySize(keyVals)> mappedVals;

image

函數做爲參數

上面討論的關於數組的狀況一樣適用於函數做爲參數,函數類型一樣也能夠decay成函數指針:

void someFunc(int, double);        // someFunc is a function;type is void(int, double)
template <typename T> void f1(T param);     // in f1, param passed by value
template <typename T> void f2(T &param);    // 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)

不過這在平時應用中也沒有太大差異。

(完)

朋友們能夠關注下個人公衆號,得到最及時的更新:

相關文章
相關標籤/搜索