- Case 1 : ParamType是一個指標或者引用,但不是universal reference
- Case 2 : ParamType是Universal Reference
- Case 3 : ParamType既不是指標也不是引用
- 陣列作為引數
- 函式作為引數
首先我們定義一下本文通用的模板定義與呼叫:
template<typename T>
void f(ParamType param);
......
f(expr); // call f with some expression
在編譯階段使用expr
來推斷ParamType
和T
這兩個型別。這兩個型別通常不同,因為ParamType
會有const
和引用等修飾。例如:
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
的一樣,比如上面的例子中,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&
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*
改為指標也一樣:
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
是左值,那麼T
和ParamType
會被推斷為左值引用。 - 如果
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,這一個規則還是比較好理解的。
注意區別Universal Reference與右值引用
這兩點需要區分清楚,比如:
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]
,ParamType
為const 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;
函式作為引數
上面討論的關於陣列的情況同樣適用於函式作為引數,函式型別同樣也可以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 ¶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)
不過這在平時應用中也沒有太大差別。
(完)
朋友們可以關注下我的公眾號,獲得最及時的更新: