generating calls to lambdas with arbitrary number of parameters
D

3

7

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?

Dianadiandra answered 23/6, 2018 at 18:49 Comment(8)
What if f has default arguments?Pyrophoric
Do you want consume one or two args for call? I mean: given apply_on_each_args(lambda, 1, 2, "hello", "bye") do you expect to call lambda(1, 2), lambda(2, "hello"), lambda("hello", "bye") or only lambda(1, 2), lambda("hello", "bye")?Rider
@Rider I would like to consume the args is groups according to the number of arguments that the lambda expects. that is: lambda(1, 2), next lambda("hello", "bye"). ThanksDianadiandra
And you want a function that accept a lambda with an undefined number of arguments?Rider
yes, if possible. But I would not have any problem with a function expecting lambdas with 2, 3... etc. Some like such apply_on_each_2_args(lambda, 1, 2, "hello", "bye"), apply_on_each_3_args(lambda, 1, 2, 3, "hello", "intermezzo", "bye"),Dianadiandra
with a fixed number of arguments, if you accept the classic recursive solution, it's simple. Do you accept a recursive solution or do you want a C++17 unpacking style solution?Rider
Any kind of solution would be good for me, although I am curious about the c++17 version.Dianadiandra
So do I: I don't know a way to make it in C++17 style; I'll prepare you a simple recursive with fixed number of arguments; if something better comes in my mind, I'll try to develop it.Rider
B
3

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.

Bacolod answered 23/6, 2018 at 20:25 Comment(2)
The (func, {1, 2}, {"hello", "bye"}) syntax could work if the template function takes std::initializer_list<T> parameters, but unfortunately that means all arguments in each group would need to have the same type.Pristine
Nice voodoo. I've copie... ehmm... I've taken inspiration from it.Rider
R
4

The best I can imagine (at the moment) is the good old recursive way.

By example

// ground case
template <typename Func>
void apply_on_each_2_args (Func)
 { }

// recursive case
template <typename Func, typename A0, typename A1, typename ... Args>
void apply_on_each_2_args (Func f, A0 a0, A1 a1, Args ... args)
 { f(a0, a1); apply_on_each_2_args(f, args...); }
Rider answered 23/6, 2018 at 19:55 Comment(2)
There would be a base case missing; for stopping recursion and it allows the compiler to generate. Right?Dianadiandra
@Irleon - you're obviously right; I was thinking an alternative way and I've forgotten the ground case; sorry; answer corrected.Rider
B
3

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.

Bacolod answered 23/6, 2018 at 20:25 Comment(2)
The (func, {1, 2}, {"hello", "bye"}) syntax could work if the template function takes std::initializer_list<T> parameters, but unfortunately that means all arguments in each group would need to have the same type.Pristine
Nice voodoo. I've copie... ehmm... I've taken inspiration from it.Rider
R
2

A way to make a apply_on_each() that receive a lambda (or a function) that receive an undefined number of generic arguments and call they (partially) unfolding in a C++17 way.

To be honest, it's only the generalization of the Bolov's voodoo answer.

First of all, a set of constexpr functions to detect the number of arguments of a function (supposing the arguments are generic, so supposing a list of integer zeros is acceptable)

template <typename F, typename ... Ts>
constexpr auto numArgsH (int, Ts ... ts)
   -> decltype( std::declval<F>()(ts...), std::size_t{} )
 { return sizeof...(Ts); }

template <typename F, typename ... Ts>
constexpr auto numArgsH (long, Ts ... ts)
 { return numArgsH<F>(0, 0, ts...); }

template <typename F>
constexpr auto numArgs ()
 { return numArgsH<F>(0); }

Now the apply_on_each() function that detect the number of arguments for the function func and, following the Bolov's example, call a (first) helper function adding a (double, in this generalization) list of indexes and the std::tuple of arguments

template <typename F, typename ... Ts>
void apply_on_each (F func, Ts ... ts)
 {
   static constexpr auto num_args { numArgs<F>() };

   apply_on_each_h1(func,
                    std::make_index_sequence<sizeof...(Ts)/num_args>{},
                    std::make_index_sequence<num_args>{},
                    std::make_tuple(ts...));
 }

Now the first helper function that "unpack" the first index sequence, using C++17 folding, and call the second helper function

template <typename F, std::size_t ... Is, std::size_t ... Js, 
          typename ... Ts>
void apply_on_each_h1 (F func,
                       std::index_sequence<Is...> const &,
                       std::index_sequence<Js...> const & js, 
                       std::tuple<Ts...> const & t)
 { (apply_on_each_h2<Is>(func, js, t), ...) ; }

Now the last helper function that, playing with indexes, call the func with the right arguments

template <std::size_t I, typename F, std::size_t ... Js, typename ... Ts>
void apply_on_each_h2 (F func,
                       std::index_sequence<Js...> const & js, 
                       std::tuple<Ts...> const & t)
 { func(std::get<I*sizeof...(Js)+Js>(t)...); }

The following is a full example

#include <tuple>
#include <utility>
#include <iostream>
#include <type_traits>

template <typename F, typename ... Ts>
constexpr auto numArgsH (int, Ts ... ts)
   -> decltype( std::declval<F>()(ts...), std::size_t{} )
 { return sizeof...(Ts); }

template <typename F, typename ... Ts>
constexpr auto numArgsH (long, Ts ... ts)
 { return numArgsH<F>(0, 0, ts...); }

template <typename F>
constexpr auto numArgs ()
 { return numArgsH<F>(0); }

template <std::size_t I, typename F, std::size_t ... Js, typename ... Ts>
void apply_on_each_h2 (F func,
                       std::index_sequence<Js...> const & js, 
                       std::tuple<Ts...> const & t)
 { func(std::get<I*sizeof...(Js)+Js>(t)...); }

template <typename F, std::size_t ... Is, std::size_t ... Js, 
          typename ... Ts>
void apply_on_each_h1 (F func,
                       std::index_sequence<Is...> const &,
                       std::index_sequence<Js...> const & js, 
                       std::tuple<Ts...> const & t)
 { (apply_on_each_h2<Is>(func, js, t), ...) ; }

template <typename F, typename ... Ts>
void apply_on_each (F func, Ts ... ts)
 {
   static constexpr auto num_args { numArgs<F>() };

   apply_on_each_h1(func,
                    std::make_index_sequence<sizeof...(Ts)/num_args>{},
                    std::make_index_sequence<num_args>{},
                    std::make_tuple(ts...));
 }

int main()
 {
   auto l1 = [](auto a)
    { std::cout << "- l1:" << a << std::endl; };

   auto l2 = [](auto a, auto b)
    { std::cout << "- l2:" << a << ", " << b << std::endl; };

   auto l3 = [](auto a, auto b, auto c)
    { std::cout << "- l3:" << a << ", " << b << ", " << c << std::endl; };

   apply_on_each(l1, 1, 2l, 3ll, "4", '5', 6.0);
   apply_on_each(l2, 1, 2l, 3ll, "4", '5', 6.0);
   apply_on_each(l3, 1, 2l, 3ll, "4", '5', 6.0);
 }
Rider answered 24/6, 2018 at 1:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.