要求的参考折叠规则的简明解释: (1) A&&->A&, (2) A&&&->A&, (3) A&&&->A&, 和 (4) A&&&&->A&& [英] Concise explanation of reference collapsing rules requested: (1) A& & -> A& , (2) A& && -> A& , (3) A&& & -> A& , and (4) A&& && -> A&&

查看:53
本文介绍了要求的参考折叠规则的简明解释: (1) A&&->A&, (2) A&&&->A&, (3) A&&&->A&, 和 (4) A&&&&->A&&的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

来自链接:

<块引用>

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

虽然我可以做出有根据的猜测,但我想对这些引用折叠规则背后的基本原理进行简要说明.

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

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

解决方案

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

完美"转发意味着有效地转发参数,就像用户直接调用该函数一样(减去省略,转发会破坏).用户可以传递三种值:lvalues、xvalues和prvalues,接收位置可以通过三种方式取值:by value,by(可能是const)左值引用,和by(可能是const)右值参考.

考虑这个函数:

templatevoid Fwd(T &&v) { Call(std::forward(v));}

按价值

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

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

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

通过左值引用

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

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

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

如果你用纯右值调用 Fwd,我们再次得到 Type&&,所以一切都像以前一样工作.您不能将临时值传递给采用非常量左值的函数,因此我们的转发函数在尝试这样做时同样会卡住.

通过右值引用

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

如果您使用左值调用 Fwd,我们会得到 Type&.这不会绑定到右值引用参数,因此会导致编译错误.const Type& 也不会绑定到右值引用参数,因此它仍然失败.如果我们直接用左值调用 Call 就会发生这种情况.

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

使用纯右值也是如此.

标准::转发

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

The following link provides the 4 forms of reference collapsing (if I'm correct that these are the only 4 forms): http://thbecker.net/articles/rvalue_references/section_08.html.

From the link:

  1. A& & becomes A&
  2. A& && becomes A&
  3. A&& & becomes A&
  4. A&& && becomes A&&

Although I can make an educated guess, I would like a concise explanation for the rationale behind each of these reference-collapsing rules.

A related question, if I might: Are these reference-collapsing rules utilized in C++11 internally by such STL utilities such as std::move(), std::forward(), and the like, in typical real-world use cases? (Note: I'm specifically asking whether the reference-collapsing rules are utilized in C++11, as opposed to C++03 or earlier.)

I ask this related question because I am aware of such C++11 utilities as std::remove_reference, but I do not know if the reference-related utilities such as std::remove_reference are routinely used in C++11 to avoid need for the reference-collapsing rules, or whether they are used in conjunction with the reference-collapsing rules.

解决方案

The reference collapsing rules (save for A& & -> A&, which is C++98/03) exist for one reason: to allow perfect forwarding to work.

"Perfect" forwarding means to effectively forward parameters as if the user had called the function directly (minus elision, which is broken by forwarding). There are three kinds of values the user could pass: lvalues, xvalues, and prvalues, and there are three ways that the receiving location can take a value: by value, by (possibly const) lvalue reference, and by (possibly const) rvalue reference.

Consider this function:

template<class T>
void Fwd(T &&v) { Call(std::forward<T>(v)); }

By value

If Call takes its parameter by value, then a copy/move must happen into that parameter. Which one depends on what the incoming value is. If the incoming value is an lvalue, then it must copy the lvalue. If the incoming value is an rvalue (which collectively are xvalues and prvalues), then it must move from it.

If you call Fwd with an lvalue, C++'s type-deduction rules mean that T will be deduced as Type&, where Type is the type of the lvalue. Obviously if the lvalue is const, it will be deduced as const Type&. The reference collapsing rules mean that Type & && becomes Type & for v, an lvalue reference. Which is exactly what we need to call Call. Calling it with an lvalue reference will force a copy, exactly as if we had called it directly.

If you call Fwd with an rvalue (ie: a Type temporary expression or certain Type&& expressions), then T will be deduced as Type. The reference collapsing rules give us Type &&, which provokes a move/copy, which is almost exactly as if we had called it directly (minus elision).

By lvalue reference

If Call takes its value by lvalue reference, then it should only be callable when the user uses lvalue parameters. If it's a const-lvalue reference, then it can be callable by anything (lvalue, xvalue, prvalue).

If you call Fwd with an lvalue, we again get Type& as the type of v. This will bind to a non-const lvalue reference. If we call it with a const lvalue, we get const Type&, which will only bind to a const lvalue reference argument in Call.

If you call Fwd with an xvalue, we again get Type&& as the type of v. This will not allow you to call a function that takes a non-const lvalue, as an xvalue cannot bind to a non-const lvalue reference. It can bind to a const lvalue reference, so if Call used a const&, we could call Fwd with an xvalue.

If you call Fwd with a prvalue, we again get Type&&, so everything works as before. You cannot pass a temporary to a function that takes a non-const lvalue, so our forwarding function will likewise choke in the attempt to do so.

By rvalue reference

If Call takes its value by rvalue reference, then it should only be callable when the user uses xvalue or rvalue parameters.

If you call Fwd with an lvalue, we get Type&. This will not bind to an rvalue reference parameter, so a compile error results. A const Type& also won't bind to an rvalue reference parameter, so it still fails. And this is exactly what would happen if we called Call directly with an lvalue.

If you call Fwd with an xvalue, we get Type&&, which works (cv-qualification still matters of course).

The same goes for using a prvalue.

std::forward

std::forward itself uses reference collapsing rules in a similar way, so as to pass incoming rvalue references as xvalues (function return values that are Type&& are xvalues) and incoming lvalue references as lvalues (returning Type&).

这篇关于要求的参考折叠规则的简明解释: (1) A&amp;&amp;-&gt;A&amp;, (2) A&amp;&amp;&amp;-&gt;A&amp;, (3) A&amp;&amp;&amp;-&gt;A&amp;, 和 (4) A&amp;&amp;&amp;&amp;-&gt;A&amp;&amp;的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