【问题标题】:Lambda capturing rvalue reference by referenceLambda 通过引用捕获右值引用
【发布时间】:2019-08-28 18:27:59
【问题描述】:

以下代码标准正确吗? (godbolt)

即by-ref 捕获表示临时的转发引用,并从函数中返回生成的 lambda 值,在同一表达式中

当然,存储 lambda 供以后使用会使它包含一个悬空引用,但我指的是 main 中的确切用法。

我的疑虑与this SO answer 和可能的this language defect 有关。具体来说,有一条令人生畏的评论说“标准中的引用捕获生命周期规则引用捕获的变量,而不是数据及其范围” - 这似乎是说捕获的对临时的引用可能在我的代码。

#include <stdlib.h>
#include <string.h>
#include <cassert>

template<typename F>
auto invoke(F&& f)
{
    return f();
}

template<typename F>
auto wrap(F&& f)
{
    return [&f]() {return f();}; // <- this by-ref capture here
}

int main()
{
    int t = invoke(wrap(
        []() {return 17;}
    ));

    assert(t == 17);
    return t;
}

【问题讨论】:

  • 这是您的实际用例吗?因为你当然可以做return f();
  • 只是检查。你意识到invokewrap 中的那些通用引用没有被传播,对吧?你也可以传递左值引用。
  • @SombreroChicken 在实际用例中 wrap 当然会做一些事情
  • @RichardHodges 是的,我可以声明 invoke 采用 l-value ref 但它不会改变问题 - invoke 函数只是用作使用从 @ 返回的 lambda 的一种方式987654332@ 在同一个表达式内

标签: c++ lambda temporary-objects forwarding-reference


【解决方案1】:

您的代码中有一个相对较短的窗口的 UB。 (注:这是一个很奇怪的说法)。原始的 lambda 引用捕获规则声明该引用仅在捕获的 变量 超出范围之前有效。

这可能导致一种逐个引用的捕获,否则在 C++ 标准中是不可能的。 (你能得到的最接近的是对包含引用的单成员结构的引用)

理论上,您可以利用这一事实使 lambda 引用捕获基于堆栈帧;捕获当前堆栈帧,并且所有(几乎?)引用参数都将位于该堆栈帧的固定偏移量。

由于大多数(全部?)ABI 将引用参数实现为底层指针,这将导致函数参数的引用参数在 lambda 返回后悬空。

没有编译器利用这一事实。该优化从未使用过,只是尽可能地观察到。 “lambda 的引用捕获具有变量引用的生命周期”规则从未被任何编译器(或至少我听说过的任何编译器)利用。

当它被发现时,它被作为标准中的缺陷解决方案来解决,这意味着它追溯地重新定义了 的含义。

因此,虽然在历史上的 编译器下,这在技术上是 UB,但当前没有兼容的 编译器可以将其视为 UB,并且所有历史 C++11 编译器都以与当前编译器相同的方式处理它。所以你是安全的。

【讨论】:

  • 具有包容性的历史课的精彩答案。传奇。
  • 我对这个答案感到非常谦虚,在过去的 14 年里,我几乎只在专业环境中编程 C++,其中大部分是在金融/低延迟等方面。我相信我有足够的信息将此标记为答案,但我会尝试加强这个主题。
【解决方案2】:

是的,您的代码中没有 UB。 f 绑定到 lambda,但是您调用了在同一表达式中捕获 f 的 lambda,因此它的生命周期还没有结束。您链接的缺陷报告阐明了通过引用捕获引用的方式。通过澄清引用捕获实际上是对捕获的引用绑定到的对象的引用,解决了这个问题。

在您的情况下,捕获的 f 是对 lambda 的引用(而不是参数 f)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-26
    • 1970-01-01
    • 2020-01-23
    相关资源
    最近更新 更多