C++11的lambda表达式能捕获文件作用域变量吗?

5

ISO C++11规范的第5.1.2节p10规定:

捕获列表中的标识符使用未限定名称查找(3.4.1)的常规规则进行查找;每次这种查找都应在局部Lambda表达式所处的可达范围内找到一个带有自动存储期的变量。如果实体(即变量或this指针)出现在Lambda表达式的捕获列表中,则称其为显式捕获。

这似乎意味着Lambda无法捕获文件作用域变量。例如,下面的程序是非法的:

#include <iostream>

int x = 13;

int main()
{
  auto l = [](){ return x; };

  std::cout << l() << std::endl;

  return 0;
}

然而,g++ 4.7.1却输出了我所期望的结果:
$ g++ --version
g++ (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ g++ -std=c++11 lambda.cpp 
$ ./a.out 
13

但是clang 3.0会崩溃:

$ clang --version
Ubuntu clang version 3.0-6ubuntu3 (tags/RELEASE_30/final) (based on LLVM 3.0)
Target: i386-pc-linux-gnu
Thread model: posix
$ clang -std=c++11 lambda.cpp 
0  libLLVM-3.0.so.1 0xb70a59e8
1  libLLVM-3.0.so.1 0xb70a5f34
2                   0xb775d400 __kernel_sigreturn + 0
3  clang            0x0869f2e9 clang::Sema::DeduceAutoType(clang::TypeSourceInfo*, clang::Expr*, clang::TypeSourceInfo*&) + 73
<snip>

我的程序是否非法?如果非法的话,捕获文件作用域变量的禁令是基于什么理由的?


8
你的例子中并没有捕获变量 x,只是使用了它。毕竟它是全局变量(所以你可以使用它,因为它是可见和可访问的)。参见这个问题 - dyp
1
如果这是非法的,那么禁止捕获文件作用域变量的理由是什么?您只需要捕获自动变量,因此您只能捕获自动变量。请参见https://dev59.com/nGYr5IYBdhLWcg3wQIC-#13827928。 - dyp
谢谢您的解释!如果您将您的评论整合成一个简短的答案,我会接受它。 - Jared Hoberock
2
clang 崩溃的原因是因为在 3.1 版本之前,clang 对 lambda 的支持并不完整。在具备完整 lambda 支持的版本中,这个问题可以得到解决。 - bames53
1个回答

5

为了进行名称查找,lambda表达式的主体被视为在lambda表达式的上下文中。也就是说,名称查找的发生好像在lambda表达式外使用该名称一样。详见[expr.prim.lambda]/7。例如:

#include <iostream>

int x = 13;
int y = 0;

int main()
{
  static int y = 42;
  int z = 1729;

  auto l = [/*forget about the capture for a moment*/]()
  { return x+y+z; };
  // find the names x,y,z as if they had been mentioned outside the lambda
  // find the locals y, z and the global x

  std::cout << l() << std::endl;

  return 0;
}

现在,您需要捕获自动存储期变量。我猜这样可以使它更少出错,因为您可以复制并返回lambda,以便在调用lambda时自动变量已被销毁:

int main()
{
  static int y = 42;

  std::function<int()> f;
  {
    int z = 1729;

    f = [](){ return x+y+z; }; // imagine we could do this
  }

  std::cout << f() << std::endl; // uh-oh!

  return 0;
}

当然,具有静态存储期的变量不会出现这个问题。
具体而言,[expr.prim.lambda]/12说:
如果lambda表达式或通用lambda函数调用运算符模板的实例化使用odr(3.2)来引用其作用域内的“this”或自动存储期变量,则必须由lambda表达式捕获该实体。
非自动变量也可以通过名称查找找到,但不受此规则影响。您可以在未捕获它们的情况下使用它们。
注意:odr-use松弛规定允许一些对自动变量的使用而无需捕获它们,例如:
int main()
{
  int x = 42;
  constexpr int y = 1789;

  auto l = []() -> int
  {
      decltype(x) my_var = 100;  // no odr-use of `x`
      return my_var*y;           // no odr-use of `y`
  };

  std::cout << l() << std::endl;

  return 0;
}

实际上,如果捕获是按值进行的话,你的“uh-oh”就没问题了。我认为标准没有必要指定一个默认值,因为你可以通过添加一个字符来明确地捕获所有内容。 - Steve Jessop
@SteveJessop 是的,我提到的是不捕获z,类似于不捕获xy。标准可以允许在不捕获它们的情况下访问自动变量,但这会导致生存期问题(以及有关所引用对象位置的等效实现问题)。因此,它们需要被捕获,然后有多种方法可以做到这一点。 - dyp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接