【问题标题】:min and perfect forwarding最小和完美转发
【发布时间】:2010-06-22 17:28:49
【问题描述】:

min 算法通常是这样表示的:

template <typename T>
const T& min(const T& x, const T& y)
{
    return y < x ? y : x;
}

但是,这不允许min(a, b) = 0 形式的构造。您可以通过额外的重载来实现:

template <typename T>
T& min(T& x, T& y)
{
    return y < x ? y : x;
}

我想做的是通过完美转发统一这两个重载:

template <typename T>
T&& min(T&& x, T&& y)
{
    return y < x ? std::forward<T>(y) : std::forward<T>(x);
}

但是,g++ 4.5.0 向min(2, 4) 发出警告,我返回对临时的引用。我是不是做错了什么?


好的,我明白了。问题在于条件运算符。在我的第一个解决方案中,如果我调用 min(2, 4),条件运算符会看到一个 xvalue,因此会从转发的 x 移动以生成一个临时对象。当然,通过引用返回它是危险的!如果我分别转发整个表达式而不是 xy,编译器就不会再抱怨了:

template <typename T>
T&& min(T&& x, T&& y)
{
    return std::forward<T>(y < x ? y : x);
}

好的,我摆脱了算术类型的引用:)

#include <type_traits>

template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type
min(T x, T y)
{
    return y < x ? y : x;
}

template <typename T>
typename std::enable_if<!std::is_arithmetic<T>::value, T&&>::type
min(T&& x, T&& y)
{
    return std::forward<T>(y < x ? y : x);
}

【问题讨论】:

  • 嗯,在我看来,最后一个示例也根据 FCD 创建了一个临时文件。但这似乎很奇怪:它似乎创建了将int&amp;&amp; 绑定到int xvalue 的临时对象?!我认为 xvalues 是匿名右值引用,可以由右值引用绑定而无需创建临时对象?您修改后的代码似乎也发生了同样的情况,不是吗?返回类型为int&amp;&amp;,返回表达式为int xvalue。为什么编译器不再警告它?
  • 如果我正确阅读了8.5.3,它会说这会在将引用绑定到 xvalue 时创建一个 int 临时变量:int x = 0; int &amp;&amp;rx = (int&amp;&amp;)x; 同样在使用 std::move 时。我认为这不是目的。
  • @Johannes:你具体说的是 8.5.3 的哪一部分?
  • @Fred 所有规则 :) 这一次,最后一个子弹适用,它创建了一个临时的。
  • int 不是类类型。所以为int 创建了一个临时的。我认为这是一个缺陷。

标签: c++ templates c++11 forwarding rvalue-reference


【解决方案1】:

在我看来,您试图将问题过于简单化。不幸的是,让它完全正确绝对不是微不足道的。如果您还没有阅读N2199,那么现在是阅读的好时机。右值引用继续发展,因此它的 min 和 max 的引用实现可能不再完全正确,但它至少应该是一个相当不错的起点。警告:参考实现比你想象的要复杂很多

【讨论】:

  • 该提案似乎被拒绝了。知道为什么吗?
  • @jalf:我怀疑人们看了一眼参考实现,然后吓得尖叫着跑开了。 :-)
  • “你试图把问题过分简单化” -> 我更喜欢过于简单化的问题而不是过于复杂的解决方案:)
  • @FredOverflow:不要误会我的意思——我想几乎每个人都同意 N2199 中提出的“解决方案”过于复杂。 OTOH,它并不过分复杂,只是出于反常——尽管论文没有被接受,问题没有改变,语言中没有添加任何内容来让更简单的解决方案完成同样的任务。基本上每个更简单的解决方案都会有问题和缺点。
【解决方案2】:

您不希望完美转发,在这里,您希望返回T&amp;const T&amp; 而永远不要返回T&amp;&amp;std::forward 旨在将您的一个参数传递给另一个函数,而不是用于返回值。

我想你想要的是:

template <typename T>
min(T&& x, T&& y) -> decltype(x)
{
    return y < x ? y : x;
}

编辑以避免悬空引用问题:

template <typename T>
struct dedangle { typedef T type; }

template <typename T>
struct dedangle<const T&> { typedef T type; }

template <typename T, typename U>
min(T&& x, U&& y) -> dedangle<decltype(0?y:x)>::type
{
    return y < x ? y : x;
}

// dedangle is re-usable by max, etc, so its cost is amortized

【讨论】:

  • 我不确定decltype(x) 是哪种类型,但它要么是T,这根本不是我想要的,要么是T&amp;&amp;,这是你说我应该的'不行。对吗?
  • 标准的第 14.8.2.1 节 [temp.deduct.call] 描述了模板函数的参数上发生的转换。 decltype(x) 可以很容易地成为 int&amp;const int&amp;,而返回类型不经过此过程。查看标准该部分中的示例 #3。
  • 我不同意,decltype(x) 总是与T&amp;&amp; 相同。您可以通过将行 static_assert(std::is_same&lt;T&amp;&amp;, decltype(x)&gt;::value, "ouch"); 插入到 min 函数模板中来说服自己。
  • 所以因为转换是在T 上,而不是T&amp;&amp; x 参数上,它到底会影响返回类型吗?如果您继续返回 T&amp;&amp; 但省略 std::forward 怎么办?
  • 那行不通,因为如果我调用min(2, 4),那么T 就是int&amp;&amp;,并且由于引用折叠规则,T&amp;&amp; 也是int&amp;&amp;。所以我会尝试将int&amp;&amp; 绑定到y &lt; x ? y : x,这是一个左值(因为 y 和 x 和每个形式参数一样都是左值),但右值引用不绑定到左值。
猜你喜欢
  • 2015-09-07
  • 2015-04-11
  • 1970-01-01
  • 1970-01-01
  • 2012-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-12
相关资源
最近更新 更多