Lambda 通过引用捕获右值引用
Lambda capturing rvalue reference by reference
下面的代码符合标准吗? (godbolt)
即by-ref 捕获代表临时的转发引用,并从函数返回结果 lambda by-value,在相同的表达式.
当然,存储 lambda 以备后用会使它包含悬空引用,但我指的是 main
.
中的确切用法
我的疑虑与 this SO answer and potentially this language defect 有关。具体来说,有一条令人生畏的评论说 "the reference capture lifetime rule in the standard references captured variables, not data, and their scope" - 这似乎是说捕获的对临时对象的引用在我的代码中可能无效。
#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;
}
是的,您的代码中没有 UB。 f
绑定到 lambda,但您在同一表达式中调用捕获 f
的 lambda,因此它的生命周期尚未结束。您链接的缺陷报告阐明了通过引用获取引用的含义。通过澄清引用捕获实际上是对捕获引用绑定到的对象的引用来解决。
在您的例子中,捕获的 f
是对 lambda 的引用(而不是参数 f
)。
您的代码中有 UB 相对较短 window。 (注意:这是一个很奇怪的说法)。原始的 lambda 按引用捕获规则规定引用仅在捕获的 变量 超出范围之前有效。
这可能导致一种按引用捕获,否则在 C++ 标准中是不可能的。 (您可以获得的最接近的是对包含引用的单成员结构的引用)
理论上,您可以利用这一事实使 lambda 引用捕获成为基于堆栈帧的;捕获当前堆栈帧,并且所有(几乎?)引用参数都将处于该堆栈帧的固定偏移量。
由于大多数(全部?)ABI 在后台将引用参数实现为指针,这将导致函数参数的引用参数是在 lambda 返回后悬空的引用。
没有编译器利用这个事实。该优化从未使用过,只是尽可能地观察到。 “lambda 的引用捕获具有变量引用的生命周期”规则从未被任何编译器(或至少我听说过的任何编译器)利用。
当它被发现时,它在标准中被作为缺陷解决方案解决,这意味着它追溯地重新定义了 c++11 的含义。
因此,虽然在历史 c++11 编译器下,这在技术上是 UB,但当前兼容的 c++11 编译器无法将其视为 UB,并且所有历史 C++11 编译器都以与当前编译器相同的方式对待它做。所以你是安全的。
下面的代码符合标准吗? (godbolt)
即by-ref 捕获代表临时的转发引用,并从函数返回结果 lambda by-value,在相同的表达式.
当然,存储 lambda 以备后用会使它包含悬空引用,但我指的是 main
.
我的疑虑与 this SO answer and potentially this language defect 有关。具体来说,有一条令人生畏的评论说 "the reference capture lifetime rule in the standard references captured variables, not data, and their scope" - 这似乎是说捕获的对临时对象的引用在我的代码中可能无效。
#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;
}
是的,您的代码中没有 UB。 f
绑定到 lambda,但您在同一表达式中调用捕获 f
的 lambda,因此它的生命周期尚未结束。您链接的缺陷报告阐明了通过引用获取引用的含义。通过澄清引用捕获实际上是对捕获引用绑定到的对象的引用来解决。
在您的例子中,捕获的 f
是对 lambda 的引用(而不是参数 f
)。
您的代码中有 UB 相对较短 window。 (注意:这是一个很奇怪的说法)。原始的 lambda 按引用捕获规则规定引用仅在捕获的 变量 超出范围之前有效。
这可能导致一种按引用捕获,否则在 C++ 标准中是不可能的。 (您可以获得的最接近的是对包含引用的单成员结构的引用)
理论上,您可以利用这一事实使 lambda 引用捕获成为基于堆栈帧的;捕获当前堆栈帧,并且所有(几乎?)引用参数都将处于该堆栈帧的固定偏移量。
由于大多数(全部?)ABI 在后台将引用参数实现为指针,这将导致函数参数的引用参数是在 lambda 返回后悬空的引用。
没有编译器利用这个事实。该优化从未使用过,只是尽可能地观察到。 “lambda 的引用捕获具有变量引用的生命周期”规则从未被任何编译器(或至少我听说过的任何编译器)利用。
当它被发现时,它在标准中被作为缺陷解决方案解决,这意味着它追溯地重新定义了 c++11 的含义。
因此,虽然在历史 c++11 编译器下,这在技术上是 UB,但当前兼容的 c++11 编译器无法将其视为 UB,并且所有历史 C++11 编译器都以与当前编译器相同的方式对待它做。所以你是安全的。