Using fold expression with std::apply on two tuples
Asked Answered
K

1

6

I just started learning C++17 fold expressions. I understand that it is possible to apply a fold expression to a tuple, like in the following example (inspired by replies to this question):

#include <iostream>
#include <tuple>

int main() {
    std::tuple in{1, 2, 3, 4};
    std::cout << "in = ";
    std::apply([](auto&&... x) { ((std::cout << x << ' '), ...); }, in);
    std::cout << std::endl;

    std::multiplies<int> op;
    auto out = std::apply([&](auto&& ...x) { return std::tuple{op(x, 3)...}; }, in);
    std::cout << "out = ";
    std::apply([](auto&&... x) { ((std::cout << x << ' '), ...); }, out);
    std::cout << std::endl;
}

Output:

in = 1 2 3 4
out = 3 6 9 12

Is it possible to zip two tuples together using a similar approach? Referring to the example above, I would like to replace the constant 3 by another tuple, such as this hypothetical version of std::apply:

auto out = std::apply([&](auto&& ...x, auto&& ...y) { return std::tuple{op(x, y)...}; }, inX, inY);

In case fold expressions are not applicable to this purpose, is there an alternative method to achieve the same result in C++20 (other than template recursion and/oriSFINAE)?

Kanchenjunga answered 10/7, 2020 at 20:8 Comment(2)
But you want a linear product or a cartesian product? I mean... from 2 tuples of 4 elements, you want a tuple of 4 elements or a tuple of 16 elements?Trifid
I was asking for a linear product, sorry if my question lacked precision.Kanchenjunga
T
13

You can apply twice:

auto out = std::apply([&](auto&&... x){
    return std::apply([&](auto&&... y){
        return std::make_tuple(op(x, y)...);
    }, inY);
}, inX);

Or you can use an index sequence, easier in C++20 since we have more generalized lambda syntax, though still fairly dense:

auto out = [&]<size_t... Is>(std::index_sequence<Is...>){
    return std::make_tuple(op(std::get<Is>(inX), std::get<Is>(inY))...);
}(std::make_index_sequence<std::tuple_size_v<decltype(inX)>>());

Might be worth adding a helper like...

auto indices_for = []<typename... Ts>(std::tuple<Ts...> const&){
    return = []<size_t... Is>(std::index_sequence<Is...>){
        return [](auto f) -> decltype(auto) {
            return f(std::integral_constant<size_t, Is>()...);
        };
    }(std::index_sequence_for<Ts...>());
};

That is, indices_for(t) gives you a function that takes a function and invokes it with a bunch of integral constants. This is a mess, but it's a mess you have to write one time and can easily test. This lets you write:

auto out = indices_for(inX)([&](auto... Is){
    return std::make_tuple(op(std::get<Is>(inX), std::get<Is>(inY))...);
});

Something like that.

Tangleberry answered 10/7, 2020 at 20:18 Comment(1)
Wow! It never occurred to me that I could simply nest calls to std::apply... Thanks a lot! Thank you also for taking the time to suggest an alternate method.Kanchenjunga

© 2022 - 2024 — McMap. All rights reserved.