Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I avoid template recursion here?

I've written a for_each for tuples:

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?

like image 611
Jonathan Mee Avatar asked Dec 18 '22 18:12

Jonathan Mee


2 Answers

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.

like image 58
Piotr Skotnicki Avatar answered Dec 24 '22 01:12

Piotr Skotnicki


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

like image 41
TartanLlama Avatar answered Dec 24 '22 02:12

TartanLlama