为啥这个函数在给定右值参数的情况下返回一个左值引用?

Posted

技术标签:

【中文标题】为啥这个函数在给定右值参数的情况下返回一个左值引用?【英文标题】:Why does this function return an lvalue reference given rvalue arguments?为什么这个函数在给定右值参数的情况下返回一个左值引用? 【发布时间】:2014-12-08 21:30:35 【问题描述】:

min函数的以下定义

template <typename T, typename U>
constexpr auto
min(T&& t, U&& u) -> decltype(t < u ? t : u)

    return t < u ? t : u;

有一个问题:看起来写是完全合法的

min(10, 20) = 0;

这已经用 Clang 3.5 和 g++ 4.9 进行了测试。

解决方案很简单,只需使用std::forward 来恢复参数的“右值性”,即修改正文和decltype

t < u ? std::forward<T>(t) : std::forward<U>(u)

但是,我无法解释为什么第一个定义不会产生错误。


鉴于我对转发和通用引用的理解,tu 在传递整数文字时都将它们的参数类型推断为 int&amp;&amp;。但是,在min 的主体中,参数具有名称,因此它们是左值。现在,really complicated rules for the conditional operator come into play,但我认为相关的行是:

E2 [和] E3 都是相同类型的左值。在这种情况下,结果具有相同的类型和值类别。

因此operator?: 的返回类型也应该是int&amp;&amp;,不是吗?但是,(据我所知)Clang 和 g++ 都有 min(int&amp;&amp;, int&amp;&amp;) 返回左值引用 int&amp;,因此允许我分配结果。

显然,我的理解存在差距,但我不确定我到底错过了什么。谁能给我解释一下这里到底发生了什么?


编辑:

正如 Niall 正确指出的那样,这里的问题不在于条件运算符(它按预期返回类型为 int&amp;&amp; 的左值),而在于 decltypedecltype的规则说

如果表达式的值类别是左值,则decltype指定T&

所以函数的返回值变成了int&amp;&amp; &amp;,根据C++11的引用折叠规则变成了普通的int&amp;(与我预期的int&amp;&amp;相反)。

但是如果我们使用std::forward,我们会将operator?:(返回)的第二个和第三个参数转换为右值——特别是xvalues。由于 xvalues 仍然是 glvalues(你在后面跟上吗?),同样的条件运算符规则适用,我们得到相同类型和值类别的结果:即 int&amp;&amp; 这是一个 xvalue。

现在,当函数返回时,它会触发不同的decltype 规则:

如果表达式的值类别是xvalue,则decltype指定T&&

这一次,引用折叠给了我们int&amp;&amp; &amp;&amp; = int&amp;&amp;,更重要的是,函数返回了一个xvalue。这使得分配给返回值是非法的,正如我们所希望的那样。

【问题讨论】:

在正文中,我认为,您可以使用 std::move 明确告诉编译器您不再需要这些值。 @Garrappachc 如前所述,我知道解决方案(使用std::forward 而不是std::move,因此该函数仍适用于左值参数)。我很好奇为什么第一个(不正确的)定义做了它的作用。 如果删除了尾随返回类型(即auto min(...) ... )并允许编译器推断它,则问题可能与decltype() 规则有关,它返回int @Niall 我只是在编辑问题以提出类似的问题!谢谢:-) 条件表达式的类型不是右值ideone.com/ufbjIQ 【参考方案1】:

问题可能与decltype() 规则有关。

这是暗示的;如果删除了尾随返回类型

template <typename T, typename U>
constexpr auto
min(T&& t, U&& u)

    return t < u ? t : u;

并且允许编译器推断它,return type is int

decltype的使用

decltype ( expression ) ... 如果表达式的值类别是左值,则decltype指定T&

取自cppreference。

由于涉及tu 的表达式是左值(它们是左值 - 命名为右值引用),因此返回的是左值引用。

在这种情况下,它会导致可能修改文字的情况。使用"universal references"(或"forwarding references")和相关的引用折叠规则时,需要谨慎使用转发。

正如您已经指出的,要纠正这种情况,需要正确使用std::forward,并且返回类型将是预期的类型。


有关std::forward的更多详细信息和参考折叠可以找到here on SO。

【讨论】:

普通的auto 返回的情况有点误导,因为auto 从不推导出引用(这就是decltype(auto) 存在于C++14 中的原因)。但是在给定左值参数时,关于decltype 形成引用的部分正是我正在寻找的答案,谢谢:-) 没错,auto 会发生更多事情,这只是问题所在的提示

以上是关于为啥这个函数在给定右值参数的情况下返回一个左值引用?的主要内容,如果未能解决你的问题,请参考以下文章

C++11 中的左值引用和右值引用的区别

C11新特性右值引用&&

右值引用的参数传递疑问(C++0x)

重新理解C11的右值引用

具有适用于左值和右值的引用参数的 C++ 函数

函數返回值是引用类型,此函数当左值右值