引用折叠规则(保存为A& & -> A&
C++98/03)存在一个原因:允许完美转发工作。
“完美”转发意味着有效地转发参数,就好像用户直接调用了函数一样(减去省略,被转发破坏)。用户可以传递三种值:lvalues、xvalues 和 prvalues,并且接收位置可以通过三种方式获取值:按值、按(可能是 const)左值引用和按(可能是 const)右值参考。
考虑这个函数:
template<class T>
void Fwd(T &&v) { Call(std::forward<T>(v)); }
按价值
如果Call
按值获取其参数,则必须在该参数中进行复制/移动。哪一个取决于传入的值是什么。如果传入的值是左值,那么它必须复制左值。如果传入的值是一个右值(它们统称为 xvalues 和 prvalues),那么它必须从它移动。
如果Fwd
使用左值调用,C++ 的类型推导规则意味着T
将推导为Type&
,其中Type
是左值的类型。显然,如果左值是const
,它将被推导出为const Type&
。引用折叠规则意味着Type & &&
变成Type &
for v
,一个左值引用。这正是我们需要调用Call
的。用左值引用调用它会强制复制,就像我们直接调用它一样。
如果您Fwd
使用右值调用(即:Type
临时表达式或某些Type&&
表达式),则T
将推导出为Type
. 引用折叠规则给了我们Type &&
,它引发了移动/复制,这几乎就像我们直接调用它一样(减去省略号)。
通过左值引用
如果Call
通过左值引用获取它的值,那么它应该只在用户使用左值参数时才可调用。如果它是一个 const-lvalue 引用,那么它可以被任何东西(lvalue、xvalue、prvalue)调用。
如果您Fwd
使用左值调用,我们将再次Type&
获得v
. 这将绑定到非常量左值引用。如果我们用 const 左值调用它,我们会得到const Type&
,它只会绑定到 中的 const 左值引用参数Call
。
如果您Fwd
使用 xvalue 调用,我们将再次Type&&
获得v
. 这将不允许您调用采用非常量左值的函数,因为 xvalue 不能绑定到非常量左值引用。它可以绑定到 const 左值引用,因此如果Call
使用 a const&
,我们可以Fwd
使用 xvalue 调用。
如果您Fwd
使用纯右值调用,我们再次得到Type&&
,所以一切都像以前一样工作。您不能将临时值传递给采用非常量左值的函数,因此我们的转发函数同样会在尝试这样做时窒息。
通过右值引用
如果Call
通过右值引用获取它的值,那么它应该只在用户使用 xvalue 或 rvalue 参数时才可调用。
如果你Fwd
用左值调用,我们得到Type&
. 这不会绑定到右值引用参数,因此会导致编译错误。Aconst Type&
也不会绑定到右值引用参数,所以它仍然失败。Call
如果我们直接使用左值调用,这正是会发生的情况。
如果您Fwd
使用 xvalue 调用,我们会得到Type&&
,它有效(当然,cv 限定仍然很重要)。
使用纯右值也是如此。
标准::转发
std::forward 本身以类似的方式使用引用折叠规则,以便将传入的右值引用作为 xvalues(作为 xvalues 的函数返回值Type&&
)和传入的左值引用作为左值(返回Type&
)传递。