【问题标题】:What's the difference between "auto v = f()" and "auto&& v = f()"?“auto v = f()”和“auto&& v = f()”有什么区别?
【发布时间】:2017-02-27 00:31:42
【问题描述】:
#include <vector>

using namespace std;

vector<int> f()
{
    return{};
}

// Update: Below is not compilable
void g(vector<int>)
{}

// Update: Below is the my initial intent.
/*
void g(const vector<int>&)
{}
*/

void g(vector<int>&&)
{}

int main()
{
    auto       v1 = f();
    auto&& v2 = f();

    g(forward<vector<int>>(v1));
    g(forward<vector<int>>(v2));
}

C++11 是否保证g(forward&lt;vector&lt;int&gt;&gt;(v1)) 会调用f(vector&lt;int&gt;)f(const vector&lt;int&gt;&amp;)g(forward&lt;vector&lt;int&gt;&gt;(v2)) 会调用f(vector&lt;int&gt;&amp;&amp;)

【问题讨论】:

  • 这不能在我的机器上编译。对 g() 的调用是模棱两可的。所以问题实际上应该是为什么会发生这种情况。
  • 我强烈建议您恢复编辑,您的更改会使现有答案无效。或者也许在编辑中发布一个附录,而不更改原始代码
  • @M.M,按照您的建议更新了帖子。
  • 最后一段也可以进行编辑(以及类型更改,您写了f,您的意思是g

标签: c++ c++11 auto perfect-forwarding type-deduction


【解决方案1】:

区别在于v1 是一个向量,而v2 是一个对向量的右值引用。

重载g 你所做的方式是一个非常糟糕的主意。如果参数是 cv 非限定右值,则调用将是不明确的,因为两个 g 都可以接受具有身份转换序列的右值。但是,可以接受一个重载采用T&amp;,另一个采用T&amp;&amp;

如果您要转发f() 的值类别,请不要将其复制/移动到v1 中。这会破坏价值类别信息。 v1 永远是左值。

此外,您没有正确使用std::forwardv1v2 都将被强制转换为右值引用,并且在这种情况下在重载决议下的行为方式相同。

std::forward 的正确用法如下所示:

void g(vector<int>&);
void g(vector<int>&&);
int main() {
    auto&& v1 = function_returning_lvalue_vector();
    auto&& v2 = function_returning_rvalue_vector();
    g(forward<decltype(v1)>(v1));  // calls lvalue overload
    g(forward<decltype(v2)>(v2));  // calls rvalue overload
}

【讨论】:

  • v2 是一个右值引用 ...实际上不是,请参阅 M.M. 的回答
  • @zett42 为这个答案辩护-说“v2 is...”是模棱两可的; v2 实体是一个右值引用,但v2 表达式是一个左值。我同意第一段可以稍微澄清一下,因为对于不了解这种区别的人来说,这没有多大意义。
【解决方案2】:

有一个与返回值函数关联的对象,称为返回值f() 的返回值为vector&lt;int&gt;

在 C++11 和 C++14 中,f() 按值返回:

  • auto v1 = f(); 使用复制/移动构造函数从返回值初始化vector&lt;int&gt;,这将被称为v1。这是一个复制省略上下文。
  • auto&amp;&amp; v2 = f(); 使名称 v2 指定返回值,并延长返回值的生命周期。

如果编译器确实实现了复制省略,那么这两个代码具有相同的效果。从 C++17 开始,使用所谓的“保证复制省略”,这两个代码将是相同的。

“相同”是指除decltype(identifier) 的结果之外的所有方面都相同,如下所述。


您的两个g 调用之间没有区别。在这两种情况下,参数都是std::vector 类型的左值。 表达式 v1v2 不会“记住”它们最初是否是返回值对象。

std::forward 仅在给定模板参数时才有用,该参数是完美转发推导的结果(因此,可能是引用类型)。

建议使用decltypedecltype(identifier) 是一个特殊情况,在应用了auto 扣除之后,它确实回想起了 标识符 是如何声明的。

  • decltype(v1)vector&lt;int&gt;
  • decltype(v2)vector&lt;int&gt; &amp;&amp;

但现在我们有了:

  • std::forward&lt;decltype(v1)&gt;vector&lt;int&gt; &amp;&amp;
  • std::forward&lt;decltype(v2)&gt;vector&lt;int&gt; &amp;&amp;

所以你仍然没有区分g 的两种不同形式。

事实上,正如 cmets 中所指出的,根本不可能调用 g。每个电话都会模棱两可。在重载决议中,直接引用绑定是一种身份转换; T 类型的 xvalue 参数与参数 TT&amp;&amp; 一样匹配。 (类似地,T 类型的左值参数将与参数TT&amp; 一样匹配)。

有可能重载g 以获得左值与右值重载。但是您也需要对v1 的初始化进行更改:

void g(vector<int> const &) {}
void g(vector<int> &&) {}

// ...
auto const& v1 = f();
auto&& v2 = f();

g( std::forward<decltype(v1)>(v1) );
g( std::forward<decltype(v2)>(v2) );

【讨论】:

  • 到目前为止一切都很好,但是为什么对 g() 的调用是模棱两可的?如果我删除 forward() 就不再模棱两可了。那么第一个重载总是会被调用,正如预期的那样。
  • @zett42 类型为T 的右值与T 相等匹配为T&amp;&amp;
  • 但是 forward() 不应该返回一个左值引用吗?
  • @zett42 forward&lt;T&gt; 仅在 T 是左值引用类型时才给出左值。否则它会给出一个 xvalue
  • 我明白了,forward 在这里无法按预期工作,因为该类型已明确指定给 forward。所以它只是返回该类型的右值,就像任何常规函数一样。
猜你喜欢
  • 1970-01-01
  • 2020-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-18
  • 1970-01-01
  • 1970-01-01
  • 2019-04-18
相关资源
最近更新 更多