要求的参考折叠规则的简明解释:(1) A& & -> A& , (2) A& && -> & , (3) && &a

Posted

技术标签:

【中文标题】要求的参考折叠规则的简明解释:(1) A& & -> A& , (2) A& && -> & , (3) && & -> & , & (4A && && -> &&【英文标题】:Concise explanation of reference collapsing rules requested: (1) A& & -> A& , (2) A& && -> A& , (3) A&& & -> A& , and (4) A&& && -> A&& 【发布时间】:2012-12-05 14:45:25 【问题描述】:

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

来自链接:

    A&&变成A& A& && 变成 A& A&& & 变成 A& A&& && 变为 A&&

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

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

我问这个相关问题是因为我知道像std::remove_reference 这样的 C++11 实用程序,但我不知道像 std::remove_reference 这样的引用相关实用程序是否经常在 C++11 中使用到 避免需要引用折叠规则,或者是否将它们引用折叠规则结合使用。

【问题讨论】:

您可能会对 Scott Meyers 的 this video 感兴趣。 现在看。谢谢! “典型的现实世界用例” - 完美转发(我称之为非常有用的现实世界用例)完全建立在那些参考折叠规则之上(并且可能我认为,反过来又是设计这些规则的实际原因)。通过引入一种利用移动语义的新型引用,他们也有机会定义相应的折叠规则以促进完美转发,从而用一个特征解决两个问题。 我认为A& & becomes 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 按值获取其参数,则必须在该参数中进行复制/移动。哪一个取决于传入的值是什么。如果传入的值是左值,那么它必须复制左值。如果传入的值是一个右值(它们统称为 xvalues 和 prvalues),那么它必须从它移开。

如果你用左值调用Fwd,C++ 的类型推导规则意味着T 将被推导出为Type&amp;,其中Type 是左值的类型。显然,如果左值为const,则推导出为const Type&amp;。引用折叠规则意味着Type &amp; &amp;&amp; 变为Type &amp; 用于v,一个左值引用。这正是我们需要调用的Call。用左值引用调用它会强制复制,就像我们直接调用它一样。

如果您使用右值调用Fwd(即:Type 临时表达式或某些Type&amp;&amp; 表达式),则T 将被推断为Type。引用折叠规则给了我们Type &amp;&amp;,它引发了移动/复制,这几乎就像我们直接调用它一样(减去省略号)。

通过左值引用

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

如果您使用左值调用Fwd,我们将再次获得Type&amp; 作为v 的类型。这将绑定到非常量左值引用。如果我们用 const 左值调用它,我们会得到 const Type&amp;,它只会绑定到 Call 中的 const 左值引用参数。

如果您使用 xvalue 调用 Fwd,我们将再次获得 Type&amp;&amp; 作为 v 的类型。这将允许您调用采用非常量左值的函数,因为 xvalue 不能绑定到非常量左值引用。它可以绑定到 const 左值引用,因此如果 Call 使用了 const&amp;,我们可以使用 xvalue 调用 Fwd

如果您使用纯右值调用Fwd,我们将再次得到Type&amp;&amp;,因此一切正常。您不能将临时值传递给采用非常量左值的函数,因此我们的转发函数同样会在尝试这样做时窒息。

通过右值引用

如果Call 通过右值引用获取它的值,那么它应该只有在用户使用 xvalue 或 rvalue 参数时才可调用。

如果你用左值调用Fwd,我们得到Type&amp;。这不会绑定到右值引用参数,因此会导致编译错误。 const Type&amp; 也不会绑定到右值引用参数,所以它仍然失败。如果我们直接使用左值调用Call,这正是会发生的情况。

如果您使用 xvalue 调用 Fwd,我们会得到 Type&amp;&amp;,它有效(当然,cv 限定仍然很重要)。

使用纯右值也是如此。

std::forward

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

【讨论】:

优秀的答案。我对此很感兴趣:If you call Fwd with an lvalue, C++'s type-deduction rules mean that T will be deduced as Type&amp;。也许我缺乏理解超出了评论的范围,我需要更多地研究这个(或发布另一个问题)。但是,我不明白为什么类型推导会选择T作为Type&amp;而不是Type @DanNissenbaum:有几个原因,但最简单的也是最明显的:它不会复制值(实际上可能无法复制)。 如果T 被推断为Type,那么Fwd()参数类型 将是Type&amp;&amp;,对吧?我想这就是我不明白的。 @DanNissenbaum:然后转发就不会完美了。如果我将左值传递给FwdFwd 需要将左值传递给CallFwdType&amp;&amp; 转发为 xvalue,而不是左值。因此,转发并不完美。此外,该规则是 C++98 的一部分,其中不存在右值引用。 如我所想,很明显编译器不会T推断为Type,如果调用Fwd的参数是左值,因为函数签名将是Fwd(Type &amp;&amp;),并且编译器不允许将左值传递给接受右值引用的函数。下一个可能的选择是TType &amp;,对应一个函数签名Fwd(Type &amp; &amp;&amp;),(根据引用折叠规则)变成Fwd(Type &amp;),这是一个可接受的函数签名,所以使用。 【参考方案2】:

规则其实很简单。 Rvalue reference 是对某个临时值的引用,该临时值不会在使用它的表达式之外持续存在 - 与引用持久数据的 lvalue reference 形成对比。因此,如果您有对持久数据的引用,无论您将它与其他什么引用结合起来,实际引用的数据都是左值 - 这涵盖了前 3 条规则。第 4 条规则也很自然——对右值引用的右值引用仍然是对非持久数据的引用,因此产生了右值引用。

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

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

remove_reference 等类型特征的使用确实取决于您的需求; moveforward 覆盖最休闲的情况。

【讨论】:

但是,在情况 3 中,您有 A&amp;&amp; &amp;,因此您“开始”引用一个 临时 值 (A&amp;&amp;),然后取一个引用它 (A&amp;&amp; &amp;),那么这个 not 不是属于“if you have a reference to a persisting data, no matter what other references you combine it with...”类别吗(因为您没有从对持久数据的引用开始)? @DanNissenbaum 这是一个很好的观察。我想尽管起点是右值引用,但编译器会检查整个表达式以做出更有根据的决定。 & 和 && 之间的关系就像false(&) 和true(&&) 之间的逻辑and “左值”是一个表达式类别,不可能将“实际引用的数据”描述为左值。右值引用也可以引用非临时对象(例如X&amp;&amp; y = std::move(x);

以上是关于要求的参考折叠规则的简明解释:(1) A& & -> A& , (2) A& && -> & , (3) && &a的主要内容,如果未能解决你的问题,请参考以下文章

Python中 sys.argv[]的用法简明解释

Python中 sys.argv[]的用法简明解释

Python中 sys.argv[]的用法简明解释

HashSetLinkedHashSetTreeSet 简明解释

Python中 sys.argv[]的用法简明解释

"VPN"是啥意思请简明解释一下!