为什么这个函数在给定rvalue参数的情况下返回左值引用?

Tri*_*dle 13 c++ conditional-operator rvalue-reference c++11 universal-reference

以下定义了一个min函数

template <typename T, typename U>
constexpr auto
min(T&& t, U&& u) -> decltype(t < u ? t : u)
{
    return t < u ? t : u;
}
Run Code Online (Sandbox Code Playgroud)

有一个问题:它似乎写得完全合法

min(10, 20) = 0;
Run Code Online (Sandbox Code Playgroud)

这已经过Clang 3.5和g ++ 4.9的测试.

解决方案很简单,只是std::forward用来恢复参数的"rvalue-ness",即修改正文和decltype

t < u ? std::forward<T>(t) : std::forward<U>(u)
Run Code Online (Sandbox Code Playgroud)

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


鉴于我的转发和普遍引用,两者的理解tu演绎他们的论据类型,int&&当传递整数常量.但是,在正文中min,参数有名称,因此它们是左值.现在,条件运算符真正复杂规则发挥作用,但我认为相关的是:

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

因此,返回类型operator?:应该是int&&为好,应该不是吗?但是,(据我所知),Clang和g ++都min(int&&, int&&)返回了左值引用int&,因此允许我分配给结果.

很明显,我的理解存在差距,但我不确定我到底是什么.任何人都可以向我解释这里到底发生了什么吗?


编辑:

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

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

所以函数的返回值变为int&& &,由C++ 11的引用折叠规则变为普通int&(与我的预期相反int&&).

但是如果我们使用std::forward,我们将operator?:(后面)的第二个和第三个参数转换为rvalues - 特别是xvalues.因为xvalues仍然是glvalues(你在后面保持?),所以应用相同的条件运算符规则,并且我们得到相同类型和值类别的结果:即,int&&它是xvalue.

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

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

这一次,参考折叠给了我们int&& && = int&&,更重要的是,函数返回了一个xvalue.这使得分配给返回值是非法的,就像我们想要的那样.

Nia*_*all 7

问题可能在于decltype()规则.

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

template <typename T, typename U>
constexpr auto
min(T&& t, U&& u)
{
    return t < u ? t : u;
}
Run Code Online (Sandbox Code Playgroud)

并且允许编译器推导出它,返回类型是int.

使用decltype

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

取自cppreference.

由于涉及t和的表达式u是左值(它们是左值 - 命名为r值引用),因此返回值是左值引用.

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

正如您已经指出的那样,为了纠正这种情况,正确使用std::forward需求和返回类型将是预期的.


有关详细信息std::forward和参考折叠,请参见此处.