Search code examples
c++17variadic-templatestemplate-meta-programminggeneric-lambdafold-expression

generating calls to lambdas with arbitrary number of parameters


The following definition has proven to be very useful for me:

template<class Func, class... Args>
void apply_on_each_args(Func f, Args... args)
{
    (f(args), ...);
}

Basically, the arguments pack folded on comma operator, allows to define several calls to a function taking an argument. For example:

apply_on_each_args([] (auto x) { cout << x << endl; }, 1, 2, "hello");

will call the anonymous lambda on 1, 2 and "hello".

That idea presented, I would like to do the same thing but passing lambdas taking two, three, and so on arguments. For example, something like that

apply_on_each_args([] (auto x, auto y) { /* use x and y */ }, 1, 2, "hello",  "bye");

Any clue, technique, idea, etc that allow achieving it?


Solution

  • Ok, my voodoo is strong tonight:

    auto foo(int, int) -> void;
    
    template <class Func, class... Args, std::size_t... I>
    void apply_on_2x_indexes(Func f,  std::index_sequence<I...>, std::tuple<Args...> t)
    {
        (f(std::get<I * 2>(t), std::get<I * 2 + 1>(t)), ...);
    }
    
    template<class Func, class... Args>
    void apply_on_each_2_args(Func f, Args... args)
    {
        apply_on_2x_indexes(f, std::make_index_sequence<sizeof...(Args) / 2>{},
                            std::tuple{args...});   
    }
    
    auto test()
    {
        apply_on_each_2_args(foo, 1, 2, 3, 4); // calls foo(1, 2) foo(3, 4)
    }
    

    Forwarding omitted for brevity.

    To better understand how this works we can manually expand:

    apply(on_each_2_args(foo, 1, 2, 3, 4))
    ↳ apply_on_2x_indexes(f, std::index_sequence<0, 1>{}, std::tuple{1, 2, 3, 4})
      ↳ (f(std::get<0 * 2>(t), std::get<0 * 2 + 1>(t)),  f(std::get<1 * 2>(t), std::get<1 * 2 + 1>(t)))
        (f(std::get<0>(t), std::get<1>(t)),  f(std::get<2>(t), std::get<3>(t)))
        (f(1, 2), f(3, 4))
    

    Another approach:

    One thing that I don't like in your call syntax

    apply_on_each_2_args([] (auto x, auto y) { }, 1, 2, "hello",  "bye");
    

    is that is not clear how arguments are grouped per call.

    So I would like to group them. Unfortunately I can't get it to work like this for varargs:

    apply_on_each_2_args([] (auto x, auto y) { }, {1, 2}, {"hello",  "bye"});
    

    but we can be a little more verbose with tuple:

    template<class Func, class... Args>
    void apply_on_each_2_args(Func f, Args... args)
    {
        (std::apply(f, args), ...);
    }
    
    auto test()
    {
        apply_on_each_2_args([](auto a, auto b){ /*use a, b*/ },
                             std::tuple{1, 2}, std::tuple{"hello", "bye"});
    }
    

    Is not exactly what you asked but is an approach worth considering.