引子
老爷子上来第一大章(Item 1-4)就讨论了类型推导,依次介绍了:
- 模版类型推导
- auto类型推导
- decltype关键字
- 获取变量类型的正确姿势
当然了,从最简单的auto关键字入手,确实是方便老c++用户熟悉c++11/14的快速方法。
本文就先介绍模板类型推倒吧。
正文
先看一段代码
template<typename T>
void f(ParamType param);
这是一个最简单的c++模板代码,根据ParamType
的不同,我们可以将其分为如下几类
- non-universal 引用
T&
- universal引用
T&&
- 非引用 :
T
和T*
此时为传值,包括传指针(值)
1. non-universal引用 T&
这时根据传入的类型,我们推导规则如下:
- 如果传入的类型是引用类型,那么会忽视掉引用部分,如果是指针类型,则会忽视指针部分
- 如果传入的类型不是引用类型那么原来是什么类型就推导成什么类型
此时上面的代码变成
template<typename T>
void f(T& param);
那么对于下面3个变量声明
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
的时候有
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&
显然这些类型推导是自然且符合我们预期的。
2. universal引用 T&&
虽然Item 1中尚未准确定义universal引用,但是为了方便读者,我们的提前剧透一下Item 24中的定义:
形如T&&的类型有两种情况:一种是普通的rvalue reference,另一种则被称为 universal reference,它既可能表示lvalue reference,也可能表示rvalue reference。区分他们的方式为: 如果一个函数模板参数的类型为T&&,其中T是需要推导的类型,那么此处的T&&指代universal reference。若不严格满足T&&类型或没有发生类型推导,则T&&指代rvalue reference。
详细的内容留在Item 24里面解释,这里不再赘述(等不及的读者也可以参考这篇文章)。
在这种情况下,我们适用如下规则:
- 如果传入的参数是lvalue,那么就推导成引用类型
- 如果是rvalue,则用上面non-universal的规则
下面回到最开始的代码,我们有
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&&
3. 非引用 T
和T*
此时就是最基础的传值(pass-by-value),注意到传指针本质上也是在传指针的值,所以我们将指针也归到这一类。此时我们适用如下规则:
- 如果传入参数是引用,忽略引用属性
- 如果传入参数不是引用类型,忽略
const
和volatile
修饰符
则我们的例子变成
template<typename T>
void f(T param); // param is now passed by value
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
之所以去掉cv修饰符,本质上是由于传值是对原来的对象进行了一次copy,自然const
和volatile
这种属性就丢失了。
为了加深理解,我们考虑一个更复杂的例子:
template<typename T>
void f(T param); // param is still passed by value
const char* const ptr = // ptr is const pointer to const object
"Fun with pointers";
f(ptr); // so what is the type of T?
答案是const char*
,这是由于我们是传值。故ptr
会被拷贝一遍,这样失去了它本身的不变性(即星号右边的 const
),但是它本身修饰的字符串的不变性仍然被保留。
最后我们在考虑两个特殊情况,即传入参数是数组和函数指针的情况。
4.传入参数为数组或者函数指针
由于历史原因,C++中数组类型会退化成指针类型。于是我们有如下的例子:
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]
const char * ptrToName = name; // array decays to pointer
f(name); // name is array, but T deduced as const char*
因此在参数是传值时,数组会被推导成指针。
但是当参数是传引用时,就发生了变化:
template<typename T>
void f(T& param); // template with by-reference parameter
f(name); // pass array to f, so T is const char [13] and param's type is const char(&)[13]
除了数组,函数指针也会有类似的表现:
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)
总结
最后我们贴一下S.M.老爷子在本条目末尾的tips:
1.在模板类型推导中,如果传的参数是引用类型,则被当作非引用类型,即引用本身会被忽略;
2.当推导类型为universal引用时,左值参数会被特殊对待(被推导成引用);
3.当参数是传值时,cv修饰符会被忽略掉。
4.数组和函数作为参数时,会退化成指针,除非它本身作为引用。