以下链接提供了4种形式的参考折叠(如果我正确的话,这是仅有的4种形式):http://thbecker.net/articles/rvalue_references/section_08.html。

来自链接:



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),以避免需要引用折叠规则,或者是否将它们与参考折叠规则。

评论

您可能会对Scott Meyers的这段视频感兴趣。

现在看。谢谢!

“典型的现实用例”-完美转发(我将其称为非常有用的现实用例)完全建立在那些参考折叠规则上(并且反过来可能是设计这些规则的实际原因,我认为)。通过引入一种新型的利用移动语义的引用,他们也有机会定义相应的折叠规则,以促进完美的转发,从而通过一个功能解决两个问题。

我认为A&&成为A&不是参考崩溃。

#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 ++的类型推导规则意味着将推导TType&,其中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::movestd::forward实用程序时,就像您在链接中所解释的那样。

remove_reference等类型特征的使用确实取决于您的需求; moveforward适用于大多数休闲情况。

评论


但是,在第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