Is there a tuple for_each() that returns a tuple of all values returned from the functions invoked?

1.2k views Asked by At

I was looking through this tuple for_each() implementation a few months ago and was wondering if it is possible to implement a version that collects the return values of invoking the functions into a tuple as a result?

The reason I want to do this in my code base I have the following function which takes an input a variadic list of shapes, and returns a tuple of values.

template <typename... T, typename... R>
static constexpr auto map_to_opengl(T &&... shapes)
{
  return std::make_tuple(shape_mapper::map_to_array_floats(shapes)...);
}

Well, I'd like to change my function signature to accept a tuple of shapes, and return the result of invoking the function on each shape (this should be semantically equivalent to the code above). If I can do this, I can keep my code more DRY, which is important to me.

template <typename... T, typename... R>
static constexpr auto map_to_opengl(std::tuple<T...> &&shapes)
{
  return tuple_foreach(shapes, &shape_mapper::map_to_array_floats);
}

However the implementation of tuple_foreach doesn't allow any values to be collected. Is it possible to write such a function? If it exists in Hana, I missed it :(

I guess you wouldn't call this algorithm for_each, but maybe accumulate? I'm not sure here.

3

There are 3 answers

4
ildjarn On BEST ANSWER

Nothing was added to the standard library in C++17 that would particularly help here (that I can think of); here's the usual C++14 approach (using pack expansion) as a standalone algorithm:

namespace detail {
    template<typename T, typename F, std::size_t... Is>
    constexpr auto map_tuple_elements(T&& tup, F& f, std::index_sequence<Is...>) {
        return std::make_tuple(f(std::get<Is>(std::forward<T>(tup)))...);
    }
}

template<typename T, typename F, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
constexpr auto map_tuple_elements(T&& tup, F f) {
    return detail::map_tuple_elements(
        std::forward<T>(tup), f,
        std::make_index_sequence<TupSize>{}
    );
}

Online Demo

0
T.C. On

Doing it with std::apply would be something along these lines:

template<typename T, typename F>
constexpr auto map_tuple_elements(T&& tup, F f) {
    return std::apply([&f](auto&&... args){
               return std::make_tuple(f(decltype(args)(args))...);    
           }, std::forward<T>(tup));
}
0
Sam Varshavchik On

The following main() constructs a tuple, applies a function to each value in the tuple, and produces another tuple. You can trivially wrap the whole thing into a function of its own.

This solution uses a few templates that are new to C++17, but they all can be trivially reimplemented for C++14, if necessary:

#include <utility>
#include <tuple>
#include <iostream>

// The function

int square(int n)
{
    return n * 2;
}

// The helper class for unpacking a tuple into a parameter pack,
// invoking square(), then packing the result back into a tuple.

template<typename tuple, typename index_sequence> class apply_square;

template<typename tuple,
     size_t... sequence>
class apply_square<tuple, std::index_sequence<sequence...>> {

public:

    template<typename tuple_arg>
    static auto do_apply_square(tuple_arg &&tupple)
    {
        return std::make_tuple(square(std::get<sequence>(tupple))...);
    }
};

int main()
{
    // Create a sample tuple

    auto f=std::make_tuple(1, 2);

    // Invoke appropriately-specialized do_apply_square() against
    // the tuple.

    typedef std::make_index_sequence<std::tuple_size<decltype(f)>
                     ::value> tuple_indexes;

    auto f_squared=apply_square<decltype(f), tuple_indexes>
        ::do_apply_square(f);

    // f_squared should now be another tuple. Let's see:

    int &first=std::get<0>(f_squared);
    int &second=std::get<1>(f_squared);

    std::cout << first << ' ' << second << std::endl;
}