模板类型推导
对于模板函数来说,编译器需要根据实际传入的参数来推导模板类型T。例如,假设我们有下面这个模板函数:
tempalte<typename T>
void f(T& param); // param si 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
那么调用模板函数时,编译器推导出的模板类型分别为:
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、是不是reference,类型推导的结果是不一样的。回到刚开始声明的模板函数,我们声明了参数类型为T&,如果声明为T,或者是T&&,又会得到怎样的结果?我们用表格来总结一下:
x | cx | rx | rvalue | ||
---|---|---|---|---|---|
void f(T& param) | T | int | const int | const int | x |
param | int& | const int& | const int& | x | |
void f(const T& param) | T | int | int | int | int |
param | const int& | const int& | const int& | const int& | |
void f(T&& param) | T | int& | const int& | const int& | int |
param | int& | const int& | const int& | int&& | |
void f(T param) | T | int | int | int | int |
param | int | int | int | int |
- 一般情况下,param的类型是最完整的类型,继承了形参中声明的cr(const和reference)和实参总带过来的cr。但有两个特例:
- 特例一:当形参时通用引用(T&&作为模板参数时称为通用引用)时,param根据具体的实参类型,推导为左值引用或者右值引用;
- 特例二:当形参不是引用时,实参到形参为值传递,去除所有cr修饰符。
- T中是否包含cr修饰符,取决于param的修饰符是否已在形参中声明过。也就是说,T中修饰符不会与形参中已声明的修饰符重复。
为什么我们需要知道这些规则呢?这是因为,有时候需要根据传入的param的类型构造与之相关联的其它类型的对象。比如我们想要在函数内部构造一个与param同类型但去除cr修饰符的对象,那么就应该把形参声明为const T& param
,然后声明T obj
这个对象。这是因为上表中第4行表明了,无论实参包含了什么修饰符,无论是左值还是右值,T都是不带任何修饰符的单纯类型。当然,声明为T param
也是可以的,但这种情况下参数就是值传递了。所以说,根据实际情况选择合适的模板参数类型是很重要的。
最后,为了更容易实践,我们总结出下面三个推荐用法:
- 想要按值传递,将模板函数参数声明为
T param
; - 想要按引用传递,但不考虑右值时,将模板函数参数声明为
const T& param
; - 想要按引用传递,但要区分左值和右值时,将模板函数声明为
T&& param
;