Consider possible implementation of std::apply:
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F &&f, Tuple &&t, std::index_sequence<I...>) 
{
    return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
}  // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply(F &&f, Tuple &&t) 
{
    return detail::apply_impl(
        std::forward<F>(f), std::forward<Tuple>(t),
        std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{});
}
Why when invoking the function(f) with tuple of parameters to pass(t) we don't need to perform std::forward on each element of the tuple std::get<I>(std::forward<Tuple>(t))... in the implementation?
                        
You do not need to
std::forwardeach element becausestd::getis 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::getwill give you aT &(lvalue) or aT &&(rvalue). See the various overload ofstd::get.A bit of details about
std::tupleandstd::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:
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::getalready take care of that by returning member with corresponding category.Back to a concrete example with
std::forward:In the first call of
f, the type ofawill beint&&becausetuplewill be forwarded as astd::tuple<int>&&, while in the second case its type will beint&becausetuplewill be forwarded as astd::tuple<int>&.