【C/C++】【C++11】引用折叠和完美转发
引用折叠规则
- 引用折叠 C++11新标准
- C++中有明确含义的引用只有两种,一种是&左值引用类型,一种是带&&右值引用类型;
- 折叠规则:如果任意一个引用为左值引用,那么结果就为左值引用,否则就是右值引用
一共下面四种情况 & & & && && & && &&
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
template <typename T> //T是类型模板参数,T是由类型的
void func(T &&tmp)//tmp形参,形参是有类型的 tmp形参的类型和T模板参数的类型不一样
{
//T的类型不仅仅和调用者提供的实参有关系,还和tmp的类型有关;
cout << "--------------s-----------------" << endl;
using boost::typeindex::type_id_with_cvr;
cout << "T = " << type_id_with_cvr<T>().pretty_name() << endl; //显示T类型
cout << "tmp = " << type_id_with_cvr<decltype(tmp)>().pretty_name() << endl; //显示tmp类型
cout << "---------------e----------------" << endl;
}
int main()
{
int i = 19;
func(i); //i是左值, T = int &, tmp = int &
func(100); //i是右值,T = int, tmp = int &&
//i是左值时
//void func(int & &&tmp){...} int&一组 &&tmp是一组,我们认为编译器实例化的结果
//void func(int & tmp){...} 编译器实际实例化的
//& &&变成& 即为引用折叠 C++11新标准
//一共下面四种情况
//& &
//& &&
//&& &
//&& &&
//折叠规则:如果任意一个引用为左值引用,那么结果就为左值引用,否则就是右值引用
return 0;
}
引用的引用
int b = 5;
int& bb = b;
//int&& bx = bb; 不存在引用的引用
//编译器内部在进行函数模板推断的时候可以有,有引用折叠,有引用的引用这个叫法,但是没有这个写法;
转发、完美转发
转发1
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
void func(int m, int n)
{
++n;
cout << m + n << endl;
}
//函数模板:要把收到的参数以及这些参数相应的类型(比如左值引用还是右值应用,比如const)不变的转变给其他函数(转发给func函数),就是转发
template <typename F, typename T1, typename T2>
void func_test(F f, T1 t1, T2 t2) //F就是要转发到的目标函数
{
//转发
f(t1, t2);
}
int main()
{
int i = 50;
//func(42, i);
int j = 70;
func_test(func, 20, j);
cout << j << endl;
return 0;
}
转发1的问题
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
void func(int m, int &n)
{
++n;
cout << m + n << endl;
}
//函数模板:要把收到的参数以及这些参数相应的类型(比如左值引用还是右值应用,比如const)不变的转变给其他函数(转发给func函数),就是转发
template <typename F, typename T1, typename T2>
void func_test(F f, T1 t1, T2 &t2) //F就是要转发到的目标函数
{
//转发
f(t1, t2);
}
int main()
{
int i = 50;
//func(42, i);
int j = 70;
func_test(func, 20, j);
cout << j << endl;
return 0;
}
转发2
- T&&,实参的所有信息都会传递到万能引用当中,从而让编译器推导出来函数模板最终的形参类型;
- T& 实参中只有const属性能传递到函数模板,而实参中的左值和右值性质就无法传递到函数模板中去,所以我们必须用T&&
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
void func(int m, int &n)
{
++n;
cout << m + n << endl;
}
//函数模板:要把收到的参数以及这些参数相应的类型(比如左值引用还是右值应用,比如const)不变的转变给其他函数(转发给func函数),就是转发
template <typename F, typename T1, typename T2>
void func_test(F&& f, T1 &&t1, T2 &&t2) //F就是要转发到的目标函数
{
//转发
f(t1, t2);
}
int main()
{
int i = 50;
//func(42, i);
int j = 70;
func_test(func, 20, j);
cout << j << endl;
return 0;
}
转发2的问题
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
void func(int m, int &n)
{
++n;
cout << m + n << endl;
}
void func_(int&& m, int& n)
{
cout << m << " " << n << endl;
}
//函数模板:要把收到的参数以及这些参数相应的类型(比如左值引用还是右值应用,比如const)不变的转变给其他函数(转发给func函数),就是转发
template <typename F, typename T1, typename T2>
void func_test(F&& f, T1 &&t1, T2 &&t2) //F就是要转发到的目标函数
{
f(t1, t2); //t1是左值, m是右值,所以出错
//无法将参数 1 从“T1”转换为“int && ”
}
int main()
{
int i = 50;
//func(42, i);
int j = 70;
func_(50, j);
int&& r_ref = 80; //右值引用绑定右值 虽然&&是绑定到右值 但是右值引用r_ref本身是左值;
int& l_ref = r_ref;
int x = 5;
//func_(2, x);
func_test(func_, 2, x);
return 0;
}
完美转发
- 可以写接受任何类型实参的函数模板,并将其转发到目标函数,目标函数会接收到与转发函数所接收到的完全相同(左值性和右值性都要保持的实参);
- 使用std::forward来实现完美转发
std::forward
- C++11新函数,专门为转发而存在;要么返回一个左值,要么返回一个右值;
- 发挥作用的条件:调用函数模板,函数模板参数是万能类型,函数模板负责转发;
- std::forward的能力就是按照参数本身的类型转发;
- 对std::forward函数的两种理解
- 实参如果原来是左值,到了形参中还是左值,forward是按照形参原来的类型处理,所以std::forward之后还是个左值;
- 实参如果原来是右值,到了形参中变成了左值,forward是按照形参原来的类型处理,所以std::forward之后还是右值;
- forward有强制把左值转成右值的能力,所以forward只是对原来是右值的情况有用;
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
void func(int m, int &n)
{
++n;
cout << m + n << endl;
}
void func_(int&& m, int& n)
{
cout << m << " " << n << endl;
}
//函数模板:要把收到的参数以及这些参数相应的类型(比如左值引用还是右值应用,比如const)不变的转变给其他函数(转发给func函数),就是转发
template <typename F, typename T1, typename T2>
void func_test(F&& f, T1 &&t1, T2 &&t2) //F就是要转发到的目标函数
{
f(std::forward<T1>(t1), std::forward<T2>(t2)); //t1是左值, m是右值,所以出错
//无法将参数 1 从“T1”转换为“int && ”
}
int main()
{
int i = 50;
//func(42, i);
int j = 70;
func_(50, j);
int&& r_ref = 80; //右值引用绑定右值 虽然&&是绑定到右值 但是右值引用r_ref本身是左值;
int& l_ref = r_ref;
int x = 5;
//func_(2, x);
func_test(func_, 2, x);
return 0;
}
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
void printInfo(int& t) //类型为左值引用的形参
{
cout << "void printInfo(int& t)" << endl;
}
void printInfo(int&& t) //类型为右值引用的形参
{
cout << "void printInfo(int&& t)" << endl;
}
template <typename T>
void Test(T&& t) //万能引用
{
printInfo(t); //t是右值, T = int, t = int && t本身是左值
//t是左值, T = int &, t = int & t本身是左值
printInfo(std::forward<T>(t)); //t为右值
//t为左值
printInfo(std::move(t)); //左值转右值
}
int main()
{
Test(4); //4是右值 左值引用,右值引用,右值引用
cout << "-------------------------------" << endl;
int i = 5;
Test(i); //i是左值 左值引用,左值引用,右值引用
//原来实参是左值还是右值会保存到Test里的万能引用中的T类型中去;必须是万能引用才会保存;
return 0;
}
std::move和std::forward的区别
std::forward是强制把一个左值转成一个右值,但是如果实参就是左值,那么forward啥也不干;有条件强制类型转换;
std::move无条件强制类型转换;
万能引用
- 万能引用不是新的引用类型;只是一种形式;T&&
- 万能引用的概念有意义,方便理解引用折叠;
std::forward用法补充
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
int main()
{
int tmp = 12;
int&& def = std::move(tmp);
//std::forward <>内部是什么,就是要把tmp转换成什么类型
int&& r_def = std::forward<int>(tmp); //forward能把左值成功转换成右值;
return 0;
}
知识的价值不在于占有,而在于使用