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?
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);
}
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.
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