【问题标题】:C++ Lambdas with Ellipses in the Parameter List参数列表中带有省略号的 C++ Lambda
【发布时间】:2018-05-10 20:05:56
【问题描述】:

我正在开发一个库,它使用 lambda 来描述表达式术语的范围。因为库必须分发唯一的整数来标识每个变量,所以最好是由库而不是用户来构造变量,并且用户代码将它们作为 lambda 参数接收。

(换句话说,我正在从 miniKanren 实现“call\fresh”的 C++ 模拟。)

由于用户可能希望在特定范围内引入从零到许多新变量的任何数字,我希望用户能够将具有不同数量参数的 lambdas 传递给库。但是,我不知道有任何(简单)方法(在 C++14 中)来推断任意 lambda 对象的参数数量。

我突然想到为什么不将固定数量(例如 10)的可变 ID 参数传递给 lambda,并让用户代码在 lambda 中使用省略号来忽略不需要的参数?像这样的:

auto no_args = call_fresh([](...) { return success(); });
auto one_arg = call_fresh([](var A, ...) { return A == 1; });
auto two_args = call_fresh([](var A, var B, ...) { return A == 1 && B == 2; });

编译器资源管理器似乎接受 lambda 参数列表中的省略号,至少在 gcc 中是这样。

它会被这样调用(注意代码总是传递 10 个变量 id,无论“f”是只命名一个、两个还是一个都不命名):

template <typename F>
auto call_fresh(F f)
{
   return [f](StateCounter sc) {
      return f(sc+0,sc+1,sc+2,sc+3,sc+4,
          sc+5,sc+6,sc+7,sc+8,sc+9);
   };
}

我很惊讶它的存在,有什么理由不使用带有省略号的 lambdas 吗?

【问题讨论】:

  • variadic templates 或 initializer lists 是您想要的吗?
  • 虽然可变参数模板可能会简化参数编号检测器,但它并不能解决(据我所知)这样的检测器目前仅适用于没有捕获的 lambda(因为只有它们有将它们衰减为函数指针的运算符)。至于初始化列表,它们会遇到所有参数必须具有相同类型的问题,并且我希望最终拥有不止一种类型的“var”。

标签: c++ lambda c++14 variadic-functions generic-lambda


【解决方案1】:

但是,我不知道有任何(简单的)方法(在 C++14 中)来推断任意 lambda 对象的参数数量。

在我看来,您正在寻找 sizeof...() 而不是可变参数 auto 参数列表

#include <iostream>

int main ()
 {
   auto l = [](auto ... as) { return sizeof...(as); };

   std::cout << l(1, 2L, 3.0, 4.0f, "5") << std::endl; // print 5
 }

【讨论】:

  • 赞成是因为我学到了一些新东西(我不知道 auto ... 语法),但这实际上与我需要的相反。我不需要知道 lambda 内部的参数数量,我需要知道 lambda 调用者的参数数量。
  • 换句话说,目标是让 lambda 告诉调用代码它需要多少,而不是让调用代码告诉 lambda 它得到了多少。传递一个固定的数字只是一种解决方法,因为它没有允许库检查用户的 lambda 对象的反射。
  • @Dennis - 不确定是否理解......对你有好处的类型特征(或 constexpr 函数,或类似的东西),给定一个 lambda,返回它需要的参数数量?类型是已知的还是可以不同的?对不起,但从你的问题不清楚,对我来说,你想要什么。
  • hlt 答案中的代码回答了你的问题,我的问题。如果我的问题令人困惑,那可能是因为它的形式是“鉴于我不能这样做,这是一个好的解决方法吗?”并且 hlt 的回答是使我的问题的前提无效:实际上可以做我真正需要的事情,因此不需要变通方法。
【解决方案2】:

您的 lambda 本质上是 C 风格的 variadic functions。使用它们并没有错误,如果您不想访问这些值(这有点难看),那很好。

然而,您似乎真正想要解决的根本问题是让您的库找到函数/lambda 的参数数量(或 arity) /...,您可以使用模板元编程来完成 - 您的用户无需解决该问题。

披露:在我也在研究的库中有一个实现,here。

这是一个简单的例子:

template <typename Callable>
struct function_arity : public function_arity<decltype(&Callable::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_arity<ReturnType(ClassType::*)(Args...) const>
{
    constexpr static size_t arity = sizeof...(Args);
};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_arity<ReturnType(ClassType::*)(Args...)>
{
    constexpr static size_t arity = sizeof...(Args);
};

编译器会自动为您推导出参数类型,sizeof... 会为您提供所需的参数数量。

然后,您可以使用 function_arity&lt;decltype(lambda)&gt;::arity 来获取 lambda 的参数数量。最后一个版本处理mutable lambdas,其中调用运算符是非常量的。您可能还想扩展它以与 noexcept 正常工作,否则您会遇到像 this libc++ bug 这样的错误。

不幸的是,这不适用于重载或模板化的 operator()(例如,如果您在 lambda 中使用 auto 类型的参数)。如果您还想支持函数而不是 lambda,则可能需要额外的特化。

【讨论】:

    猜你喜欢
    • 2021-06-24
    • 2011-03-09
    • 2013-06-25
    • 2011-03-25
    • 2019-09-10
    • 2011-02-02
    • 1970-01-01
    • 1970-01-01
    • 2012-03-26
    相关资源
    最近更新 更多