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