Question

I very often see example of this form:

template <typename T, typename U>
auto add(T&& t, U&& u) -> decltype(std::forward<T>(t) + std::forward<U>(u))
{
    return std::forward<T>(t) + std::forward<U>(u);
}  

but I dare to say that this is better more correct way:

template <typename T, typename U>
auto add(T&& t, U&& u) -> decltype(t + u)//no forwarding here
{
    return std::forward<T>(t) + std::forward<U>(u);
}

Why? First and foremost, decltype in this example has only to deduce return type so (t + u) is the return type not (std::forward(t) + std::forward(u)), second code generated by two ver is identicall, and third decltype(u + t) is more direct and expresses exactly what the intentions of a programmer are without bringing out "guts" of implementation.

What is your opinion on this subject?

Was it helpful?

Solution

In general, I cannot think of a sensible use case where there will be a difference, but I guess that you could find some case where the operation has different implementations for rvalue-references and rvalues, and the language rules do not dictate that the return type of the different overloads has to be the same (even if common sense would dictate it).

So, in the general case, there will be no difference, and in the case where there are differences, well, those cases need extra care and attention for much worse problems than the template itself...

// sick corner case:
struct type {};
int operator+( type&& lhs, type&& rhs );
double operator+( type const & lhs, type const & rhs );

I can think on situations where you would like to offer different overloads for rvalue-references (consider some implementation of a list that offers operator+ as concatenation, then an overload with rvalue references could avoid the cost of copying by just mangling with the pointers and leaving the argument lists empty), but it would be utterly confusing if the type of the result would depend on the l/rvalue-ness of the arguments.

OTHER TIPS

The first version is more correct because it matches exactly what the function body is going to return. As already pointed out, there is simply no guarantee that

decltype(std::forward<T>(t) + std::forward<U>(u))

will be the same type as

decltype(t + u)

Arguably, this is quite a corner case, but the "correct" way is to use std::forward.

Within decltype(t + u), the variables t and u are already no rvalue references anymore, they'd get treated as simple lvalue references, hence you need the aditional std::forward. (Atleast, that's how I understand it. Could be wrong though.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top