【问题标题】:Lambda/function of arbitrary arity and with captures as a function argument任意数量的 Lambda/函数,并将捕获作为函数参数
【发布时间】:2019-02-24 10:41:06
【问题描述】:

在我的 C++ 程序中,我经常需要在某个小的有限域上根据其参数的所有可能值构建函数的值向量。例如,像这样:

int q = 7;
vector<int> GFq;
for (int x = 0; x < q; x++) GFq.push_back(x);

auto P = [q](int x, int y) -> int { return (x*x+y) % q; };
auto Q = [q](int x, int y) -> int { return (x+2*y) % q; };
auto f = [q,P,Q](int x1, int y1, int x2, int y2) 
        -> int {return (P(x1,y1) + Q(x2,y2)) % q; }

vector<int> table;
for (int x1: GFq) for (int y1: GFq) for (int x2: GFq) for (int y2: GFq)
    table.push_back(f(x1,y1,x2,y2));

这种模式在我的代码中经常重复,我很自然地想把它变成一个函数。所以我需要这样的东西:

template<typename F>  // not sure if I need to use templates
vector<int> tabulate(int q, F f) {
    // run through values 0..q-1 for all arguments of f
    // and store the values of f to the resulting vector
}

一些问题/问题:

  • 我希望能够将任意函数传递给tabulate(),包括不同数量的函数(即f(x)f(x,y) 等)
  • 我想构造我“即时”传递的函数,包括其他函数的使用(与f 相同的方式是在第一个代码sn-p 中由PQ 构造的)
  • 如果我设法传递这样一个函数,我如何在tabulate() 内对f 的所有可能参数(即0..q-1 的每个参数)运行循环?

【问题讨论】:

  • 是的,但我也希望能够使用 f 的两个、三个等参数调用制表
  • 这就是为什么你在给定的示例代码中有第一个模板重载tabulate( const Function f, Args&amp;&amp;... args),你传递f并转发所有应该使用f作为第二个模板参数调用的参数。
  • 嗯,但是当我尝试调用 std::vector&lt;int&gt; table = tabulate( GFq.begin(), GFq.end(), [q, P](int x1, int y1, int x2) { return (P(x1, y1) + x2) % q; } ); 时,它给了我一个编译错误
  • @Jarod42 好吧,是的,但主要问题不是迭代而是传递任意数量的函数

标签: c++ lambda variadic-functions


【解决方案1】:

我希望能够将任意函数传递给 tabulate(),包括不同数量的函数(即 f(x)、f(x,y) 等)

使tabulate 成为接受任意类型对象作为函数的模板。

我想构造我“即时”传递的函数,包括其他函数的使用(与第一个代码 sn-p 中从 P 和 Q 构造 f 的方式相同

您可以直接使用 lambda 作为函数参数。

如果我设法传递这样一个函数,如何在 tabulate() 中对 f 的所有可能参数(即每个参数为 0..q-1)运行循环?

在伪代码中:

params = {0, ..., 0};

while (1)
{
    // Call function with `params` here.

    int i = 0;
    for (i = 0; i < params.size(); i++)
    {
        params[i]++;
        if (params[i] == q)
            params[i] = 0;
        else
            break;
    }
    if (i == params.size())
        break;
}

实际上,您需要将参数存储在std::array(或std::tuple,如下代码所示),并使用std::apply 使用这些参数调用您的函数。


一个完整的实现:

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

template <typename T, typename ...P, std::size_t ...I>
bool increment_tuple_impl(T q, std::tuple<P...> &t, std::index_sequence<I...>)
{
    auto lambda = [&](auto index) -> bool
    {
        auto &elem = std::get<index.value>(t);
        elem++;
        if (elem == q)
        {
            elem = 0;
            return 0;
        }
        else
        {
            return 1;
        }
    };

    return (lambda(std::integral_constant<std::size_t, I>{}) || ...);
}

template <typename T, typename ...P>
bool increment_tuple(T q, std::tuple<P...> &t)
{
    return increment_tuple_impl(q, t, std::make_index_sequence<sizeof...(P)>{});
}

template <typename T, typename F, std::size_t MaxArity, typename ...P>
auto tabulate_impl(T q, F &&f)
{
    if constexpr (!std::is_invocable_v<F, P...>)
    {
        static_assert(sizeof...(P) < MaxArity, "Invalid function.");
        return tabulate_impl<T, F, MaxArity, P..., T>(q, std::forward<F>(f));
    }
    else
    {
        using return_type = std::invoke_result_t<F, P...>;
        std::vector<return_type> vec;
        std::tuple<P...> params{};
        do
        {
            vec.push_back(std::apply(f, params));
        }
        while (increment_tuple(q, params));
        return vec;
    }
}

template <typename T, typename F>
auto tabulate(T q, F &&f)
{
    constexpr int max_arity = 8;
    return tabulate_impl<T, F, max_arity, T>(q, std::forward<F>(f));
}

int main()
{
    auto v = tabulate(3, [](int x, int y){return x*10 + y;});

    // Prints `0 10 20 1 11 21 2 12 22`.
    for (auto x : v)
        std::cout << x << ' ';
}

【讨论】:

  • 该解决方案有效,但我不明白,因为它使用了很多我不熟悉的概念:) 我现在正在阅读 C++ 文档以了解它的工作原理和原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-19
  • 2020-05-05
  • 2018-02-16
  • 2011-12-27
  • 2021-08-30
  • 2017-12-18
相关资源
最近更新 更多