在C++11 lambda中逐引用捕获引用

在C++11 lambda中逐引用捕获引用,c++,c++11,lambda,language-lawyer,C++,C++11,Lambda,Language Lawyer,考虑这一点: #include <functional> #include <iostream> std::function<void()> make_function(int& x) { return [&]{ std::cout << x << std::endl; }; } int main() { int i = 3; auto f = make_function(i); i

考虑这一点:

#include <functional>
#include <iostream>

std::function<void()> make_function(int& x) {
    return [&]{ std::cout << x << std::endl; };
}

int main() {
    int i = 3;
    auto f = make_function(i);
    i = 5;
    f();
}
这个程序保证在不调用未定义行为的情况下输出5吗

如果我通过值[=]捕获x,我理解它是如何工作的,但我不确定是否通过引用捕获来调用未定义的行为。是在make_函数返回后,我将得到一个悬空引用,还是只要原始引用的对象仍然存在,捕获的引用就可以保证工作


在这里寻找基于标准的明确答案:到目前为止,它在实践中运行良好

TL;DR:这个问题中的代码并没有得到标准的保证,而且lambdas的一些合理实现导致了它的崩溃。假设它是不可移植的,而不是使用

std::function<void()> make_function(int& x)
{
    const auto px = &x;
    return [/* = */ px]{ std::cout << *px << std::endl; };
}
从C++14开始,您可以使用初始化捕获明确使用指针,强制为lambda创建新的引用变量,而不是重用封闭范围内的引用变量:

std::function<void()> make_function(int& x)
{
    return [&x = x]{ std::cout << x << std::endl; };
}
乍一看,这似乎是安全的,但该标准的措辞引起了一点问题:

最小封闭范围为块范围的lambda表达式3.3.3是局部lambda表达式;任何其他lambda表达式在其lambda介绍人中不应具有捕获默认值或简单捕获。局部lambda表达式的到达范围是一组封闭范围,包括 最里面的封闭函数及其参数

所有此类隐式捕获的实体应在lambda表达式的范围内声明


[注意:如果实体通过引用隐式或显式捕获,则在实体的生存期结束后调用相应lambda表达式的函数调用运算符可能会导致未定义的行为。-结束注意]

我们期望发生的是,在make_函数中使用的x主要引用i,因为引用就是这样做的,实体i被引用捕获。由于该实体在lambda调用时仍然存在,所以一切都很好

但是!隐式捕获的实体必须在lambda表达式的到达范围内,而main中的i不在到达范围内:除非参数x在到达范围内计数,即使实体i本身在到达范围之外

这听起来像C++中的其他地方不同,创建了一个引用引用,引用的生命周期意义重大。

当然,我希望看到标准得到澄清

同时,TL中显示的变体;DR部分是绝对安全的,因为指针是由lambda对象本身中存储的值捕获的,并且它是指向对象的有效指针,该对象在lambda调用期间一直有效。我还希望通过引用捕获实际上最终会存储一个指针,所以这样做不会导致运行时损失

仔细观察,我们还认为它可能会断裂。请记住,在x86上,在最终的机器代码中,使用EBP相对寻址访问本地变量和函数参数。参数的偏移量为正,而局部变量的偏移量为负。其他体系结构有不同的寄存器名,但许多以相同的方式工作。无论如何,这意味着可以通过只捕获EBP的值来实现引用捕获。然后,可以通过相对寻址再次找到类似的局部变量和参数。事实上,我相信我已经听说过lambda实现,这些语言在C++之前就已经有了lambdas,即:捕获定义lambda的栈框架。 这意味着,当make_函数返回且其堆栈帧消失时,所有访问局部变量和参数的功能都消失了,即使是那些引用

该标准包含以下规则,可能特别支持这种方法:

未指定是否在引用捕获的实体的闭包类型中声明其他未命名的非静态数据成员

结论:问题中的代码没有得到标准的保证,lambdas有合理的实现,这导致它被破坏。假设它是不可移植的。

代码保证工作正常

<> P>在深入研究标准的措辞之前,C++委员会的意图是这个代码工作。然而,目前的措辞被认为在这方面不够清楚,事实上,对标准的后C++14进行的错误修复打破了使其工作的微妙安排,因此被提出澄清问题,并正在通过委员会。据我所知,没有实现会出错

我想澄清两件事,因为Ben Voigt的回答包含了一些事实错误,这些错误造成了som e混乱:

