引用折叠
template<typename T>
void print(T &&t)
{
}
在使用如上的模板函数时,如果T的类型为T&或者T&&,则形参t的类型对应为T& &&或者T && &&。此时形成了引用的引用,从而会发生引用折叠,引用折叠规则如下:
引用1 | 引用2 | 折叠结果 |
& | & | & |
& | && | & |
&& | & | & |
&& | && | && |
从表中可以看出引用折叠的规则为任意一个引用为左值引用时结果为左值引用,只有当两个引用都为右值引用时结果才为右值引用。
所以上面的例子当形参t类型为T& &&时,引用折叠为T&。当形参t类型为T&& &&时,引用折叠为T&&。
万能引用
void print1(int &t)
{
}
void print2(int &&t)
{
}
上面两个函数,print1只能接收左值,print2只能接收右值。如果需要一个既能接收左值也能接收右值的函数,这时就用到万能引用。使用模板函数时,函数参数设置为T&&,该函数能够接收左值和右值,进而将参数处理为左值引用和右值引用,因此称为万能引用。
template<typename T>
void print(T &&t)
{
}
int main()
{
int x = 100;
print(x);
print(1);
return 0;
}
在推导类型时,将print中的T和&&分开看。
print(x)时,T推导为int&,模板函数推导为print(int & &&t),根据引用折叠变化为print(int &t)。
print(1)时,T推导为int,模板函数推导为print(int &&t),根据引用折叠变化为print(int &&t)。
可以看出print(T &&t)配合引用折叠可以接收左右值参数,进而将其对应处理为左右值引用。
完美转发
template<typename T>
void print(T & t)
{
std::cout << "Lvalue ref" << std::endl;
}
template<typename T>
void print(T && t)
{
std::cout << "Rvalue ref" << std::endl;
}
template<typename T>
void testForward(T && v)
{
print(v);
}
int main()
{
testForward(1);
return 0;
}
上面程序执行后,输出"Lvalue ref"。上面万能引用可知testForward参数v是个右值引用,但是调用print时还是调用的左值模板函数。
万能引用只是提供接收的左值和右值的能力,并将参数转化为对应的左右值引用。但是左值引用和右值引用作为参数时,c++后续使用都会被处理为左值, 因此print会调用左值模板函数。
c++使用std::forward完美转发来解决模板函数处理万能引用需要区分原有左右值属性的问题。
template<typename T>
void print(T & t) {
T x = t;
std::cout << "Lvalue ref" << std::endl;
}
template<typename T>
void print(T && t) {
std::cout << "Rvalue ref" << std::endl;
}
template<typename T>
void testForward(T && v)
{
print(std::forward<T>(v));
}
int main()
{
int xy = 1;
testForward(xy);
testForward(1);
return 0;
}
执行上面代码,输出为:
Lvalue ref
Rvalue ref
testForward分别传入左值和右值,使用std::forward处理参数v,可以保持v原有的左右值属性进而匹配相应的print函数。