为什么这个函数针对rvalue参数返回一个lvalue引用?

13
以下是一个 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 来恢复参数的“rvalue-ness”,即修改主体和 decltype 以说
t < u ? std::forward<T>(t) : std::forward<U>(u)

然而,我无法解释为什么第一个定义不会产生错误。
根据我对转发和通用引用的理解,当传递整数字面量时,tu都推断其参数类型为int&&。然而,在min函数体内,参数具有名称,因此它们是左值。现在,条件运算符的真正复杂规则开始发挥作用,但我认为相关行是:

  • E2和E3都是相同类型的glvalue。在这种情况下,结果具有相同的类型和值类别。
因此,operator?:的返回类型也应该是int&&,不是吗?然而,(据我所知)Clang和g++都使min(int&&, int&&)返回一个左值引用int&,从而允许我将其赋值给结果。

显然我对此事的理解存在一些漏洞,但我不确定具体缺少什么。有人能向我解释这里到底发生了什么吗?


编辑:

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

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

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

但是,如果我们使用std::forward,我们将operator?:的第二个和第三个参数(重新)转换为右值 - 具体来说是xvalue。由于xvalue仍然是glvalue(你还跟上了吗?),因此同样的条件运算符规则适用,并且我们得到相同类型和值类别的结果:即一个int&&,它是一个xvalue。

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

如果表达式的值类别是xvalue,那么decltype指定为T&&。这次,引用折叠给了我们int&& && = int&&,更重要的是函数返回一个xvalue。这使得对返回值进行赋值是非法的,正如我们所希望的那样。

在函数体中,我认为你可以使用std::move来明确告诉编译器你不再需要这些值了。 - Garrappachc
正如@Garrappachc所指出的,我知道解决方案(即使用std::forward而不是std::move,以便函数仍然可以处理左值参数)。我很好奇第一个(错误的)定义为什么会产生这样的结果。 - Tristan Brindle
如果移除尾部返回类型(即auto min(...) { ... }),并允许编译器推断,那么问题可能出在decltype()规则上,它会返回int - Niall
@Niall 我刚刚在编辑问题,想问类似的事情!谢谢 :-) - Tristan Brindle
条件表达式的类型不是rvalue。http://ideone.com/ufbjIQ - user657267
1个回答

7
可能问题出现在decltype()规则上。 这一点有所提示;如果移除尾随返回类型。
template <typename T, typename U>
constexpr auto
min(T&& t, U&& u)
{
    return t < u ? t : u;
}

如果编译器允许推导出它,返回类型是int

使用decltype

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

引用自cppreference

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

在这种情况下,可能会导致字面量被修改。使用“通用引用”(或“转发引用”)和相关的引用折叠规则时需要注意谨慎使用转发。

正如您已经注意到的那样,在使用std::forward的正确用法的情况下,返回类型将是预期的类型以纠正这种情况。


有关std::forward和引用折叠的更多详细信息,请参见此处的SO


普通的auto返回情况有点误导人,因为auto从不推断引用(这就是为什么C++14中存在decltype(auto)的原因)。但是关于decltype在给定左值参数时形成引用的部分正是我正在寻找的答案,谢谢 :-) - Tristan Brindle
真正发生了很多事情,auto只是指出问题可能出现的提示。 - Niall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接