Effective Modern C++(三)引用折叠

发布时间 2023-06-04 18:30:52作者: icecreamjn
template<typename T>
void func(T&& param);

对于一个通用引用,只有当实参被用来实例化通用引用形参时,才会推导形参T。

编码机制是简单的。当左值实参被传入时,T被推导为左值引用。当右值被传入时,T被推导为非引用。

Widget widgetFactory();     //返回右值的函数
Widget w;                   //一个变量(左值)
func(w);                    //用左值调用func;T被推导为Widget&
func(widgetFactory());      //用右值调用func;T被推导为Widget

这一编码机制也是forward函数进行完美转发的基础。

在此,我们需要认识到,声明一个引用的引用是不合法的,因为引用本身不是一个对象,所以不能定义引用的引用

int x;
…
auto& & rx = x;             //错误!不能声明引用的引用

但是,再次考虑这个模版,当我们的T是一个左值时,模版会被实例为一下样式

template<typename T>
void func(T&& param);

func(w);  

void func(Widget& && param);

一个引用的引用,这显然是不合法的,但是编译器为什么没有报错呢?

答案是引用折叠reference collapsing)。是的,禁止声明引用的引用,但是编译器会在特定的上下文中产生这些,模板实例化就是其中一种情况。当编译器生成引用的引用时,引用折叠指导下一步发生什么。

引用根据规则折叠为单个引用:

如果任一引用为左值引用,则结果为左值引用。否则(即,如果引用都是右值引用),结果为右值引用。

再次回看上节讲的forward函数

template<typename T>
void f(T&& fParam)
{
    …                                   //做些工作
    someFunc(std::forward<T>(fParam));  //转发fParam到someFunc
}

因为fParam是通用引用,我们知道类型参数T的类型根据f被传入实参(即用来实例化fParam的表达式)是左值还是右值来编码。

std::forward的作用是当且仅当传给f的实参为右值时,即T为非引用类型,才将fParam(左值)转化为一个右值。

std::forward可以这样实现:

template<typename T>                                //在std命名空间
T&& forward(typename
                remove_reference<T>::type& param)
{
    return static_cast<T&&>(param);
}

当实参为一个左值时,模版被实例化为

Widget& forward(Widget& param)
{ return static_cast<Widget&>(param); }

当实参为一个右值时,T被推导为一个非引用,因此模版被实例化为

Widget&& forward(Widget& param)
{ return static_cast<Widget&&>(param); }

引用折叠发生在四种情况下。第一,也是最常见的就是模板实例化。第二,是auto变量的类型生成,具体细节类似于模板,因为auto变量的类型推导基本与模板类型推导雷同。

以及typedef与别名声明的创建和使用,decltype