Item1——理解模板类型推导(Understand template type deduction)
条款1——理解模板类型推导
本小节主要介绍模板函数的推导类型。在一般情况下,我们可以根据模板调用很快就看出模板的类型。但是在复杂的场景中,我们无法快速看出来,这就需要我们去了解并且属性模板类型的推导规则。
template<typename T>
void f(ParamType param);
f(expr); //通过expr 来推导 T 和 param 的类型
T 类型的推导结果,依赖于 e x p r expr expr 的类型 , 还依赖 P a r a m T y p e ParamType ParamType 的形式。这里需要分为三种不同的情况讨论:
- P a r a m T y p e ParamType ParamType 是引用或指针类型,但不是万能引用
- P a r a m T y p e ParamType ParamType 是万能引用
- P a r a m T y p e ParamType ParamType 即不是指针也是引用
Case 1 : P a r a m T y p e ParamType ParamType 是一个指针或者引用,但不是万能引用
这种情况下的推导规则为:
- 若 e x p r expr expr 是引用类型,则会先将引用部分忽略
- 接着,再对 e x p r expr expr 的类型和 P a r a m T y p e ParamType ParamType 的类型执行模式匹配 , 来决定 T 的类型
第一个例子:
template<typename T>
void f(T& param);
声明了以下的变量:
int x = 27; //x 的类型为 int
const int cx = x; //cx 的类型为 const int
const int& rx = x; //rx 的类型为 const int 的引用
在三次调用中,对 p a r a m param param 和 T 的类型推导结果为:
f(x); //T 的类型是 int param 的类型是 int&
f(cx); //T 的类型是 const int param 的类型是 const int&
f(rx); //T 的类型是 const int param 的类型是 const int&
注意:
- 在第二个,三个调用中,因为rx 和 cx 都被声明为const int。所以T 也被推导为 const int,形参被推导为const int&。向有 T& 类型的模板传入 const对象是安全的,因为该对象的常量属性会成为 T 的类型推导结果的组成部分。
- 在第三个调用中,即使 rx 有引用类型,T 也没有被推导为引用。因为 rx 的引用性(referednce-ness)会在推导过程中被忽略。
第二个例子:
template<typename T>
void f(const 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 的类型是 const int&
f(cx); //T 的类型是 int param 的类型是 const int&
f(rx); //T 的类型是 int param 的类型是 const int&
第三个例子:
template<typename T>
void f(T* param); //现在 param 是一个指针 推到方式也是类似的
int x = 27; //x 的类型为 int
const int* px = &x;
f(&x); //T 的类型是 int param 的类型是 int*
f(px); //T 的类型是 cosnt int param 的类型是 const int*
Case 2 : P a r a m T y p e ParamType ParamType 是一个万能引用
对于有万能引用形参的模板而言,规则就没那么简单了。此类形参的声明方式类似于右值引用(即在函数模板中有类型形参T时,万能引用的声明类型写作 T&&), 更详细的在 Item 24 中展开介绍。
- 如果 e x p r expr expr 是左值, T 和 P a r a m T y p e ParamType ParamType 都会被推到为左值引用
- 如果 e x p r expr expr 是右值, 则复合Case 1 中的规则
例子:
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&&
注意:
- 万能引用形参的推导规则不同于左值引用 和 右值引用形参。当遇到万能引用的时候,类型推导规则会区分实参是左值还是右值。非万能引用不会做这样的区分。
Case 3 : P a r a m T y p e ParamType ParamType 既不是指针也不是引用
当 P a r a m T y p e ParamType ParamType 既不是指针也不是引用时,也就是所谓的值传递的时候。因为值传递相当于是拷贝实参的一个副本,无论这个副本发生什么变化,都不会影响到实参。
- 如果 e x p r expr expr 是引用类型,则忽略其引用部分
- 如果 e x p r expr expr 是const对象,则忽略。是volatile对象也忽略。
第一个例子:
template<typename T>
void f(T param); //此时param 是值传递
int x = 27;
const int cx = x;
const int& rx = x;
f(x); //T 和 param 的类型都是 int
f(cx); //T 和 param 的类型都是 int
f(rx); //T 和 param 的类型都是 int
注意:即使rx 和 cx 都有const 也不会影响到身为副本的param。同样实参若有volatile,也不会影响到副本parameter。
需要说明的是,const 和 volatile 仅仅会在 按值形参的地方被忽略。如果形参是 const 的引用或者指针, e x p r expr expr 的常量属性会在类型推导的过程中加以保留。
第二个例子:
template<typename T>
void f(T param);
const char* const ptr = "感觉不如原神...画质"; //ptr 是一个指向 const对象 的 const 指针
f(ptr) //param 的类型为 const char*
ptr 这个指针本身会按比特复制给 param,这个指针自己会被按值传递。在类型推导的过程中,ptr指向对象的常量属性得到保留,但自身的常量属性则会在以复制方式创建新指针 param 的过程中被忽略。
数组实参
除了以上三种主流的情况以外,还有一些少数的例子——传递数组的情况。
一般来说数组类型是不同于 指针类型的,尽管有时候它们看起来可以互换。造成这种情况的原因是:有时候数组会退化成指向其首元素的指针,以下代码就是例子:
const char name[] = "Genshin..."; //name 的类型为 const char[13]
const char* ptr = name; //数组退化成了指针
这时我们考虑 把 数组传递给 值传递的函数模板的情况
template<typename T>
void f(T param);
f(name);
由于数组形参声明会按照它们好像是指针形参那样处理,按值传递给函数模板的数组类型将被推导成指针类型。
f(name); //name虽然是一个数组,但是 T 的类型却被推导成 const char *
值得注意的是:尽管函数无法声明真正的数组类型的形参,但是却可以将形参声明为数组的引用!
template<typename T>
void f(T& param); //此时是按引用传递
f(name); //T 被推导为 const char[13] ,param 的类型为 const char (&) [13]
我们利用声明数组引用的这一特性可以写一个模板,用来推导出数组含有的元素个数:
//以编译期常量的方式返回数组的元素个数
//该数组形参没起名字,在这里只关心其中含有元素的个数
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
return N;
}
通过这个模板函数,我们可以声明一个数组,它的元素个数 和 第一个数组的元素个数相同
int keyVals[] = { 1,2,3,4,5,6,7 };
int mappedVals[arraySize(keyVals)];
std::array<int, arraySize(keyVals)> mappedVals;
函数实参
与数组相同,函数也会退化成函数指针。所以两者的推到规则也大致相同。
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); // f2 按引用传递
f2(someFunc); // param是函数引用 类型为 void(&)(int,double)
总结
- 在模板类型推导过程中,具有引用类型的实参会被当成非引用类型来处理。即它的引用属性会被忽略
- 对万能引用形参进行推导时,左值实参进行特殊处理
- 对按值传递的形参进行推导时,如果其实参类型带有 const 或 volatile 修饰词,则会忽略其属性
- 在模板类型推导过程中,数组 或 函数类型的实参会退化成对应的指针,除非它们被用来初始化引用
相关文章
- 母函数模板核心
- 【C/C++学院】0825-类模板/final_override/类模板与普通类的派生类模板虚函数抽象模板类/类模板友元/位运算算法以及类声明/Rtti 实时类型检测/高级new创建/类以及函数包装器
- 2 ~ express ~ 模板引擎的配置与使用
- spring mvc:练习 @RequestParam(参数绑定到控制器)和@PathVariable(参数绑定到url模板变量)
- pycharm中python模板代码自动生成
- Django 模板
- 90、 Android UI模板设计
- js es6 模板字符
- zblog模板怎么安装?zblog主题安装教程
- Go Gin框架使用模板:传入参数(页面显示参数)
- 【面试攻略】模板和动态参数
- windows屏幕保护程序opengl模板
- thinkphp在前端页面的js代码中可以使用 U方法吗? 可以使用模板变量如__URL__等吗?
- 基于JAVA实现的WEB端UI自动化 - WebDriver框架篇 - XSLT (报告、模板框架)
- ubuntu20.04/16.04下C++的通用Makefile模板解析
- 【C++要笑着学】STL Array | 非类型模板参数 | 模板的特化 | 全特化与半特化 | 模板的优缺点