The correct way to use decltype as return type

I very often see an 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 say that this is better correct:

 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 of all, decltype in this example should only output the return type, therefore (t + u) is the return type not (std :: forward (t) + std :: forward (u)), the second code generated by two ver, identicall and third decltype (u + t) are more direct and express precisely that the intentions of the programmer do not cause the guts of the implementation.

What is your opinion on this?

+4
source share
3 answers

In general, I can’t think of a reasonable use case where there will be a difference, but I think that you could find a case where the operation has different implementations for rvalue references and rvalues, and language rules do not dictate that the return type of various overloads should be the same (even if common sense defines it).

Thus, in the general case there will be no difference, and in case there are differences, well, these cases need special care and attention to 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 of situations where you would like to offer different overloads for rvalue links (consider some implementation of the list that operator+ offers as concatenation, then overloading with rvalue links can avoid the cost of copying by simply manipulating the pointers and leaving the argument lists empty) , but it would be completely confusing if the type of the result would depend on the l / rvalue-ness arguments.

+2
source

The first version is more correct, because it exactly corresponds to the fact that the body of the function will return. As already stated, there is no guarantee that

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

will be the same type as

 decltype(t + u) 

This may be a pretty angular case, but the “right” way is to use std :: forward.

+3
source

Inside decltype(t + u) variables t and u no longer have rvalue references, they will be considered as simple lvalue reference values, so you need aditional std::forward . (At least, as I understand it. Maybe I'm wrong.)

+2
source

All Articles