How to defer expanding a parameter pack?

I was toying around with tuples. I wanted to zip an arbitrary number of tuples. There is probably a better way to do that than what I came up with, but my solution led me to a problem that is interesting in itself.

Sometimes, you want to expand one parameter pack at a time, and those parameter packs can be template arguments and function arguments in the same call. I could not find an obvious way to expand Is without expanding ts, besides stuffing ts into a std::tuple and unpacking it again using std::get<I>.

Obviously, it’s preferable not to split one function into five functions. I know I could use lambdas, but I’m not sure that would be any cleaner.

Is there a nice way to defer expanding ts?

https://godbolt.org/z/sY5xMTa7P

#include <iostream>
#include <tuple>
#include <string_view>

template <typename T, typename... Ts>
auto get_first(T t, Ts...) {
    return t;
}

template <size_t I, typename... Ts>
auto zip2_impl4(Ts... ts) {
    return std::make_tuple(std::get<I>(ts)...);
}

template <size_t I, typename T, size_t... Is>
auto zip2_impl3(T t, std::index_sequence<Is...>) {
    return zip2_impl4<I>(std::get<Is>(t)...);
}

template <size_t I, typename T>
auto zip2_impl2(T t) {
    using size = std::tuple_size<T>;
    using seq = std::make_index_sequence<size::value>;
    return zip2_impl3<I>(t, seq{});
}

template <size_t... Is, typename... Ts>
auto zip2_impl(std::index_sequence<Is...> seq, Ts... ts) {
    // need to defer expanding the pack ts,
    // because the packs Is and ts need to expand separately
    auto t = std::make_tuple(ts...);
    return std::make_tuple(zip2_impl2<Is>(t)...);
}

template <typename... Ts>
auto zip2(Ts... ts) {
    using size = std::tuple_size<decltype(get_first(ts...))>;
    using seq = std::make_index_sequence<size::value>;
    return zip2_impl(seq{}, ts...);
}

int main() {
    using namespace std::literals;
    auto ints = std::make_tuple(1,2,3);
    auto svs1 = std::make_tuple("a"sv, "b"sv, "c"sv);
    auto svs2 = std::make_tuple("d"sv, "e"sv, "f"sv);
    auto zipped = zip2(ints, svs1, svs2);

    std::apply([](auto... args) {
        (std::apply([](auto... args) {
            ((std::cout << args), ...);
        }, args), ...);
    }, zipped);

    return 0;
}
output: 1ad2be3cf

>Solution :

If I understood you correctly:
https://godbolt.org/z/e4fvr45rz

#include <type_traits>
#include <tuple>
#include <cstddef>

template <size_t Indx,typename ... Tuples>
constexpr auto zip(Tuples ... tuples)
{
    return std::make_tuple(std::get<Indx>(tuples)...);
}

template <typename ... Tuples, size_t ... Indx>
constexpr auto zip(std::index_sequence<Indx...>, Tuples... tuples)
{
    // will expand simultaniously, not what you want...
    //return std::tuple_cat(std::make_tuple(std::get<Indx>(tuples)...));
    return std::tuple_cat(zip<Indx>(tuples...)...);
}

template <typename ... Tuples>
constexpr auto zip(Tuples ... tuples)
{
    return zip(std::make_index_sequence<sizeof...(Tuples)>(), tuples...);
}

#include <iostream>
#include <string_view>

int main()
{
    using namespace std::literals;
    auto ints = std::make_tuple(1,2,3);
    auto svs1 = std::make_tuple("a"sv, "b"sv, "c"sv);
    auto svs2 = std::make_tuple("d"sv, "e"sv, "f"sv);
    auto zipped = zip(ints, svs1, svs2);

    std::apply([](auto ... args){
        ((std::cout << args), ...);
    }, zipped);

    return 0;
}

Output:

Program returned: 0
1ad2be3cf

So there is a way, I guess. You have to have your parameter packs in different contexts: one in the function arguments, another in the template argumetns. If you try to expand both packs in one "context" you will get a compile-time error:

// this will not compile
// return std::tuple_cat(std::make_tuple(std::get<Indx>(tuples)...)...);

// and this will expand them simultaniously, giving you "1bf" as a result
return std::tuple_cat(std::make_tuple(std::get<Indx>(tuples)...));

UPDATE:
https://godbolt.org/z/9E1zj5q4G – more generic solution:

template <typename FirstTuple, typename ... Tuples>
constexpr auto zip(FirstTuple firstTuple, Tuples ... tuples)
{
    return zip(std::make_index_sequence<std::tuple_size_v<FirstTuple>>(),firstTuple,  tuples...);
}
int main()
{
    using namespace std::literals;
    auto ints = std::make_tuple(1,2,3);
    auto svs1 = std::make_tuple("a"sv, "b"sv, "c"sv);
    auto svs2 = std::make_tuple("d"sv, "e"sv, "f"sv);    
    auto svs3 = std::make_tuple("g"sv, "h"sv, "i"sv);

    auto zipped = zip(ints, svs1, svs2, svs3);

    std::apply([](auto ... args){
        ((std::cout << args), ...);
    }, zipped);

    return 0;
}

Outputs: 1adg2beh3cfi

Instead of number of tuples you pass a number of elements. You have to b sure that the number of elements is the same for each tuple

Leave a Reply