【问题标题】:Why does [=]{} have a lambda capture?为什么 [=]{} 有 lambda 捕获?
【发布时间】:2019-05-01 00:36:03
【问题描述】:

在直观的层面上,不需要携带任何状态(通过引用或其他方式)的 lambda 应该可以干净地转换为裸函数指针是有道理的。然而,我最近惊讶地发现 GCC、Clang 和 MSVC 中出现以下错误:

int main(int, char *[]) {
    void (*fp)() = []{}; // OK
  //fp = [=]{};          // XXX - no user defined conversion operator available
  //fp = [&]{};          // XXX - same ...
}

C++17 规范(或至少 visible public draft version N4713)在第 8.4.5.1 节的第 7 项中引用了 [expr.prim.lambda.closure] 对带有和不带有捕获的 lambda:

非泛型 lambda 表达式的闭包类型没有 lambda 捕获,其约束(如果有)得到满足,具有一个转换函数指向具有 C++ 语言链接 (10.5) 的函数的指针与闭包类型的函数调用运算符相同的参数和返回类型。 ...

但是,查看正式语法,您可以在第 8.4.5 节中看到以下内容[expr.prim.lambda]

  • lambda 表达式
    • lambda-introducer 复合语句
    • ...
  • lambda-introducer
    • [lambda-captureopt]
  • ...

并在 § 8.4.5.2 [expr.prim.lambda.capture]

  • lambda 捕获
    • 默认捕获
    • 捕获列表
    • 默认捕获,捕获列表
  • 默认捕获
    • &
    • =

因此,令我沮丧的是,所有编译器实际上都在遵守法律条文......

为什么语言将捕获的存在定义为声明中的一种狭义语法区别,而不是基于主体是否包含对任何非静态/捕获状态的引用?

【问题讨论】:

  • 你假设[=][&] 没有捕获fp 他们所做的。
  • @0x499602D2 为什么fp 没有在体内使用?
  • 我认为在当前标准文本中指定的方式正是最容易写下来的方式。我可以反过来问你为什么要使用[=],而你显然不想捕捉任何东西。
  • @Brian 这显然是一个人为的最小示例。我的真实用例涉及一个返回 lambda 的可变参数完美转发函数,该函数在被包装在类型擦除类中后在其他地方检查是否有状态。解决方法是在内部测试 sizeof...(args) 并产生两个几乎相同的 lambda 主体之一,但这是一个丑陋的模式。

标签: c++ c++11 lambda language-lawyer function-pointers


【解决方案1】:

允许转换的更改是由国家机构评论发起的。见n3052: Converting Lambdas to Function Pointers,指的是国家机构comment UK 42

具有空捕获列表的 lambda 与常规函数类型具有相同的语义。通过要求这种映射,我们得到了一个有效的 lambda 类型,它具有一个已知的 API,它也与现有的操作系​​统和 C 库函数兼容。

N3052 的分辨率是:

解决方案:添加新段落:“具有空捕获集的 lambda 表达式应可转换为指向函数类型 R(P) 的指针,其中 R 是返回类型,P 是 lambda 的参数类型列表表达。”此外,(a)允许转换为函数引用和(b)允许外部“C”函数指针类型可能会很好。

...

在第 5 段之后添加一个新段落。此编辑的目的是为没有 lambda 捕获的 lambda 获得闭包到函数指针的转换。

没有 lambda 捕获的 lambda 表达式的闭包类型具有一个公共的非虚拟非显式 const 转换函数,该函数指向具有与闭包类型的函数调用运算符相同的参数和返回类型的函数的指针。这个转换函数的返回值应该是一个函数的地址,当被调用时,它与调用闭包类型的函数调用运算符具有相同的效果。

这就是我们今天所处的位置。请注意评论说empty capture list,我们今天所拥有的似乎与评论中措辞的意图相符。

看起来这是基于国家机构评论的修复,并且应用范围很窄。

【讨论】:

    【解决方案2】:

    您提出的规则非常脆弱,尤其是在 P0588R1 之前的世界中,当隐式捕获依赖于 odr 使用时。

    考虑:

    void g(int);
    void h(const int&);
    
    int my_min(int, int);
    
    void f(int i) {
        const int x = 1, y = i;
        [=]{ g(x); }; // no capture, can convert?
        [=]{ g(y); }; // captures y
        [=]{ h(x); }; // captures x
        [=]{ my_min(x, 0); }; // no capture, can convert?
        [=]{ std::min(x, 0); }; // captures x
    }
    

    【讨论】:

    • 但当时显然脆弱性不是问题,因为默认参数中的 lambda 规范使用了脆弱规则:void f() { const int x = 1; void a1(int = [=]{ return x; }()); /* <- OK, no capture */ void a2(int = [=]{ return *&x; }()); /* <- Error */ } :/
    • 如果在 C++ 的整个历史中,如果委员会中没有任何人显然关心所有程序员都没有暴露于这种脆弱性,我可以假装关心它过于脆弱。
    • @cpplearner 本地函数声明很少见;带有默认参数的局部函数声明更加罕见;带有使用 lambda 的默认参数的局部函数声明更是如此。
    • @cpplearner 当程序员被期望正确使用更简单、面向初学者的功能时,脆弱性从来都不是问题,比如内联函数const int x = 1; inline int f() { return x; } /* <- OK */ inline int g() { return *&x; } /* <- ill formed no diag required */(如果包含在多个 TU 中)
    猜你喜欢
    • 1970-01-01
    • 2012-11-11
    • 2021-08-04
    • 1970-01-01
    相关资源
    最近更新 更多