型别推导
函数模板大致形如:
template<typename T>
void f(ParamType param);
f(expr);
什么是型别推导?就是推导模板中,在函数调用之类中T到底是啥。
template<typename T>
void f(const T& param);
int x = 0;
f(x); // T 推导为int, ParamType推导为const int&
以下分三种情况讨论
1. ParamType是一个指针或者引用,但不是万能引用。
什么是万能引用呢? &&
推导步骤:
1.1 如果expr具有引用型别,先将引用部分忽略。
怎么理解这句话呢? 本来我们是这样定义模板的。
template <typename T>
void f(T& param);
expr就是
int x = 27;
const int cx = x;
const int& rx = x;
上面这些就叫做expr, 忽略引用就是,在推导的时候 const int& rx = x;
不将引用带入推导。
1.2 而后,对expr和ParamType执行模式匹配,然后决定T的型别。
f(x); // param const int , T const int
f(cx); // param 型别 const int&, 那么 T就是const int
f(rx); // param 型别 const int&, 那么 T就是const int
1.3 结论
当向引用型别传入const对象时,T&传入的const对象是安全的。
换句人话说,就是如果传入的expr有const的也有非const的,又要保证const对象的const性,只需要
template <typename T>
void f(T& param);
没必要写成
template <typename T>
void f(const T& param);
那么具体的 const T&
template <typename T>
void f(const T& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // param const int , T const int
f(cx); // param 型别 const int&, 那么 T就是int
f(rx); // param 型别 const int&, 那么 T就是int
当然我们实际操作的使用的是ParamType 所以如无必要T&
即可。
那么当param是指针的时候
template <typename T>
void f(T* param);
int x = 27;
const int *px = &x; // px const int * , px可变 px指向的内容不能变。
f(&x); // T 的型别 int, param 是int*
f(px); // T const int param const int*
实话说使用的时候我们关心param型别就好了。
2. ParamType是万能引用
- 这种情况,只需要expr, T和ParamType都会被推导成,左值引用。
- 如果expr是右值,按照情况1来区分。
template <typename T>
void f(T&& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // param const int& , T const int&
f(cx); // param 型别 const int&, 那么 T就是const int&
f(rx); // param 型别 const int&, 那么 T就是const int&
f(27); // param int&&, T int
关键点区分param是左值还是右值。
3.ParamType不是指针也不是引用
这种情况比较简单,因为这种情况都是值传递。
template <typename T>
void f(T param); // param值传递,无论传什么,param都是实参的副本,全新对象。
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // param int , T int
f(cx); // param 型别 int, 那么 T就是int
f(rx); // param 型别 int, 那么 T就是int
注意不管实参是否带const,都不能决定cx,rx能否改变。
注意这种情况下带指针的情况
template <typename T>
void f(T param); // param值传递
cosnt char* const ptr = "hello";
f(ptr); // 传递型别为const char* const ,param 推导为const char*
比如我这样写是没毛病的
#include <iostream>
template <typename T>
void f(T param) {
const char * ch = "world";
param = ch;
}
int main() {
const char* const ch_ptr = "hello";
f(ch_ptr);
return 0;
}
这说明当ParamType为非指针非引用的时候,传指针的时候需要注意,比如实参为const char* const类型,只会保证
不会通过该指针改变指针指向的内容,但是还是会改变指针本身的值。
4.数组到指针的退化
这种情况很简单,平时我们也会把指针和数组混用,比如 ptr[0] = ??? or *ptr = ???
这里需要注意的就是void f(T param)
和void f(T& param)
对数组实参的推导
4.1. 当使用T时,数组实参推导结果是指针。
4.2. 当使用T&时,推导结果是带实际大小的数组的引用。
比如
template <typename T>
void f(T& param){}
const char ch[] = "Hello World!";
f(ch); // ParamType 型别 const char (&)[13];
于是我们可以得到这样一个应用,获取数组大小
#include <iostream>
template <typename T, std::size_t N>
constexpr std::size_t GetArraySize(T (&)[N]) noexcept{
return N;
}
int main() {
const char ch[] = "Hello World!";
std::cout << GetArraySize(ch) << std::endl;
return 0;
}
运行结果
于是我们可以通过一个数组都构建另一个大小相同的数组
int keyVarl[] = {1, 2, 3};
int otherKeyVarl[GetArraySize(keyVarl)];
5. 函数实参的退化
这里函数实参退化和数组实参退化一样,也是分两种情况。一种是非引用非指针型别,这时候函数实参会退化成函数指针;
另一种是引用型别,这种就是函数引用的具体型别。
void someFunc(int, double);
template <typename T>
void f1(T param); //在f1中按照值传递
f1(someFunc); // param被推导为函数指针,具体型别为void (*)(int, double)
template <typename T>
void f2(T& param); //在f1中按照引用传递
f2(someFunc); // param被推导为函数引用,具体型别为void (&)(int, double)