使用任意数量的参数生成对 lambda 的调用
generating calls to lambdas with arbitrary number of parameters
下面的定义被证明对我很有用:
template<class Func, class... Args>
void apply_on_each_args(Func f, Args... args)
{
(f(args), ...);
}
基本上,参数包折叠在逗号运算符上,允许定义对带有参数的函数的多次调用。例如:
apply_on_each_args([] (auto x) { cout << x << endl; }, 1, 2, "hello");
将在 1
、2
和 "hello"
上调用匿名 lambda。
提出了这个想法,我想做同样的事情,但传递带有两个、三个等参数的 lambda。例如,类似的东西
apply_on_each_args([] (auto x, auto y) { /* use x and y */ }, 1, 2, "hello", "bye");
有什么线索、技巧、想法等可以实现吗?
我能想到的(目前)最好的是旧的递归方式。
举例
// 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...); }
好的,今晚我的巫术很强大:
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)
}
为简洁起见省略转发。
为了更好地理解这是如何工作的,我们可以手动展开:
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))
另一种方法:
有一件事我不喜欢你的调用语法
apply_on_each_2_args([] (auto x, auto y) { }, 1, 2, "hello", "bye");
不清楚每个调用的参数是如何分组的。
所以我想把它们分组。不幸的是,对于可变参数,我无法让它像这样工作:
apply_on_each_2_args([] (auto x, auto y) { }, {1, 2}, {"hello", "bye"});
但我们可以更详细一点 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"});
}
与您的要求不完全相同,但这是一种值得考虑的方法。
一种使 apply_on_each()
接收 lambda(或函数)的方法,该 lambda 接收未定义数量的通用参数并调用它们(部分)以 C++17 方式展开。
老实说,这只是博洛夫巫毒答案的概括。
首先,一组constexpr
个函数来检测一个函数的参数个数(假设参数是通用的,那么假设整数零的列表是可以接受的)
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); }
现在 apply_on_each()
函数检测函数 func
的参数数量,并且按照 Bolov 的示例,调用一个(第一个)辅助函数添加一个(双精度,在这个泛化中)索引列表和参数 std::tuple
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...));
}
现在第一个辅助函数即"unpack"第一个索引序列,使用C++17折叠,并调用第二个辅助函数
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), ...) ; }
现在是最后一个辅助函数,它处理索引,使用正确的参数调用 func
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)...); }
下面是一个完整的例子
#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);
}
下面的定义被证明对我很有用:
template<class Func, class... Args>
void apply_on_each_args(Func f, Args... args)
{
(f(args), ...);
}
基本上,参数包折叠在逗号运算符上,允许定义对带有参数的函数的多次调用。例如:
apply_on_each_args([] (auto x) { cout << x << endl; }, 1, 2, "hello");
将在 1
、2
和 "hello"
上调用匿名 lambda。
提出了这个想法,我想做同样的事情,但传递带有两个、三个等参数的 lambda。例如,类似的东西
apply_on_each_args([] (auto x, auto y) { /* use x and y */ }, 1, 2, "hello", "bye");
有什么线索、技巧、想法等可以实现吗?
我能想到的(目前)最好的是旧的递归方式。
举例
// 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...); }
好的,今晚我的巫术很强大:
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)
}
为简洁起见省略转发。
为了更好地理解这是如何工作的,我们可以手动展开:
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))
另一种方法:
有一件事我不喜欢你的调用语法
apply_on_each_2_args([] (auto x, auto y) { }, 1, 2, "hello", "bye");
不清楚每个调用的参数是如何分组的。
所以我想把它们分组。不幸的是,对于可变参数,我无法让它像这样工作:
apply_on_each_2_args([] (auto x, auto y) { }, {1, 2}, {"hello", "bye"});
但我们可以更详细一点 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"});
}
与您的要求不完全相同,但这是一种值得考虑的方法。
一种使 apply_on_each()
接收 lambda(或函数)的方法,该 lambda 接收未定义数量的通用参数并调用它们(部分)以 C++17 方式展开。
老实说,这只是博洛夫巫毒答案的概括。
首先,一组constexpr
个函数来检测一个函数的参数个数(假设参数是通用的,那么假设整数零的列表是可以接受的)
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); }
现在 apply_on_each()
函数检测函数 func
的参数数量,并且按照 Bolov 的示例,调用一个(第一个)辅助函数添加一个(双精度,在这个泛化中)索引列表和参数 std::tuple
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...));
}
现在第一个辅助函数即"unpack"第一个索引序列,使用C++17折叠,并调用第二个辅助函数
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), ...) ; }
现在是最后一个辅助函数,它处理索引,使用正确的参数调用 func
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)...); }
下面是一个完整的例子
#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);
}