Apply a function to each element of a tuple ("map" a tuple) C++ doesn't (yet) have a 'map' higher-order function, certainly not one which applies to a compile-time sequence of values like a tuple. We have std::transform, and there's an equivalent of it for tuples listed on SO, but that's iterator-based, and anyway - I don't want to use any reference to the output. I want to pass a tuple and a function / variadic lambda, and get another tuple of the same length with the function applied to each element. 

I'm also sure this has been implemented multiple times, but since I want to build up some "tuple confidence", I've done it myself. Here's what I got:

namespace detail {

template &lt;class F, typename Tuple, size_t... Is&gt;
auto transform_each_impl(Tuple t, F f, std::index_sequence&lt;Is...&gt;) {
    return std::make_tuple(
        f(std::get&lt;Is&gt;(t) )...
    );
}

} // namespace detail

template &lt;class F, typename... Args&gt;
auto transform_each(const std::tuple&lt;Args...&gt;&amp; t, F f) {
    return detail::transform_each_impl(
        t, f, std::make_index_sequence&lt;sizeof...(Args)&gt;{});
}


And this works in the cases I've tried - I haven't tested extensively.

I'd like general feedback/suggestions, but also:


When is the function resolved or instantiated in the case of different-type arguments? In what cases will it be the same (instantiated) function with forced conversion, as opposed to different instantiations for the different types? Am I really getting the latter at all?
Is there any std::forward'ing that I need to do?
Can I avoid a helper function somehow?
What do you think about the name?
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.