Search code examples
c++boostboost-hana

How to apply a tuple of actions on tuple of numbers?


I have two tuples, one containing values and another tuple containing actions for these values. Now I want to apply the corresponding action on each value, with as little code "overhead" as possible. Something like the simplified example below.

#include <iostream>
#include <boost/hana.hpp>

namespace hana = boost::hana;
using namespace hana::literals;


struct ThinkPositive
{
    void operator()(int &val) const
    {
        std::cout << "Think positive!\n";
        val = std::abs(val);
    }
};

struct Nice
{
    void operator()(int &val) const
    {
        std::cout << val << " is nice!\n";
    }
};

void numbers()
{
    auto handlers = hana::make_tuple(Nice{}, ThinkPositive{});
    auto nums = hana::make_tuple(5, -12);
    auto handlers_and_nums = hana::zip(handlers, nums);

    hana::for_each(handlers_and_nums, [](auto &handler_num) {
        handler_num[0_c](handler_num[1_c]);
    });

    auto result = hana::transform(handlers_and_nums, [](const auto &handler_num) {
        return handler_num[1_c];
    });

    hana::for_each(result, [](const auto num) {
        std::cout << "got " << num << '\n';
    });
}

int main()
{
    numbers();
}

While the example above works it would be nicer to modify the contents of nums in place.

Is there a way to modify nums in place?


Solution

  • You could use zip_with, but it seems to be against its nature (it requires the function to actually return something, but your operators () return nothing:

    auto special_compose = [](auto&& l, auto&& r){ l(r); return 0; };
    hana::zip_with(special_compose, handlers, nums);
    

    demo


    If you can make your operators return something, you could go with lockstep:

    hana::fuse(hana::fuse(hana::lockstep(hana::always(0)))(handlers))(nums);
    

    demo

    There should be something like lockstep defined without the outer f call, but I found nothing in the docs.


    A little more standard solution (won't fit your requirement of as little code overhead as possible):

    template<typename Fs, typename Params, size_t... is>
    void apply_in_lockstep_impl(Fs&& fs, Params&& ps, std::index_sequence<is...>){
        int x[] = { (fs[hana::integral_c<size_t,is>](ps[hana::integral_c<size_t,is>]),0)... };
    }
    
    template<typename Fs, typename Params>
    void apply_in_lockstep(Fs&& fs, Params&& ps){
        static_assert(hana::size(fs) == hana::size(ps), "");
        apply_in_lockstep_impl(std::forward<Fs>(fs),
                               std::forward<Params>(ps),
                               std::make_index_sequence<decltype(hana::size(ps))::value>{});
    }
    

    but at the call site it is prettier:

    apply_in_lockstep(handlers, nums);
    

    demo


    As was pointed in the comments another level of indirection can also help. Here this would mean to transform the sequence into a sequence of pointer, via which the original values are modified.

    auto nums_ptr = hana::transform(nums, [](auto &num) { return &num; });
    auto handlers_and_nums = hana::zip(handlers, nums_ptr);
    hana::for_each(handlers_and_nums, [](auto &handler_num) {
        handler_num[0_c](*handler_num[1_c]);
    });
    

    demo


    Another, more "traditional", way is to iterate over a range. This would be like using an old for loop.

    auto indices = hana::make_range(0_c, hana::length(handlers));
    hana::for_each(indices, [&](auto i) {
        handlers[i](nums[i]);
    });
    

    demo