要求参考折叠规则的简要说明:(1)A && - > A&,(2)A &&& - > A&,(3)A &&& - > A&,和(4)A && && - > A &&

Dan*_*aum 39 c++ stl

以下链接提供了4种形式的引用折叠(如果我是正确的,这些是唯一的4种形式):http://thbecker.net/articles/rvalue_references/section_08.html.

从链接:

  1. A &&成为A&
  2. A &&&成为A&
  3. A &&&成为A&
  4. A && &&成为A &&

虽然我可以做出有根据的猜测,但我想简要解释这些参考折叠规则背后的基本原理.

一个相关的问题,如果可能的话:在典型的实际用例中,这些STL实用程序(例如,等)在C++ 11内部使用这些引用折叠规则吗?(注意:我特别询问C++ 11中是否使用了引用折叠规则,而不是C++ 03或更早版本.)std::move()std::forward()

我问这个相关的问题,因为我知道这样的C++ 11实用程序std::remove_reference,但我不知道是否与std::remove_referenceC++ 11中常规使用的引用相关的实用程序,以避免需要引用 - 折叠规则,或者它们是否参考折叠规则一起使用.

Nic*_*las 33

参考折叠规则(除了A& & -> A&,这是C++ 98/03)存在的原因之一是:允许完美的转发工作.

"完美"转发意味着有效地转发参数,就好像用户直接调用了函数一样(减去elision,它被转发打破).用户可以传递三种值:lvalues,xvalues和prvalues,接收位置可以通过三种方式获取值:by value,by(可能是const)左值引用,以及by(可能是const)rvalue参考.

考虑这个功能:

template<class T>
void Fwd(T &&v) { Call(std::forward<T>(v)); }
Run Code Online (Sandbox Code Playgroud)

按价值

如果Call按值获取其参数,则必须在该参数中进行复制/移动.哪一个取决于传入的值是什么.如果传入值是左值,则它必须复制左值.如果传入的值是一个rvalue(统称为xvalues和prvalues),那么它必须从它移动.

如果调用Fwd与左值,C++的类型推演规则意味着T会推导出Type&,这里Type是左值的类型.显然,如果左值是const,它将被推断为const Type&.参考崩溃规则意味着Type & &&变成Type &v,一个左值引用.这正是我们需要调用的内容Call.使用左值引用调用它将强制复制,就像我们直接调用它一样.

如果Fwd使用rvalue 调用(即:Type临时表达式或某些Type&&表达式),T则将推断为Type.引用折叠规则给了我们Type &&,它引发了一个移动/复制,这几乎就像我们直接调用它一样(减去elision).

通过左值参考

如果Call通过左值引用获取其值,则它应该仅在用户使用左值参数时可调用.如果它是一个const-lvalue引用,那么它可以被任何东西调用(lvalue,xvalue,prvalue).

如果你Fwd用左值调用,我们再次得到Type&它的类型v.这将绑定到非const左值引用.如果我们用const左值调用它,我们得到const Type&,它只会绑定到一个const左值引用参数Call.

如果你Fwd用xvalue 打电话,我们再次得到Type&&它的类型v.这会不会让你调用一个函数,它接受一个非const左值作为x值不能绑定到一个非const左值参考.它可以绑定到const lvalue引用,所以如果Call使用a const&,我们可以Fwd用xvalue 调用.

如果你Fwd用prvalue 打电话,我们再次得到Type&&,所以一切都像以前一样.你不能将临时函数传递给带有非常量左值的函数,因此我们的转发函数同样会尝试这样做.

按左值参考

如果Call通过右值引用获取其值,则只有在用户使用xvalue或rvalue参数时才能调用它.

如果你Fwd用左值调用,我们就会得到Type&.这不会绑定到右值引用参数,因此会产生编译错误.A const Type&也不会绑定到右值引用参数,因此它仍然失败.如果我们Call直接用左值调用,这正是会发生的事情.

如果你Fwd使用xvalue 打电话,我们会得到Type&&,这是有效的(cv-qualification仍然很重要).

使用prvalue也是如此.

的std ::前进

std :: forward本身以类似的方式使用引用折叠规则,以便将传入的右值引用作为xvalues(函数返回值为Type&&xvalues)和传入的左值引用传递为lvalues(返回Type&).

  • 正如我所考虑的,很明显,如果调用“Fwd”的参数是左值,则编译器不能*将“T”推断为“Type”,因为函数签名将是“Fwd” (类型 &amp;&amp;)`,并且编译器不允许将左值传递给接受右值引用的函数。下一个可能的选择是“T”是“Type &amp;”,它对应于函数签名“Fwd(Type &amp; &amp;&amp;)”,它(根据参考折叠规则)变成“Fwd(Type &amp;)”,它是一个可接受的函数签名,因此使用它。 (2认同)
  • (重新发布并进行了小修复):这部分不正确:“如果您使用 xvalue(即:某些 `Type&amp;&amp;` 表达式)调用 `Fwd`,则 `T` 将被推导为 `Type&amp;&amp;。`” 相反,`T ` 被推导为[作为非引用](https://godbolt.org/g/KjhoNy)。参数类型*是* `Type &amp;&amp;` 并且不需要引用折叠。对于纯右值(即任何右值)也是如此。 (2认同)

Som*_*ame 6

规则实际上非常简单.Rvalue reference是对某些临时值的引用,该临时值不会超出使用它的表达式 - 与lvalue reference持久化数据的引用相反.因此,如果您有对持久数据的引用,则无论您将其与哪些引用结合使用,实际引用的数据都是左值 - 这涵盖了前3个规则.第四条规则也是自然的 - 对右值引用的右值引用仍然是对非持久数据的引用,因此产生了右值引用.

是的,C++ 11实用程序依赖于这些规则,链接提供的实现与实际标题匹配:http://en.cppreference.com/w/cpp/utility/forward

是的,在使用std::movestd::forward实用程序时,正在应用折叠规则以及模板参数推导规则,就像链接中所解释的那样.

类型特征的使用remove_reference实际上取决于您的需求; moveforward为最随意的案件提供保险.

  • @DanNissenbaum 这是一个很好的观察。我认为,尽管起点是右值引用,但编译器会检查整个表达式以做出更有根据的决定。&amp; 和 &amp;&amp; 之间的关系就像“false”(&amp;) 和“true”(&amp;&amp;) 之间的逻辑“与”。 (3认同)
  • “左值”是一个表达式类别,不可能将“实际引用的数据”描述为左值。右值引用也可以引用非临时对象(例如“X&amp;&amp; y = std::move(x);”) (3认同)
  • 但是,在情况 3 中,您有 `A&amp;&amp; &amp;`,因此您从对*临时* 值 (`A&amp;&amp;`) 的引用“开始”,然后引用它 (`A&amp;&amp; &amp;`),所以不会这**不**属于“如果您有对持久数据的引用,无论您将其与其他什么引用结合在一起......”类别(因为您不是从对持久数据的引用开始的)数据)? (2认同)