C++ 万能引用和模板类型推断

一、万能引用

首先我们来看一个函数模板的代码:

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 &&param)     //  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 &&param)  {}      //不是万能引用

四、推导规则

在函数模板推导类型时,实际上要推导两个类型(T:模版参数,param:函数形參)

需要用T定义变量,因此需要知道模板参数T的类型

可能需要将形参para传给其他的变量,因此需要知道形参para的类型

函数模板里的几种形参类型:

1. 左值引用(指针)

按指针或者引用传递 (实参是引用时,会抛弃引用)

template <typename T>
void func(T &param) // 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)))

区别:

  1. auto忽略const ,忽略引用,decltype保留引用
  2. 引用推导的时候,auto推导出原类型(抛弃&),deltype推导出引用
  3. 解引用(*)推导的时候,auto推导出原类型,decltype推导出的是原类型的引用
int *p = &count;
auto temp = *p;    //temp--->int
decltype(*p) temp = count;  //因为temp被推导为int & ,所以要初始化,即得绑定一个左值
  1. 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;
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值