Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading