Item 28: Understand reference collapsing.
引用折叠。
不同的引用是不能同时使用的。例如: Widget& && w 是非法的。但是在特定情况下,引用是可以折叠的。
首先一个知识点:
在使用模板函数的时候
template<typename T>
void func(T&& param)
C++的编码机制为,当传入参数为左值的时候,T将推导为左值引用,当传入参数为右值的时候,T将推导为非引用
例如:
Widget widgetFactory();
Widget w;
func(w); w为左值,将被推导为Widget&类型
func(widgetFactory()); 参数为右值,将被推导为Widget类型
下面类解释引用折叠原理。当在以下四种情况的时候,引用是可以连续使用的,但是会被系统折叠,折叠的原理就是
T& & -> T&
T& && -> T&
T&& & -> T&
T&& && -> T&&
左值右值引用的配搭有四种,只有当右值和右值在一起的时候会折叠为右值,其他的情况都是左值。
现在由引用折叠来解释完美转发的原理
template<typename T>
T&& forward(typename remove_reference<T>::type& param)
{
return static_cast<T&&>(param);
}
当传入一个左值Widge 的时候,T将被推导为Widge& 类型(上面说过),代入到模板中:
Widge& && forward(typename remove_reference<Widge&>::type& param)
{
return static_cast<Widge& &&>(param);
}
按照之前说的折叠原理:
Widge& forward(Widge& param)
{
return static_cast<Widge&>(param);
}
再来看当输入为右值的时候, T将被推导为Widge,代入:
Widge && forward(typename remove_reference<Widge>::type& param)
{
return static_cast<Widge&&>(param);
}
化简后:
Widge&& forward(Widge& param)
{
return static_cast<Widge&&>(param);
}
返回右值。
在c++14中,remove_reference_t 简化了代码:
template<typename T>
T&& forward(remove_reference_t<T>& param)
{
return static_cast<T&&>(param);
}
这是第一种可以折叠引用的情况,也是最常用的,第二种就是auto, 因为之前的章节讲过,auto 其实是和template 相似,auto&& w1 = w, 因为w1被初始化为左值,所以auto将被推导为 Widge&, 然后整体结果就是 Widge& && w1 = w, 折叠后Widge& w1 = w。 当 auto&& w2 = widgetFactory() 时,auto 被推导为 Widge(右值推导),结果就变成 Widge&& w2 = widgetFactory()。
第三种引用的折叠情况为typedefs 或者alias, 别名。
template<typename T>
class Widget{
public:
typedef T&& RvalueRefToT;
...
};
Widget<int&> w; 当我们用左值进行初始化时
typedef int& && RvalueRefToT; 这里将T替换为int&
typedef int& RvalueRefToT; 折叠后变成这样
最后一种就是decltype