作用域是C++中的静态词汇概念,它描述了程序源代码的区域,其中不合格名称查找将特定名称与声明关联。这与生命无关。看见 lambda的到达范围规则也是一个语法属性,用于确定何时允许捕获。例如:

void f(int n) {
  struct A {
    void g() { // reaching scope of lambda starts here
      [&] { int k = n; };
      // ...
n在这里的作用域中,但lambda的到达作用域不包括它,因此无法捕获它。换句话说,lambda的作用域是它可以到达和捕获变量的范围——它可以到达封闭的非lambda函数及其参数,但它不能到达该函数之外,并捕获出现在外部的声明

所以到达范围的概念与这个问题无关。被捕获的实体是make_函数的参数x,它在lambda的范围内

好的,让我们看看标准在这个问题上的措辞。根据[expr.prim.lambda]/17,只有引用复制捕获的实体的id表达式才会转换为lambda闭包类型上的成员访问;引用通过引用捕获的实体的id表达式被单独保留,并且仍然表示它们在封闭范围中所表示的相同实体

这看起来很糟糕:引用x的生命周期已经结束,那么我们如何引用它呢?事实证明,在引用的生命周期之外,几乎看不到引用的方法。你可以看到它的声明,在这种情况下,它在范围内,因此可以使用,或者它是一个类成员,在这种情况下,类本身必须在其生命周期内,成员访问表达式才有效。因此,直到最近,该标准才禁止在其使用寿命之外使用参考

lambda的措辞利用了这样一个事实,即在引用的生命周期之外使用引用不会受到惩罚,因此不需要给出任何明确的规则来说明通过引用捕获的实体的访问方式——它只意味着您使用该实体;如果是引用,则名称表示其初始值设定项。直到最近,包括在C++11和C++14中,这就是保证工作的方式


然而,不能在引用的生命周期之外提及引用,这并不是完全正确的;特别是,您可以从它自己的初始值设定项中引用它,也可以从引用之前的类成员的初始值设定项中引用它,或者如果它是命名空间范围变量,并且您可以从另一个在引用之前初始化的全局变量访问它。被引入来修复这个疏忽,但它无意中通过引用打破了lambda捕获的规范。我们应该在C++17发布之前修复此回归;我已经提交了一份国家机构的意见,以确保它具有适当的优先级。

注意,另一个捕获x位置的安全解决方案是:std::function make_functionint&x{auto px=&x;return[=]{std::cout是的,这很值得一提。谢谢。刚刚更新了上面的注释,以显示参数可以保留为引用。重要的是通过值关闭指针。这可能会导致未定义的行为。这是正常的标准说法吗?:听起来出人意料地模糊:P@dyp:再次查看/18或/17。每个id表达式都是复制捕获的实体的odr use 3.2被转换为对闭包类型的相应未命名数据成员的访问。它被转换为对闭包类型的成员的访问。在此转换之前,它是对原始实体的访问。并且转换不是为了通过引用捕获而进行的。这些仍然作为访问到达作用域中的原始实体。@dyp:所以我们知道,在lambda中,未转换的变量访问是对到达作用域中原始实体的访问。编译器如何通过引用完成此捕获是不受限制的。/17或/18明确说明了不受限制的访问odr使用,也不获取transformed@BenVoigt当然,带有引用类型的变量是“通过引用捕获”的,但该术语的定义没有使我们能够解释当通过引用捕获的变量超出范围时会发生什么。C++11 5.1.2/22是非规范性的,我认为有点混淆,指的是“生存期”一个变量,而不是一个作用域。我的结论是,结果是未定义的行为,只是因为标准未能定义该行为。@Kris:很抱歉,您不同意该规范。很明显,1捕获的实体是引用,而不是引用,2在捕获的对象的生存期后使用lambda未定义ITES已结束。实体是如何通过引用捕获的,而实体本身是引用而不是引用的引用?如果您想要的是指针的副本,则有一个语法,这是我过去六个月答案的一部分。有n个
o事实错误在我的回答中,我提到的到达范围证明了捕获的实体是x而不是i,你在没有证据的情况下陈述了这一点。我们似乎在所有问题上都达成了一致,包括让它工作的安排在C++14中被打破了。很高兴看到C++17中的修复程序,您会注意到我的回答明确地说了一些我希望看到标准澄清的内容。CWG2011问题是否在C++17中经过审查?在中,相关部分现在是8.1.5.211,p。106