首先咱们定义一下本文通用的模板定义与调用:c++
template<typename T> void f(ParamType param); ...... f(expr); // call f with some expression
在编译阶段使用expr
来推断ParamType
和T
这两个类型。这两个类型一般不一样,由于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
的同样,好比上面的例子中,expr
和T
的类型都是int
。可是会有一些例外状况:T
的类型不只依赖expr
,还依赖ParamType
。总共分为三大类:app
ParamType
是一个指针或者引用,但不是universal reference
(或者叫forwarding references
).ParamType
是一个universal reference
。ParamType
既不是指针也不是引用。expr
是一个引用,忽略其引用部分。expr
与ParamType
的类型来决定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
若是给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&
改成指针也同样: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*
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,这一个规则仍是比较好理解的。it
这两点须要区分清楚,好比:io
template<typename T> void f(T&& param); // universal reference template<typename T> void f(std::vector<T>&& param); // rvalue reference
有一个通用规则 : universal reference
会有类型推断的过程。具体在后面的单独文章会讲,跟这篇文章的主题关系不大,这里稍微提一下 : )
这种状况就是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
可是数组做为模板参数是比较特殊的一种状况。
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*
.
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)
不过这在平时应用中也没有太大差异。
(完)
朋友们能够关注下个人公众号,得到最及时的更新: