本文探讨的问题是以下4点
0.什么是万能引用
1.为什么万能引用可以接收左值和右值
2.为什么右值传过去后会变成左值
3.如何继续保持类型(完美转发)
问题一:什么是万能引用
在c++中类型的值可以分为左值和右值,万能引用顾名思义就是可以引用左值和右值,当传入左值的时候发生了经过推导发生了《引用折叠》可以接收,当传入右值的时候推导出来就是右值
这里出现了【推导】这个字眼,这就说明了万能引用是靠推导而来的,所以只能靠模板或者auto自动推导!这里举个例子
//右值引用
void fun(int&& val){};
//万能引用
template<class T>
void fun(T&& t) {}
//右值引用
int&& ref=get_val();
//万能引用
auto&& rft=get_val();
这里可以看到参数或者返回值为固定类型就为右值引用而不是万能引用,需要自动推导才是万能引用
这里需要注意,直接参与类型推导才算是万能引用,间接参与推导的也还是右值,举个例子
template<class T>
void bar(std::vector<T>&& t) {}
这个就是右值引用,虽然有模板的推导但是这个推导是给vector使用的而不是给t推导的类型,还是那句话直接推导才是万能引用!
问题二:为什么万能引用可以接收左值和右值
参考以下代码
这里用到一个库函数:is_same_v<T,T>这个函数用来判断2个类型是不是同一个类型
这里需要认识一下模板推导
调用fun(i)的时候,因为i是左值而模板类型为T&&实际类型为T&编译器对其引用折叠把其实例化为
void func(int& t) {
std::cout << "t是不是【int&】类型" << std::is_same_v<int&, int&> << " " << "t是不是【int】类型" << std::is_same_v<int&, int> << std::endl;
}
所以输出 1,0
调用fun(2)的时候,因为2是右值编译器吧其实例化为,这里的T不会发生引用折叠,T就是int
void func(int&& t) {
std::cout <<"t是不是【int&】类型" << std::is_same_v<int, int&> << " " << "t是不是【int】类型" << std::is_same_v<int, int> << std::endl;
}
所以输出 0,1
这就是为什么万能引用可以接收左值和右值的原因,传入左值就发生引用折叠传入右值就自动推演正常接收
问题三:为什么右值传过去后会变成左值
先看这段代码
这里可以发现在这个万能引用模板中调用process()函数,不管是左值还是右值最终调用的还是左值引用为参数类型的函数
func( i )是左值调用的时候是左值,模板推演后还是左值没错,正常调用void process(int& val);
而
func(2)是右值模板推演后就变成左值了,这个怎么感觉和之前说的很矛盾?是吧
这里要注意:万能引用是可以接收左值和右值,但是不能保证右值接收过来后还能保持右值的属性,左值的属性是可以保持的
原因可以这样理解:void func(T&& t)接收的右值传递过来后,右值也要有保存自己变量的空间,这个空间就变成左值引用了
这样就解释了为什么传入的明明是右值且万能引用也可以接收但是在内部却变成了左值!
问题四:如何继续保持类型(完美转发)
这里可以使用static_cast<T>(val);对其进行类型的转化,使其在内部继续保持原始的类型
这里调用process之前使用了static_cast对其进行类型转化,若是传入左值则推导为:
void func(int& t) {
process(static_cast<int&>(t));
}
这里就自然调用了左值引用的 void process(int& val);
若是传入左值则推导为:
void func(int&& t) {
process(static_cast<int&&>(t));
}
就自然调用了右值引用的 void process(int&& val);
这样的转化就实现了属性的保持,再对其封装一下
template<class T>
T&& forward(T& v) {
return static_cast<T&&>(v);
}
这样就完成了封装,左右值成功调用到对应的函数,这个也是模仿库里面实现的代码
这个就是完美转发,解决了传递参数的时候右值会被折叠为左值的问题,传递的时候依旧保持原有属性
这个是库里的forward
不过这里还有一个小缺陷,不能在main函数里面调用,因为这个forward的实现接收的属性只能是左值,解决这个问题只需要重载一个类模板就可以了
template<class T>
T&& forward(T& v) {
return static_cast<T&&>(v);
}
//重载
template<class T>
T&& forward(T&& v) {
return static_cast<T&&>(v);
}
成功调用,解决了直接调用forward的问题