std::function 的通用 lambda 不捕获变量

Generic lambda with std::function does not capture variables

我正在尝试使用 C++14 的通用 lambda,但遇到 std::function 的问题。

#include <iostream>
#include <functional>

int main()
{
    const int a = 2;
    std::function<void(int)> f = [&](auto b) { std::cout << a << ", " << b << std::endl; };
    f(3);
}

编译失败并显示一条错误消息说 error: ‘a’ was not declared in this scope

如果我把它改成(int b)就可以了。

这是一个错误吗?还是我遗漏了什么?

我使用的 GCC 版本是 4.9.2。

int main() {
    const int a = 2;
    auto f = [&](auto b) { std::cout << a << ", " << b << std::endl; };
    f(3);
}

不知道它是否应该与 std::function 一起使用,但这肯定有效。

进一步调查:

我创建了一个 class 来尽可能地模仿 lambda:

class Functor {
private:
  int const x;

public:
  Functor() : x{24} {}
  auto operator()(int b) const -> void { cout << x << " " << b << endl; }
};


std::function<auto(int)->void> f2 = Functor{};
f2(3); // <- this works

这表明您的示例应该有效。毕竟 lambda 与具有 operator() 重载的对象和捕获变量的字段的行为相同。

如果我们将 class 更改为 auto 部分:

这行不通:

class Functor {
private:
  int const x;

public:
  Functor() : x{24} {}
  auto operator()(auto b) const -> void { cout << x << " " << b << endl; }
};

std::function<auto(int)->void> f2 = Functor{}; // <-- doesn't work

然而这有效:

class Functor {
private:
  int const x;

public:
  Functor() : x{24} {}
  template <class T>
  auto operator()(T b) const -> void { cout << x << " " << b << endl; }
};

std::function<auto(int)->void> f2 = Functor{}; // <-- this works

所以它很可能与使用 auto 作为 lambda/functions 的参数有关,这是 C++14 的新功能,因此很可能没有成熟的实现。

除非我执行以下任一操作,否则我可以重现此内容:

  • a
  • 中删除 const
  • 捕获列表
  • 中的名称a
  • std::function<void(int)> 更改为 auto
  • 通过将 auto b 更改为 int b
  • 使 lambda 非泛型
  • 使用 Clang(例如 v3.5.0)

我认为这是一个与优化相关的编译器错误,并且未能在通用 lambda 中检测到 odr-use(尽管有趣的是设置 -O0 有没有效果)。它可能与 bug 61814 有关,但我不认为它 完全 是同一回事,因此:

我已经提出来了 GCC bug 64791.

  • (更新:此错误已在 GCC 5.0 中标记为已修复。)

当然,我在 C++14 的措辞中找不到任何明显的禁止您的代码的内容,尽管在新的 C++14 措辞中一般 "obvious" 很少。 :(


[C++14: 5.1.2/6]: [..] For a generic lambda with no lambda-capture, the closure type has a public non-virtual non-explicit const conversion function template to pointer to function. The conversion function template has the same invented template-parameter-list, and the pointer to function has the same parameter types, as the function call operator template. [..]

[C++14: 5.1.2/12]: A lambda-expression with an associated capture-default that does not explicitly capture this or a variable with automatic storage duration (this excludes any id-expression that has been found to refer to an init-capture's associated non-static data member), is said to implicitly capture the entity (i.e., this or a variable) if the compound-statement:

  • odr-uses (3.2) the entity, or
  • names the entity in a potentially-evaluated expression (3.2) where the enclosing full-expression depends on a generic lambda parameter declared within the reaching scope of the lambda-expression.

[ Example:

void f(int, const int (&)[2] = {}) { } // #1
void f(const int&, const int (&)[1]) { } // #2
void test() {
  const int x = 17;
  auto g = [](auto a) {
    f(x); // OK: calls #1, does not capture x
  };

  auto g2 = [=](auto a) {
    int selector[sizeof(a) == 1 ? 1 : 2]{};
    f(x, selector); // OK: is a dependent expression, so captures x
  };
}

—end example ] All such implicitly captured entities shall be declared within the reaching scope of the lambda expression. [ Note: The implicit capture of an entity by a nested lambda-expression can cause its implicit capture by the containing lambda-expression (see below). Implicit odr-uses of this can result in implicit capture. —end note ]

[C++14: 5.1.2/13]: An entity is captured if it is captured explicitly or implicitly. An entity captured by a lambda-expression is odr-used (3.2) in the scope containing the lambda-expression. [..]