一、万能引用
首先我们来看一个函数模板的代码:
template<typename T>
void func(T& param){
}
这里模板函数参数接收一个引用,但是却不能这样调用:
func(27);
//error: invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’
这里参数只能接受一个“左值”(在C++11中可以取址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值),而27是一个右值。
这时候万能引用就要上场了~
再来看这个代码
template<typename T>
void func(T&& param){
}
再调用func
func(27)
正确,万能引用可以传入一个右值,不过传进来后类型也就成了int而不是引用。
注意:
1. 万能引用必须是函数模板。
2. 必须发生了模板类型推断并且函数模板的样子是:T&&;auto 也存在万能引用的概念。
template<typename T>
void Func(vector<T>&& a) //注意此处是右值引用
{
}
//用法
vector<int> vec;
Func(vec); //编译会报错
Func(std::move(vec)); //可以将左值转换成右值引用
二、万能引用资格剥夺和辨认
注意:
1. 剥夺:const修饰词会剥夺引用称为万能引用的资格,会被打回原形称为右值引用。
```cpp
template<typename T>
void func(const T&& param){
}
再调用func
func(27); //编译报错,有待验证
辨认
template<typename T>
class Test
{
public:
void Func(T&& a) {} //此处是一个右值引用,不是万能引用
template<typename F>
void Func1(F&& a) {} //真正的模板,是一个万能引用
};
int main()
{
Test<int> test; //实例化后,能实例具体类型,不是模板类型
int a = 15;
test.Func(a); //编译报错,不能将左值转换成右值引用
test.Func(std::move(a)); //编译正常
test.Func1(a); //编译正常,是万能引用
return 0;
}
三、类型推导
类型推导:类型推导规则
问题1:我们为什么要掌握类型推导规则?
答:只有当我们熟悉类型推导的规则,才能让我们的代码更加的灵活,通用性好。
类型推导的作用:代码更加的灵活,通用性好
类型推导的适用场景:
函数模板、类模板(万能引用)
auto
decltype
decltype(auto)
函数模板的类型推导规则:
知识点1:万能引用(未定义引用)(只限定函数模板,在其他场合下均是右值引用)
作用:代码通用性增加(因为万能引用可以接受任何类型)
template<typename T> //只限定在模板里面
void func(T &¶m) // T && 实际上就一个万能引用(未定义引用)
{
cout << "&&" << " " << param << endl;
}
int main()
{
int num = 5;
int &lr_num = num;
int &&rr_num = std::move(num);
func(num); //传入一个变量
func(5); //传入一个常量
func(lr_num); //传入一个左值引用
func(rr_num); //传入一个右值引用
return 0;
//全部调用func(T && param)
}
万能引用:既可以接受左值传递,也可以接受右值
且 T后面必须紧跟&&
template<typename T>
void func1(std::vector<T> &&v1) {} //不是万能引用 尖括号阻隔了T与&&
const关键字会消除万能引用
template<typename T>
void func(const T &¶m) {} //不是万能引用
四、推导规则
在函数模板推导类型时,实际上要推导两个类型(T:模版参数,param:函数形參)
需要用T定义变量,因此需要知道模板参数T的类型
可能需要将形参para传给其他的变量,因此需要知道形参para的类型
函数模板里的几种形参类型:
1. 左值引用(指针)
按指针或者引用传递 (实参是引用时,会抛弃引用)
template <typename T>
void func(T ¶m) // T:int & param: int && error int *&
{
// 利用Boost库打印模板推导出来的 T 类型
cout << "T type:" << type_id_with_cvr<T>().pretty_name() << endl;
// 利用Boost库打印形参的类型
cout << "param type:" << type_id_with_cvr<decltype(param)>().pretty_name() << endl;
}
const int &lrnum = num;
func(lrnum);
T---->int; param---->int &
2. 变量
按值传递(实参是const和volatile修饰,这些关键字会抛弃 cv)
template <typename T>
void func(T param)
{
cout << "T type:" << type_id_with_cvr<T>().pretty_name() << endl;
cout << "param type:" << type_id_with_cvr<decltype(param)>().pretty_name() << endl;
}
const int temp = 5;
func(temp); //T->const int temp->const int & 错误
temp 为const int,推导出来的T----> int ;param---->int &
3. 万能引用
传参时会发生引用折叠
折叠规则:传左就是左,传右就是右
int num = 5, num2 = 9;
int &&rrnum = std::move(num);
func(rrnum); //5
rrnum = num2; //rrnum仍然为左值
func(rrnum); //9
右值引用的变量名其实是左值 T---->int & param---->int &
这里的rrnum是一个左值变量,可以绑定在左值上。 但是&&rrnum是一个右值引用
func(5);
传入的常量是一个右值
T ----> int; param---->int && (但是param本身是个左值,可以绑定左值)
4. 数组和函数
退化成相应类型的指针
五、转发和完美转发的前提是万能引用
转发:将模板参数转发给其他函数(存在问题:param始终是一个左值,无法传递给一个形参为右值的函数)
解决办法:
1.std::move();
test(std::move(param));
2.完美转发std::forward()
test(std::forward(param));
六、查看类型的函数
typeid().name() //我们不用这个函数,因为是有缺陷的
现在用boost:type_id_with_cvr()
cout << "T type:" << type_id_with_cvr<T>().pretty_name() << endl;
cout << "param type:" << type_id_with_cvr<decltype(param)>().pretty_name() << endl;
七、auto:类型推导规则 == 函数模板的推导规则
int a = 0;
const auto n = a; //n 为 const int ,auto 被推导为 int
auto f = n; //n 为 const int,auto 被推导为 int(const 属性被抛弃)
const auto &r1 = a; //r1 为 const int& 类型,auto 被推导为 int
auto &r2 = r1; //r1 为 const int& 类型,auto 被推导为 const int 类型
auto 与 const 结合的用法:
当类型不为引用时,auto 的推导结果将不保留表达式的 const 属性;
当类型为引用时,auto 的推导结果将保留表达式的 const 属性。
auto:限制规则 auto变量必须在定义时初始化
1.auto不能用于函数参数
我们在定义函数的时候只是对参数进行了声明,指明了参数的类型,但并没有给它赋值,只有在实际调用函数的时候才会给参数赋值;而 auto 要求必须对变量进行初始化,所以这是矛盾的。
2.auto不能在类中推导非静态成员变量
原因还是因为auto修饰的变量定义时必须进行初始化
3.auto不能推导数组
4.auto不能在函数模板中推导形参,可以推导返回值
//void func(auto param)
template<typename T>
decltype(auto) func(T param)
{
return 1;
}
template<typename T>
auto func(T param)->decltype(param) //后置语法
{
return 1;
}
八、decltype类型推导
decltype(类型推导):返回给定变量或者表达式的类型(不计算表达式的值)
#include <boost/type_index.hpp>
using boost::typeindex::type_id_with_cvr;
int main()
{
int num = 5;
decltype(num++) temp; //不会执行表达式
cout << num << endl; //5
cout << "typeid" << type_id_with_cvr<decltype(temp)>().pretty_name() << endl;
//temp--->int
return 0;
}
推导规则: auto VS decltype (只是复述一遍变量名或者表达式的类型(保存引用和cv(const volatile)))
区别:
- auto忽略const ,忽略引用,decltype保留引用
- 引用推导的时候,auto推导出原类型(抛弃&),deltype推导出引用
- 解引用(*)推导的时候,auto推导出原类型,decltype推导出的是原类型的引用
int *p = &count;
auto temp = *p; //temp--->int
decltype(*p) temp = count; //因为temp被推导为int & ,所以要初始化,即得绑定一个左值
- auto推导时会执行; decltype不执行,只分析
decltype:通过函数调用推导函数的返回值;
int get_num()
{
return 5;
}
decltype(get_num()) temp2;
主要应用: 类的迭代器
template<typename T>
class A
{
public:
//typename T::iterator it;
//T == const vector<int>::iterator it;
decltype(T().begin()) it;
void getbegin(T &tmp)
{
it = tmp.begin();
}
};
int main()
{
const vector<int> b = { 1,2,3,4,5};
A<const vector<int>> a;
a.getbegin(b);
return 0;
}