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

Generic lambda with std::function does not capture variables

本文关键字:变量 lambda std function 带有      更新时间:2023-10-16

我正在尝试使用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。

除非我执行以下任何操作,否则我可以重现它:

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

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

我已将其作为GCC错误64791提出。

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

当然,我在 C++14 措辞中找不到任何明显的东西应该不允许你的代码,尽管在新的 C++14 措辞中通常很少有"明显"。 :(


[C++14: 5.1.2/6]: [..]对于没有 lambda 捕获的通用 lambda,闭包类型具有指向函数指针的公共非虚拟非显式 const 函数模板。转换函数模板具有与函数调用运算符模板相同的发明模板参数列表,并且指向函数的指针具有相同的参数类型。[..]

[C++14: 5.1.2/12]: 具有关联捕获默认值lambda 表达式未显式捕获此变量或具有自动存储持续时间的变量(这不包括已发现引用 init-capture 的关联非静态数据成员的任何 id 表达式this),如果复合语句

  • ODR 使用 (3.2) 实体,或
  • 在可能计算的表达式 (3.2) 中命名实体,其中封闭的完整表达式依赖于在 lambda 表达式的到达范围内声明的通用 lambda 参数。

[ 示例:

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
  };
}

—结束示例 ]所有此类隐式捕获的实体都应在 lambda 表达式的到达范围内声明。[ 注意:嵌套 lambda 表达式对实体的隐式捕获可能会导致包含的 lambda 表达式隐式捕获实体(见下文)。对此的隐式 odr 使用可能会导致隐式捕获。—尾注 ]

[C++14: 5.1.2/13]: 如果显式或隐式捕获实体,则会捕获该实体。由 lambda 表达式捕获的实体在包含 lambda 表达式的作用域中使用 (3.2)。[..]

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

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

进一步调查:

我创建了一个类来尽可能模仿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()重载的对象和捕获变量的字段相同。

如果我们更改类以进入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/函数的参数有关,这是 C++14 的新功能,因此很可能没有成熟的实现。