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来恢复参数的“右值性”,即修改正文和decltypeto say

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

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


鉴于我对转发和通用引用的理解,两者都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,我们会将 (back) 的第二个和第三个参数operator?:转换为右值——特别是 xvalues。由于 xvalues 仍然是 glvalues(你在后面跟上吗?),同样的条件运算符规则适用,我们得到相同类型和值类别的结果:即 an int&&which 是 xvalue。

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

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

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

4

1 回答 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

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

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

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


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

于 2014-10-14T08:24:51.477 回答