I have N
tuples Foo, Bar, ..., Baz
with 'M' arbitrary element types. I can run std::apply
on each tuple, and invoke some arbitrary operation on its elements:
#include <tuple>
#include <type_traits>
auto operation(auto& foo, auto& bar)
{
//operation can be arbitrary
return foo + bar;
}
int main()
{
std::tuple<int, float, double> Foo{1, 2.0f, 3.0};
std::tuple<float, bool, char> Bar{1.0f, true, 'a'};
auto result = std::apply(
[&](auto &&...foo) {
return std::apply([&](auto &&...bar) {
return std::make_tuple(operation(foo, bar)...); }, Bar);
},
Foo);
return 0;
}
These expressions become verbose and ugly when the number of tuples increase. How would I proceed to design a multi_apply
:
auto result = multi_apply([&](auto&& ... foo, auto&& ... bar, ..., auto&& ... baz)
{
return std::make_tuple(operation(foo, bar, ..., baz)...);
}, Foo, Bar, ..., Baz);
Here is a "2D-variadic" example, demonstrating what multi_apply
should handle.
#include <tuple>
#include <type_traits>
auto operation(auto &...foo_bar_baz_elem)
{
//operation can be arbitrary
return (foo_bar_baz_elem + ...);
}
int main()
{
std::tuple<int, float, double> Foo{1, 2.0f, 3.0};
std::tuple<float, bool, char> Bar{1.0f, true, 'a'};
//...
std::tuple<char, float, char> Baz{1.0f, true, 'a'};
auto result = std::apply(
[&](auto &&...foo) {
return std::apply(
[&](auto &&...bar) {
// apply ...
//...
return std::apply([&](auto &&...baz) { return std::make_tuple(operation(foo, bar, ..., baz)...); },
Bar);
},
Baz);
},
Foo);
return 0;
}
Note: Input argument size of operation()
is known at the time of Foo, Bar,..., Baz
declaration, but multi_apply
needs to be able to handle all sizes.
This lets you pass around compile time natural numbers as values:
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
And this is a tuple of them:
template<std::size_t...Is>
using indexes_t = std::tuple< index_t<Is>... >;
Next, extract the indexes of a tuple:
template<std::size_t...Is>
constexpr indexes_t<Is...> to_indexes( std::index_sequence<Is...> )
{ return {}; }
template<class...Ts>
constexpr auto to_indexes( std::tuple<Ts...> const& ) {
return to_indexes( std::make_index_sequence<sizeof...(Ts)>{} );
}
Also, a nice primitive "map" - this applies f to each element of a tuple and makes a tuple from the result.
template<class Tuple, class F>
constexpr auto map_tuple( Tuple&& tuple, F f ) {
return std::apply(
[&](auto&&...ts){
return std::make_tuple( f(ts)... );
},
std::forward<Tuple>(tuple)
);
}
we can now define shuffle_tuples
. The empty shuffle is empty:
constexpr std::tuple<> shuffle_tuples() { return {}; }
For non-empty shuffles, we take the number of elements in the first tuple and then pick those in turn for each of the tuples:
template<class T0, class...Tuples>
constexpr auto shuffle_tuples( T0&& t0, Tuples&&... tuples ) {
return map_tuple(
to_indexes(t0),
[&](auto I) {
return std::make_tuple(
std::get<I>(std::forward<T0>(t0),
std::get<I>(std::forward<Tuples>(tuples)...
);
}
);
}
and that should do it.