最小化和完美转发

7

最小化算法通常表达为:

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&&绑定到int xvalue而创建临时变量?我认为xvalues是匿名rvalue引用,可以通过rvalue引用绑定而不必创建临时变量?您的修改后的代码似乎也是如此。返回类型是int&&,返回表达式是int xvalue。为什么编译器不再发出警告呢? - Johannes Schaub - litb
@Fred 所有规则 :) 这次,最后一个要点适用于创建临时变量。 - Johannes Schaub - litb
@Johannes:在第204页的中间,我发现“否则,如果T2是类类型且[...]初始化表达式是rvalue[...],那么引用将绑定到初始化表达式rvalue”。这里不需要临时对象。 - fredoverflow
1
int不是一个类类型。因此,对于int,会创建一个临时变量。我认为这是一个缺陷。 - Johannes Schaub - litb
@Johannes:啊,我没看到那个。是的,听起来像是一个缺陷。 - fredoverflow
显示剩余2条评论
2个回答

3

在我看来,你试图过分简化问题。不幸的是,完全正确地解决这个问题是相当棘手的。如果你还没有阅读 N2199,现在是一个好时机。右值引用仍在不断发展,因此其min和max的参考实现可能不再完全正确,但至少应该是一个相当不错的起点。警告:参考实现比你喜欢的要复杂得多!


看起来那个提案被拒绝了。你知道为什么吗? - jalf
@jalf:我怀疑人们看了参考实现后,惊恐地逃走了。 :-) - Jerry Coffin
@jalf 是的,请参见http://groups.google.de/group/comp.std.c++/browse_thread/thread/da71464ad690d506 - Johannes Schaub - litb
你试图过于简化问题了。-> 我更喜欢简单化的问题而不是复杂化的解决方案 :) - fredoverflow
@FredOverflow:别误会我的意思——我认为几乎每个人都同意N2199中提出的“解决方案”过于复杂。另一方面,它并不是出于任性而过度复杂——尽管该论文未被接受,但问题并未改变,并且没有添加任何内容到语言中以使得更简单的解决方案执行相同的任务。基本上,每个更简单的解决方案都会有问题和缺陷。 - Jerry Coffin
同意,但我的意图并不是编写最通用的min函数。我只想通过完美转发将我的帖子中的这两个函数统一起来,以更好地理解这种技术。也许我在原始帖子中应该更清楚地表达我的意图。 - fredoverflow

1

在这里,你不需要完美的转发,而是要返回 T& 或者 const T&,而不是 T&&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

标准的第14.8.2.1节[temp.deduct.call]描述了模板函数参数上发生的转换。decltype(x)可以很容易地是int&const int&,而返回类型不会经历这个过程。请查看标准中该部分的示例#3。 - Ben Voigt
我不同意,decltype(x)总是等同于T&&。你可以通过在min函数模板中插入以下代码行来自己验证:static_assert(std::is_same<T&&, decltype(x)>::value, "ouch"); - fredoverflow
@Johannes: 你是在跟Ben说话还是在跟我说话? - fredoverflow
@Fred 我是在向所有感到被称呼的人发言。我并没有特别针对任何人发言。 - Johannes Schaub - litb
@Johannes:我同意,最好不要使用任何引用来处理算术类型,但是在去除模板的情况下,我认为int&& min(int&& x, int&& y) { return std::forward<int>(y < x ? y : x); }没有什么严重的问题,除了可能会出现int&& x = min(1, 2);这种悬空引用的情况。但是除此之外,这个解决方案还是很好的,不是吗? - fredoverflow
显示剩余11条评论

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