You do not need to std::forward
each element because std::get
is overloaded for rvalue-reference and lvalue-reference of tuple.
std::forward<Tuple>(t)
will give you either a lvalue (Tuple &
) or an rvalue (Tuple &&
), and depending on what you get, std::get
will give you a T &
(lvalue) or a T &&
(rvalue). See the various overload of std::get
.
A bit of details about std::tuple
and std::get
-
As mentioned by StoryTeller, every member of a tuple is an lvalue, whether it has been constructed from an rvalue or a lvalue is of no relevance here:
double a{0.0};
auto t1 = std::make_tuple(int(), a);
auto t2 = std::make_tuple(int(), double());
The question is - Is the tuple an rvalue? If yes, you can move its member, if no, you have to do a copy, but std::get
already take care of that by returning member with corresponding category.
decltype(auto) a1 = std::get<0>(t1);
decltype(auto) a2 = std::get<0>(std::move(t1));
static_assert(std::is_same<decltype(a1), int&>{}, "");
static_assert(std::is_same<decltype(a2), int&&>{}, "");
Back to a concrete example with std::forward
:
template <typename Tuple>
void f(Tuple &&tuple) { // tuple is a forwarding reference
decltype(auto) a = std::get<0>(std::forward<Tuple>(tuple));
}
f(std::make_tuple(int())); // Call f<std::tuple<int>>(std::tuple<int>&&);
std::tuple<int> t1;
f(t1); // Call f<std::tuple<int>&>(std::tuple<int>&);
In the first call of f
, the type of a
will be int&&
because tuple
will be forwarded as a std::tuple<int>&&
, while in the second case its type will be int&
because tuple
will be forwarded as a std::tuple<int>&
.
get
std::tuple<int&&>&
and then afterget
we would perfect forward result element? would it work the same? – Incorrigible