views:

184

answers:

2

The min algorithm is normally expressed like this:

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

However, this does not allow constructs of the form min(a, b) = 0. You can achieve that with an additional overload:

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

What I would like to do is unify these two overloads via perfect forwarding:

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

However, g++ 4.5.0 spits out a warning for min(2, 4) that I return a reference to a temporary. Did I do something wrong?


Okay, I get it. The problem is with the conditional operator. In my first solution, if I call min(2, 4) the conditional operator sees an xvalue and thus moves from the forwarded x to produce a temporary object. Of course it would be dangerous to return that by reference! If I forward the whole expression instead of x and y seperately, the compiler does not complain anymore:

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

Okay, I got rid of the references for arithmetic types :)

#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);
}
+1  A: 

You don't want perfect forwarding, here, you want to return either T& or const T& and never T&&. std::forward is designed for passing one of your parameters along to another function, not for return values.

I think what you want is:

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

EDIT to avoid dangling reference problem:

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
Ben Voigt
FredOverflow
Ben Voigt
I disagree, `decltype(x)` is always the same as `T` into the `min` function template.
FredOverflow
Ben Voigt
FredOverflow
Johannes Schaub - litb
@Johannes: Are you addressing Ben or me?
FredOverflow
@Johannes: Would it help to use `decltype((x))` or `decltype(0?y:x)` ?
Ben Voigt
@Fred i'm addressing whoever feels addressed. I was not addressing anyone in particular.
Johannes Schaub - litb
@Ben then the return type would be `U min(1, x);` because of the inconsistent deduction for `T` on both parameters.
Johannes Schaub - litb
@Johannes: I agree, it would be better to handle arithmetic types without any references whatsoever, but getting rid of the templates mentally, I don't see any serious problems with `int }`, except for the possibility to say `int` where x is a dangling reference. But besides that, the solution is fine, isn't it?
FredOverflow
Ben Voigt
@Ben: The dangling reference problem is caused by returning a reference, but I see no way around it here. The traditional C++98 `min` has the exact same problem.
FredOverflow
@Fred: By using a helper template, it's easy to return values when the inputs are const references, and references when the inputs are non-const references.
Ben Voigt
@Ben: I know, playing with it right now :) BTW that's why I opened a topic on enable_if a couple of minutes ago :) Will post my code here as soon as I figured it out.
FredOverflow
@Ben: Oh, you mean return values even for class types. No, that's too inefficient for me, I'd rather live with the dangling reference problem :) Personally, I have never bound an rvalue to a local reference variable...
FredOverflow
+3  A: 

It looks to me like you're trying to oversimplify the problem. Unfortunately, getting it entirely correct is decidedly non-trivial. If you haven't read N2199, now would be a good time to do so. Rvalue references continue to evolve, so its reference implementation of min and max probably isn't exactly right anymore, but it should at least be a pretty decent starting point. Warning: the reference implementation is a lot more complex than you're going to like!

Jerry Coffin
Looks like that proposal was rejected. Any idea why?
jalf
@jalf: I suspect people glanced at the reference implementation, and ran away screaming in terror. :-)
Jerry Coffin
@Jerry: sounds plausible :)
jalf
@jalf yep, see http://groups.google.de/group/comp.std.c++/browse_thread/thread/da71464ad690d506
Johannes Schaub - litb
"you're trying to oversimplify the problem" -> I prefer oversimplified problems to overcomplicated solutions :)
FredOverflow
@FredOverflow: don't get me wrong -- I think just about everybody agrees that the "solution" presented in N2199 was/is excessively complex. OTOH, it's not overly complex just out of perversity -- though the paper wasn't accepted, the problem hasn't changed, and nothing has been added to the language to let a lot simpler solution do the same task. Essentially every simpler solution *will* have problems and shortcomings.
Jerry Coffin
Agreed, but my intent was not to write the most general min function. All I wanted was to unify the two functions in my post via perfect forwarding to understand the technique better. Maybe I should have been clearer in my original post about my intentions.
FredOverflow