理解模板类型推导
现代C++(C++98之后)(暂时主要了解C++11)除去模板的类型推导,增加了两个新特性
auto和decltype,本文将对类型推导做个全方位的剖析,做不到全方位的话,那我也无能为力哈哈哈(接着看书)
以下来自<Effective Modern C++> 感谢大佬!
函数模板
template<typename T>
void f(ParamType param);
//调用
f(expr);
第一个问题,编辑器怎么推导的,让我们来考虑以下情况
std::string str;
int n = str;
这里编辑器会报错,因为string不能隐式转换成int,也就是说,编辑器有能力知道等号右边的类型是什么,从而去推导左边的类型,类型推导大致就是这个原理。
因此
在编译的时候,编辑器会expr 来进行推导出两个类型
- T(模板类型)
- ParamType(参数类型)
这两个在大多数情况下是不相同的,因为ParamType会加上一个限定装饰,类似const或者引用,举个栗子
template<typename T>
void f(const T& param); // ParamType是const T&
以下会用三个例子来分析类型推导
第一种情况: ParamType 是个非通用的引用或者是一个指针
非通用,占个坑,看完再补
非通用(&&)就是不是通用的,而通用则是用涉及到右值引用和完美转发,简单讲,通用引用可以实现输出左值推导成左值,输入右值推导成右值
类型推导的流程:
- 如果 expr 的类型是个引用,忽略引用的部分。
- 然后利用 expr 的类型和 ParamType 对比去判断 T 的类型。
举个栗子
模板
template<typename T>
void f(T& param); // param是一个引用类型
变量声明
int x = 27; // x是一个int
const int cx = x; // cx是一个const int
const int& rx = x; // rx是const int的引用
类型推导结果
f(x); // T是int,param的类型时int&
f(cx); // T是const int, param的类型是const int&
f(rx); // T是const int,param的类型时const int&
结论
- 推导结果会给对象保留常量特性(const保留)
- 引用特性会被类型推导所忽略(&舍去)
第二种情况: ParamType 是个通用的引用(Universal Reference)
通用的引用参数,就需要涉及到右值引用
函数模板使用一个类型参数 T ,一个通用的引用参数的申明类型是 T&&
条款
- 如果 expr 是一个左值, T和ParamType 都会被推导成左值引用 (完美转发)
- 如果 expr 是一个右值,那么就执行“普通”的法则(第一种情况)(也就是只推导类型)
举个栗子
template<typename T>
void f(T&& param); // param现在是一个通用的引用
int x = 27; // 和之前一样
const int cx = x; // 和之前一样
const int& rx = x; // 和之前一样
f(x); // x是左值,所以T是int&,param的类型也是int&
f(cx); // cx是左值,所以T是const int&,param的类型也是const int&
f(rx); // rx是左值,所以T是const int& param的类型也是const int&
f(27); // 27是右值,则T是int,param的类型是int&&
第三种情况: ParamType 既不是指针也不是引用
当ParamType 既不是指针也不是引用,我们把它处理成pass-by-value(值传递)
准则
-
和之前一样,如果 expr 的类型是个引用,将会忽略引用的部分。
-
如果在忽略 expr 的引用特性之后,expr 是个 const的,也要忽略掉const。如果
是volatile ,照样也要忽略掉
这种就比较简单了
数组参数
数组名通常会跟指针扯上关系(一个数组会被退化成一个指向其第一个元素的指针)
const char name[] = "J. P. Briggs"; //name的类型是const char[13]
const char * ptrToName = name; //数组被退化成指针
这时,如果对数组名进行推导的话
template<typename T>
void f(T param); // 模板拥有一个按值传递的参数
f(name); // T和param的类型会被推到成什么呢?
因为数组参数声明会被当做指针参数,传递给模板函数的按值传递的数组参数会被退化成指
针类型,意味着在模板f的调用中,模板参数T被推导成const char*
但是!如果声明是数组的引用的话,结果完全不一样!
template<typename T>
void f(T& param); // 引用参数的模板
f(name); // 传递数组给f
这时,类型推导出的就是数组!!!(什么鬼)同时,类型推导包括了数组的长度
函数参数
函数参数tm也能被推导函数指针
总结
- 在模板类型推导的时候,有引用特性的参数的引用特性会被忽略
- 在推导通用引用参数的时候,左值会被特殊处理
- 在推导按值传递的参数时候,const和/或volatile参数会被视为非const和
非volatile - 在模板类型推导的时候,参数如果是数组或者函数名称,他们会被退化成指针,除非是用
在初始化引用类型
auto 类型推导
除了一个例外, auto 类型推导就是模板类型推导
从前面我们了解到,在调用 f的地方,编译器使用expr来推导T和ParamType 的类型。
当一个变量被声明为auto ,auto 相当于模板中的T,而对变量做的相关的类型限定就像ParamType
这里的情况也有三种
- 情况1:类型声明是一个指针或者是一个引用,但不是一个通用的引用
- 情况2:类型声明是一个通用引用
- 情况3:类型声明既不是一个指针也不是一个引用
通过传入的参数类型(左值还是右值),结合类型声明(引用类型,const类型等),判断出T的类型
总结
-
auto类型推导通常和模板类型推导类似,但是auto 类型推导假定花括号初始化代表的
类型是 std::initializer_list ,但是模板类型推导却不是这样 -
auto在函数返回值或者lambda参数里面执行模板的类型推导,而不是通常意义
的auto 类型推导
理解 decltype
给定一个变量名或者表达式, decltype会告诉你这个变量名或表达式的类型
总结
- decltype几乎总是得到一个变量或表达式的类型而不需要任何修改对于非变量名的类型为 T 的左值表达式,decltype总是返回T&
- C++14支持decltype(auto) ,它的行为就像auto,从初始化操作来推导类型,但是它推
导类型时使用decltype的规则