For option 1, you need to use two template parameters to enable forwarding to happen when the two arguments have different value categories. It should be
template <typename T, typename U>
decltype(auto) f(T&& a, U&& b)
{
return a > b ? std::forward<T>(a) : std::forward<U>(b);
}
What actually gets returned here is pretty tricky to work out. First you need to know the value categories of a
and b
, and what T
and U
get deduced as. Then whatever pairing you choose goes through the very complicated rules for the ternary operator. Finally, the output of that goes through the decltype
rules to give you the actual return type of the function.
I did actually sit down and work it all out once, with a little help from SO. To the best of my recollection, it goes like this: the result will be a mutable lvalue reference if and only if a
and b
are lvalue references of compatible types; the result will be a const lvalue reference if one of a
and b
is a const lvalue reference and the other is any sort of reference; otherwise f
will return a new value.
In other words, it does the right thing for any given pair of arguments -- probably because somebody sat down and wrote the rules exactly so that it would do the right thing in all cases.
Option 2 is doing to do exactly the same as option 1, except that the decltype
rules don't come in to play, and the function will always return a new value -- plain auto
never deduces to a reference.
I can't think of an option 3 that would work RVO-wise, since as you say you're using the arguments.
All in all, I think (1) is the answer you're looking for.