【问题标题】:generating calls to lambdas with arbitrary number of parameters使用任意数量的参数生成对 lambdas 的调用
【发布时间】:2018-06-23 18:49:20
【问题描述】:

事实证明,以下定义对我非常有用:

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");

将在 12"hello" 上调用匿名 lambda。

提出了这个想法,我想做同样的事情,但传递带有两个、三个等参数的 lambda。例如,类似的东西

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

任何可以实现它的线索、技术、想法等?

【问题讨论】:

  • 如果f 有默认参数怎么办?
  • 你想调用一两个参数吗?我的意思是:给定apply_on_each_args(lambda, 1, 2, "hello", "bye"),您希望致电lambda(1, 2), lambda(2, "hello"), lambda("hello", "bye") 还是仅致电lambda(1, 2), lambda("hello", "bye")
  • @max66 我想根据 lambda 期望的参数数量来使用 args 是组。即:lambda(1, 2),下一个lambda("hello", "bye")。谢谢
  • 你想要一个接受带有未定义参数数量的 lambda 的函数吗?
  • 我也是:我不知道如何以 C++17 风格制作它;我将为您准备一个具有固定数量参数的简单递归;如果我有更好的想法,我会尝试开发它。

标签: c++17 variadic-templates template-meta-programming generic-lambda fold-expression


【解决方案1】:

我能想象的最好的(目前)是旧的递归方式。

举例

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

【讨论】:

  • 缺少基本情况;用于停止递归,它允许编译器生成。对吗?
  • @Irleon - 你显然是对的;我正在考虑另一种方法,但我忘记了基本情况;对不起;答案已更正。
【解决方案2】:

好的,今晚我的巫术很强大:

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"});
}

不完全是您所要求的,但值得考虑的方法。

【讨论】:

  • 如果模板函数采用 std::initializer_list&lt;T&gt; 参数,(func, {1, 2}, {"hello", "bye"}) 语法可以工作,但不幸的是,这意味着每个组中的所有参数都需要具有相同的类型。
  • 不错的巫毒教。我已经复制了……嗯……我从中获得了灵感。
【解决方案3】:

一种制作apply_on_each() 的方法,该apply_on_each() 接收一个接收未定义数量的通用参数并调用它们(部分)以 C++17 方式展开的 lambda(或函数)。

说实话,这只是对 Bolov 巫毒答案的概括。

首先,一组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 的示例调用(第一个)辅助函数,添加一个(双精度,在此泛化中)索引列表和 @ 987654326@ 个参数

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

现在第一个辅助函数“解包”第一个索引序列,使用 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);
 }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-06-05
    • 1970-01-01
    • 2021-06-03
    • 2021-08-26
    • 1970-01-01
    • 2018-02-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多