来自链接:
A&&成为A&
A&&&成为A&
A &&&成为A&
A && &&成为A &&
尽管我可以做出有根据的猜测,但我想对这些引用折叠规则背后的原理进行简要说明。
相关的问题,是否可以:在典型的实际用例中,诸如
std::move()
,std::forward()
之类的STL实用程序是否在C ++ 11内部使用了这些引用折叠规则? (注意:我是专门询问是否在C ++ 11中使用了引用折叠规则,而不是C ++ 03或更早的版本。)我问这个相关问题,因为我知道诸如
std::remove_reference
之类的C ++ 11实用程序,但是我不知道是否在C ++ 11中常规使用了与引用相关的实用程序(例如std::remove_reference
),以避免需要引用折叠规则,或者是否将它们与参考折叠规则。#1 楼
存在以下参考折叠规则(保存为A& & -> A&
,它是C ++ 98/03),其原因之一是:允许完美的转发工作。用户直接调用了该函数(减去省略号,这被转发破坏了)。用户可以传递三种值:lvalues,xvalues和prvalues,接收位置可以采用三种方式获取值:按值,按(可能是const)左值引用和按(可能是const)右值参考。请考虑以下功能:
template<class T>
void Fwd(T &&v) { Call(std::forward<T>(v)); }
按值
如果
Call
以值作为参数,则必须在该参数中进行复制/移动。哪一个取决于传入的值是什么。如果传入值是左值,则它必须复制左值。如果传入值是右值(共同是x值和pr值),则必须从它移出。如果使用左值调用
Fwd
,则C ++的类型推导规则意味着将推导T
如Type&
,其中Type
是左值的类型。显然,如果左值为const
,则将其推导出为const Type&
。引用折叠规则意味着Type & &&
成为Type &
(左值引用)的v
。正是我们需要调用Call
。用左值引用调用它会强制执行一个副本,就像我们直接调用它一样。将推导为Fwd
。参考折叠规则为我们提供了Type
,它引发了移动/复制,几乎就像我们直接调用它一样(减去省略号)。通过左值引用
如果
Type&&
通过左值引用获取其值,则仅当用户使用左值参数时才可以调用它。如果它是const-左值引用,则可以用任何东西(左值,左值,右值)调用。如果用左值调用
T
,我们将再次获得Type
作为Type &&
的类型。这将绑定到非常量左值引用。如果使用const左值调用它,则会得到Call
,它仅绑定到Fwd
中的const左值引用参数。如果使用xvalue调用
Type&
,我们将再次获得v
作为const Type&
。这将不允许您调用采用非常量左值的函数,因为x值无法绑定到非常量左值引用。它可以绑定到const左值引用,因此,如果Call
使用了Fwd
,我们可以使用xvalue来调用Type&&
。如果您使用prvalue来调用
v
,我们将再次得到Call
,所以一切都像以前一样。您不能将临时变量传递给采用非常量左值的函数,因此我们的转发函数也会同样尝试这样做。通过右值引用
如果
const&
通过右值引用获取其值,然后仅当用户使用xvalue或rvalue参数时才可以调用它。如果使用左值调用
Fwd
,我们将得到Fwd
。这不会绑定到右值引用参数,因此会产生编译错误。一个Type&&
也不会绑定到右值参考参数,因此它仍然失败。如果我们直接用一个左值调用Call
,就会发生这种情况。如果用一个x值调用
Fwd
,我们会得到Type&
,它可以工作(当然,cv资格仍然很重要)。 /> 使用prvalue也是一样。
std :: forward
std :: forward本身以类似的方式使用引用折叠规则,以便将传入的rvalue引用作为xvalue(函数返回值
const Type&
是xvalue)传递,并将传入的lvalue引用作为lvalues(返回Call
)。评论
极好的答案。我对此感到困惑:如果您用左值调用Fwd,则C ++的类型推导规则意味着T将被推导为Type&。也许我缺乏理解超出了评论的范围,我需要对此进行更多研究(或发布另一个问题)。但是,我不明白为什么类型推导会选择T作为Type&而不是Type。
–丹·尼森鲍姆(Dan Nissenbaum)
2012年12月5日15:59
@DanNissenbaum:有几个原因,但是最简单的是最明显的:它不会复制值(实际上可能是不可复制的)。
–尼科尔·波拉斯(Nicol Bolas)
2012年12月5日在16:34
如果将T推导为Type,那么Fwd()的参数类型将是Type &&,对吗?我想那是我不明白的。
–丹·尼森鲍姆(Dan Nissenbaum)
2012年12月5日在16:49
@DanNissenbaum:然后转发不是完美的。如果我将左值传递给Fwd,则Fwd需要将左值传递给Call。 Fwd将Type &&转发为xvalue,而不是lvalue。因此,转发并不完美。同样,该规则是C ++ 98的一部分,其中不存在右值引用。
–尼科尔·波拉斯(Nicol Bolas)
2012-12-5 17:09
如我所料,很明显,如果调用Fwd的参数为左值,则编译器无法将T推定为Type,因为函数签名将为Fwd(Type &&),并且编译器不允许将左值传递给接受右值引用的函数。下一个可能的选择是T是Type&,它对应于函数签名Fwd(Type&&&),根据参考折叠规则,该函数签名变为Fwd(Type&),它是可接受的函数签名,因此可以使用。
–丹·尼森鲍姆(Dan Nissenbaum)
2012年12月5日18:35
#2 楼
规则实际上很简单。与引用持久数据的Rvalue reference
相反,lvalue reference
是对某些临时值的引用,该临时值不会超出使用它的表达式。因此,如果您有对持久数据的引用,则无论您将其与其他哪些引用结合使用,实际引用的数据都是左值-这涵盖了前3条规则。第四条规则也是很自然的-对右值引用的右值引用仍然是对非持久数据的引用,因此会产生右值引用。是的,C ++ 11实用程序依赖于这些规则,您的链接提供的实现与真实标头匹配:http://en.cppreference.com/w/cpp/utility/forward
是的,正在应用折叠规则以及模板参数推导规则当使用
std::move
和std::forward
实用程序时,就像您在链接中所解释的那样。remove_reference
等类型特征的使用确实取决于您的需求; move
和forward
适用于大多数休闲情况。评论
但是,在第3种情况下,您拥有A &&&,因此您先引用一个临时值(A &&),然后再引用一个临时值(A &&&),所以这不是不在类别中“如果您有对持久性数据的引用,那么无论将其与...结合使用什么其他引用”(因为您不从对持久性数据的引用开始)?
–丹·尼森鲍姆(Dan Nissenbaum)
2012-12-5 17:59
@DanNissenbaum,这是一个很好的观察。我想,尽管起点是作为右值引用,但编译器会检查整个表达式以做出更有根据的决定。 &和&&&之间的关系就像一个逻辑,false(&)和true(&&)之间。
– SomeWittyUsername
2012年12月5日18:06
“左值”是一个表达式类别,无法将“实际参考数据”描述为左值。右值引用也可以引用非临时对象(例如X && y = std :: move(x);)
– M.M
17年4月4日在21:16
评论
您可能会对Scott Meyers的这段视频感兴趣。现在看。谢谢!
“典型的现实用例”-完美转发(我将其称为非常有用的现实用例)完全建立在那些参考折叠规则上(并且反过来可能是设计这些规则的实际原因,我认为)。通过引入一种新型的利用移动语义的引用,他们也有机会定义相应的折叠规则,以促进完美的转发,从而通过一个功能解决两个问题。
我认为A&&成为A&不是参考崩溃。