Apply a function to each element of a tuple ("map" a tuple)
transform, or map ?

I wouldn't name this function transform, because it's very different from that stl algorithm. std::transform can be used to modify a range in place, and can be called with either unary or binary operations. What you implement is generally called map and has its roots in functional languages; so I would call it tuple_map, for instance. Then the convention is to put the function first in the argument list, and the sequence second.

Or, you could try to implement transform, but then it gets tricky, because there are three "semantic inputs" to be considered: the tuple, its elements and the function signature. I haven't had the time to consider it thoroughly, but I believe that using decltype(fn(std::get&lt;0&gt;(tuple))) as a semantic clue is a good approximation. If it's void, you know that the semantic is at the tuple level; if it's an lvalue reference, you use std::tie to return a tuple of references; if it's an rvalue reference, std::forward_as_tuple is the most adapted; and if it's a value, you can return a new tuple. 

A sketch for transform

For instance, with C++ 17 enabled:

#include &lt;tuple&gt;
#include &lt;type_traits&gt;

template &lt;typename Fn, typename Argument, std::size_t... Ns&gt;
auto tuple_transform_impl(Fn&amp;&amp; fn, Argument&amp;&amp; argument, std::index_sequence&lt;Ns...&gt;) {
    if constexpr (sizeof...(Ns) == 0) return std::tuple&lt;&gt;(); // empty tuple
    else if constexpr (std::is_same_v&lt;decltype(fn(std::get&lt;0&gt;(argument))), void&gt;) {
        (fn(std::get&lt;Ns&gt;(argument)),...); // no return value expected
        return;
    }
    // then dispatch lvalue, rvalue ref, temporary
    else if constexpr (std::is_lvalue_reference_v&lt;decltype(fn(std::get&lt;0&gt;(argument)))&gt;) {
        return std::tie(fn(std::get&lt;Ns&gt;(argument))...);
    }
    else if constexpr (std::is_rvalue_reference_v&lt;decltype(fn(std::get&lt;0&gt;(argument)))&gt;) {
        return std::forward_as_tuple(fn(std::get&lt;Ns&gt;(argument))...);
    }
    else {
        return std::tuple(fn(std::get&lt;Ns&gt;(argument))...);
    }
}

template &lt;typename Fn, typename... Ts&gt;
auto tuple_transform(Fn&amp;&amp; fn, const std::tuple&lt;Ts...&gt;&amp; tuple) {
    return tuple_transform_impl(std::forward&lt;Fn&gt;(fn), tuple,
                          std::make_index_sequence&lt;sizeof...(Ts)&gt;());
}


Helper function

I can't see how you would avoid to use a helper function (besides using a tuple_transform function, which you're currently implementing).

You'll see I use a forwarding reference for the function argument, since it can be expensive to copy.

I keep the const lvalue reference for the tuple argument, because it's more simple (a forwarding reference wouldn't allow to constrain the type, and sizeof... wouldn't be possible either), but mainly because I can't see a case where the tuple would be consumed differently as a rvalue reference.