57

以下链接提供了 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 中被诸如 、 等 STL 实用程序内部std::move()使用?std::forward()(注意:我特别询问是否在 C++11 中使用了引用折叠规则,而不是在 C++03 或更早版本中使用。)

我问这个相关问题是因为我知道 C++ 11实用程序std::remove_reference如它们是否引用折叠规则一起使用。std::remove_reference

4

2 回答 2

41

引用折叠规则(保存为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&,其中Type是左值的类型。显然,如果左值是const,它将被推导出为const Type&。引用折叠规则意味着Type & &&变成Type &for v,一个左值引用。这正是我们需要调用Call的。用左值引用调用它会强制复制,就像我们直接调用它一样。

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

通过左值引用

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

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

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

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

通过右值引用

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

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

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

使用纯右值也是如此。

标准::转发

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

于 2012-12-05T15:34:06.480 回答
6

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

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

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

诸如remove_referenceis 之类的类型特征的使用实际上取决于您的需求;moveforward涵盖最随意的情况。

于 2012-12-05T15:31:06.783 回答