1. 某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。在此情况下,我们需要保持被转发实参的所有性质。包括实参类型是否const的,以及实参是左值还是右值。
2. 例子
template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2) //f是可调用对象
{
f(t2, t1); //将两个额外的参数逆序传递给f
}
○ 这个函数一般情况下工作得很好,但当我们希望用它调用一个接受引用参数的函数就会出现问题:
void f(int v1, int &v2)
{
cout << v1 << " " << ++v2 << endl;
}
○ f改变了绑定到v2的实参的值。但是,如果通过flip1调用f,f所做的改变就不会影响实参:
f(42, i); //f改变了实参i
flip1(f, j, 42); //==>f(42,j); 通过flip1调用f不会改变j
○ 问题在于j被传递给flip1的参数t1.此参数是一个普通的丶非引用的类型int,而非int&.因此,这个flip1调用会实例化为
void flip1(void(*fcn)(int, int&), int t1, int t2);
○ 因此j的值被拷贝到t1中。f中的引用参数被绑定到t1,而非j,从而其改变不会影响j。
○ 正确的定义:
template<typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2)
{
f(t2, t1);
}
○ 如果调用flip2(f, j, 42),将传递给参数t1一个左值j,而T1被推断为int&(左传右的第一个例外规则),此时t1类型为(int& &&),因此T1被折叠为int&;从而t1被绑定到j上,v2也因此被被绑定到t1,即被绑定到j。关于引用折叠。
| 注 | 如果一个函数参数是指向模板类型参数的右值引用(如T&&),它对应的实参的const属性和左值/右值属性将得到保持 |
○ 但是flip2对于接受一个左值引用的函数工作得很好,而不能用于接受右值引用参数的函数,如
void g(int &&i, int &j)
{
cout << i << " " << j << endl;
}
○ 当试图通过flip2调用g,则参数t2将被传递给g的右值引用参数。即使我们传递一个右值给flip2:
flip2(g, i, 42); //错误:不能将一个左值实例化int&&
○ 因为此时filp2实例化为
flip2<void (*)(int &&, int &), int &, int>;
所以调用g时发生错误。
3. 保持类型信息:std::forward
○ 这个新标准库设施可以保持原始实参的类型。不同于move,forward必须通过显式模板实参来调用。forward返回该显式实参类型的右值引用。
○ 通常,我们使用forward传递那些定义为模板类型参数的右值引用的函数参数。通过其返回类型上的引用折叠,forward可以保持给定实参的左值/右值属性。
903

被折叠的 条评论
为什么被折叠?



