I've written a for_each
for tuple
s:
template <typename Tuple, typename F, size_t begin, size_t end>
enable_if_t<begin == end || tuple_size<Tuple>::value < end> for_each(Tuple&, F&&) {
}
template <typename Tuple, typename F, size_t begin = 0U, size_t end = tuple_size<Tuple>::value>
enable_if_t<begin < end && tuple_size<Tuple>::value >= end> for_each(Tuple& t, F&& f) {
f(get<begin>(t));
for_each<Tuple, F, begin + 1, end>(t, forward<F>(f));
}
[Live Example]
But Yakk's answer to this question gives a wonderful example of how to handle running a lambda on all tuple
values non-recursively:
namespace detail {
template<class F, class...Args>
void for_each_arg(F&& f, Args&&...args) {
using detail = int[];
static_cast<void>(detail{((f(std::forward<Args>(args))), void(), 0)..., 0});
}
}
template <typename F, typename Tuple>
void for_each_tuple_element(F&& f, Tuple&& t) {
return experimental::apply([&](auto&&...args) { detail::for_each_arg(forward<F>(f), decltype(args)(args)... ); }, forward<Tuple>(t));
}
This requires apply
. You can see my simplification of Yakk's answer here: http://ideone.com/yAYjmw
My question is this: Is there a way to somehow retrofit for_each_tuple_element
with a range, avoiding the recursion that my code incurs? I've tried constructing the subset of the tuple
defined by the range, but I can't seem to do that without using recursion, and then why not just my for_each
?
You could avoid recursion by generating a sequence of calls to the std::get<Is>(t)...
function, with Is
indices ranging from begin
up to end-1
. It's fairly easy to generate a sequence of consecutive numbers starting at a given index, as it's enough to make the starting point an offset that is then added to each item of a regular index sequence, e.g.:
std::get<begin + 0>(t), std::get<begin + 1>(t), ... std::get<begin + n>(t)
where the length of the sequence is equal to the distance between begin
and end
.
#include <tuple>
#include <type_traits>
#include <utility>
#include <cstddef>
#include <limits>
template <std::size_t begin, typename Tuple, typename F, std::size_t... Is>
void for_each(Tuple&& t, F&& f, std::index_sequence<Is...>)
{
using expand = int[];
static_cast<void>(expand{ 0, (f(std::get<begin + Is>(std::forward<Tuple>(t))), void(), 0)... });
}
template <std::size_t begin = 0U, std::size_t end = std::numeric_limits<std::size_t>::max(), typename Tuple, typename F>
void for_each(Tuple&& t, F&& f)
{
for_each<begin>(std::forward<Tuple>(t), std::forward<F>(f)
, std::make_index_sequence<(end==std::numeric_limits<std::size_t>::max()?std::tuple_size<std::decay_t<Tuple>>::value:end)-begin>{});
}
Test:
int main()
{
auto t = std::make_tuple(3.14, "Hello World!", -1);
auto f = [](const auto& i) { std::cout << i << ' '; };
for_each<1>(t, f);
for_each<1,3>(t, f);
for_each<0,2>(t, f);
}
DEMO
Also, note that defaulted template parameters of a function template don't have to be placed at the end of a template parameter list, hence, you can avoid the ugly decltype(t), decltype(f)
part. This implies that end
cannot be defaulted to std::tuple_size<Tuple>::value
(since Tuple
goes after end
), but at this point all you need is some default magic number.
You could implement a make_index_range
metafunction like so:
template <std::size_t Start, std::size_t End>
struct index_range {
template <std::size_t... Idx>
static std::index_sequence<(Idx + Start)...>
make_range (std::index_sequence<Idx...>);
using type = decltype(make_range(std::make_index_sequence<End-Start>{}));
};
template <std::size_t Start, std::size_t End>
using make_index_range = typename index_range<Start, End>::type;
Then you can use this to generate your std::index_sequence
:
template <typename Tuple, typename F, std::size_t... Idx>
void for_each(Tuple& t, F&& f, std::index_sequence<Idx...>) {
(void)std::initializer_list<int> {
(std::forward<F>(f)(std::get<Idx>(t)), 0)...
};
}
template <typename Tuple, size_t begin = 0U,
size_t end = tuple_size<Tuple>::value, typename F>
enable_if_t<begin < end && tuple_size<Tuple>::value >= end>
for_each(Tuple& t, F&& f) {
for_each(t, std::forward<F>(f), make_index_range<begin, end>{});
}
You would use this like so:
auto t = std::make_tuple(1, 42.1, "hello world");
for_each<decltype(t), 2, 3>(t,[](auto e){std::cout << e << '\n';});
//outputs hello world
Note that you need to pass decltype(t)
in if you want to give a begin and end. You could avoid that by using the technique in Peter Skotnicki's answer.
Live Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With