Intro:
Imagine you have defined addition on types A and B with function A add(A,A) and B add(B,B). Now, you would like to have an addition function on tuple of these types, like tuple<A,B> add(tuple<A,B>,tuple<A,B>).
When we generalize this problem, we would like to extend an existing function working on some types to a function working on tuples of these types.
I call this extension function static_container_functor and the following code demonstrate its use:
int main() {
std::tuple<double, int> t1{3.14159, -1}, t2{2.71828, 2};
auto print = [](auto x) { std::cout << x << " "; };
auto tuple_print = static_container_functor(print);
tuple_print(t1); // prints "3.14159 -1 " and returns void instead of std::tuple<void,void> which is not possible
std::cout << std::endl;
auto add = [](auto &&x, auto &&... y) { return (x + ... + y); };
auto tuple_add = static_container_functor(add);
auto t3 = tuple_add(t1, t1, t2); // t3 is of type std::tuple<double,int>
tuple_print(t3); // prints "9.00146 0 "
return 0;
}
I hope that it is clear to you by now what I'm trying to solve.
One way to think about static_container_functor is that it is somewhat glorified for_each for std::tuple.
What I would like to know:
I would like to know how can I possibly improve my code and whether I'm missing handling some cases. Also, I'm have attempted to do perfect forwarding and I do not know how to test if it works.
Furthermore, I have not employed the standard trick with std::index_sequence in the main implementation of static_container_functor but did a small variation of it to keep all of the code together in one function. I would like to know what do you think about it?
How to handle constexpr? In the example, how can I achieve that the tuple t3 can be constexpr when t1 and t2 are constexpr?
The function is called static_container_functor and not tuple_functor because I would like to extend it such that is works on std::variant and std::array. Ideally on anything that implements std::get<>. Any tips in that direction would be great.
Right now I'm doing a partial check, with static_assert, that the input arguments are really std::tuples. Would you recommend using SFINAE like in this post? However, for that I would need thatstd::enable_if works with automatic return type deduction.
Code:
#include <array>
#include <iostream>
#include <tuple>
template <std::size_t... I>
constexpr auto integral_sequence_impl(std::index_sequence<I...>) {
return std::make_tuple((std::integral_constant<std::size_t, I>{})...);
}
template <std::size_t N, typename Indices = std::make_index_sequence<N>>
constexpr auto integral_sequence = integral_sequence_impl(Indices{});
template <typename Op>
auto static_container_functor(Op &&op) {
return [&op](auto &&c, auto &&... d) {
/* here I should add a test that c and d... are really tuples */
constexpr int N = std::tuple_size_v<std::remove_reference_t<decltype(c)>>;
static_assert(
(true == ... ==
(N == std::tuple_size_v<std::remove_reference_t<decltype(d)>>)),
"All of the arguments must have the same length!");
auto implementation = [&](auto... I) {
auto slice = [&](auto idx) {
return std::forward_as_tuple(std::get<idx>(c), std::get<idx>(d)...);
};
auto result = [&](auto idx) {
return std::apply(op, std::forward<decltype(slice(idx))>(slice(idx)));
};
auto zero = std::integral_constant<size_t, 0>{};
if constexpr /* Are all return types are void? Return void*/
((std::is_same_v<void, decltype(result(I))> && ... && true)) {
(result(I), ...);
return;
} else /* All other cases. Return std::tuple */ {
using ReturnType = std::tuple<decltype(result(I))...>;
return ReturnType{result(I)...};
}
};
return std::apply(implementation, integral_sequence<N>);
};
}
int main() {
std::tuple<double, int> t1{3.14159, -1}, t2{2.71828, 2};
auto print = [](auto x) { std::cout << x << " "; };
auto tuple_print = static_container_functor(print);
tuple_print(t1); // prints "3.14159 -1 " and returns void
std::cout << std::endl;
auto add = [](auto &&x, auto &&... y) { return (x + ... + y); };
auto tuple_add = static_container_functor(add);
auto t3 = tuple_add(t1, t1, t2); // t3 is of type std::tuple<double,int>
tuple_print(t3); // prints "9.00146 0 "
return 0;
}