[英]C++11 lambda returning lambda
这段代码对于 JS 开发者来说并不是什么陌生的东西
function get_counter()
{
return (
function() {
var c = 0;
return function() { return ++c; };
})();
}
它基本上创建了一个,它创建了不同的枚举器。 所以我想知道是否可以使用新的 lambda 语义在 C++11 中完成同样的事情? 我最终编写了这段 C++,不幸的是它不能编译!
int main()
{
int c;
auto a = [](){
int c = 0;
return [&](){
cout << c++;
};
};
return 0;
}
所以我想知道是否有一种解决方法可以编译它,如果有编译器如何使此代码正确运行? 我的意思是它必须创建单独的枚举器,但它也应该收集垃圾(未使用的 c 变量)。
顺便说一下,我使用的是 VS2012 编译器,它会生成此错误:
Error 2 error C2440: 'return' : cannot convert from 'main::<lambda_10d109c73135f5c106ecbfa8ff6f4b6b>::()::<lambda_019decbc8d6cd29488ffec96883efe2a>' to 'void (__cdecl *)(void)' c:\users\ali\documents\visual studio 2012\projects\test\test\main.cpp 25 1 Test
您的代码有一个错误,因为它包含一个悬空引用; c
引用将引用外部 lambda 中的局部变量,当外部 lambda 返回时,该变量将被销毁。
您应该使用mutable
按值 lambda 捕获来编写它:
auto a = []() {
int c = 0;
return [=]() mutable {
cout << c++;
};
};
这依赖于后标准扩展以允许在返回类型推导的 lambda 中使用多个语句; 如果包含多个语句,是否有理由不允许 lambda 推断返回类型? 修复它的最简单方法是提供一个参数,以便 lambda 只包含一个语句:
auto a = [](int c) {
return [=]() mutable {
cout << c++;
};
};
不幸的是,lambdas 中不允许使用默认参数,因此您必须将其称为a(0)
。 或者,以可读性为代价,您可以使用嵌套的 lambda 调用:
auto a = []() {
return ([](int c) {
return [=]() mutable {
cout << c++;
};
})(0);
};
它的工作方式是,当a
执行内部 lambda 时, a
所有引用的变量复制到其闭包类型的实例中,这里是这样的:
struct inner_lambda {
int c;
void operator()() { cout << c++; }
};
闭包类型的实例然后由外部 lambda 返回,并且可以被调用并在调用时修改其c
副本。
总的来说,您的(固定)代码被转换为:
struct outer_lambda {
// no closure
struct inner_lambda {
int c; // by-value capture
// non-const because "mutable"
void operator()() { cout << c++; }
}
// const because non-"mutable"
inner_lambda operator()(int c) const {
return inner_lambda{c};
}
};
如果您将c
作为按引用捕获保留,则将是:
struct outer_lambda {
// no closure
struct inner_lambda {
int &c; // by-reference capture
void operator()() const { cout << c++; } // const, but can modify c
}
inner_lambda operator()(int c) const {
return inner_lambda{c};
}
};
这里的inner_lambda::c
是对局部参数变量c
的悬空引用。
C++ 的一个自然限制是,一旦变量不再存在,通过引用捕获的 lambda 就不能再使用捕获的变量。 所以即使你让它编译,你也不能从它出现的函数中返回这个 lambda(它也恰好是一个 lambda,但这无关紧要),因为自动变量c
在返回时被销毁。
我认为您需要的代码是:
return [=]() mutable {
cout << c++;
};
我没有测试过它,我不知道哪些编译器版本支持它,但这是一个按值捕获, mutable
的说捕获的值可以由 lambda 修改。
因此,每次调用a
都会得到一个不同的计数器,其计数从 0 开始。每次调用该计数器时,它都会增加自己的c
副本。 据我了解 Javascript(不远),这就是你想要的。
我认为问题在于编译器无法推导出外部 lambda(分配给a
)的返回类型,因为它包含的不仅仅是一个简单的一行返回。 但不幸的是,也没有办法明确说明内部 lambda 的类型。 所以你将不得不返回一个std::function
,它带有一些额外的开销:
int main()
{
int c;
auto a = []() -> std::function<void()> {
int c = 0;
return [=]() mutable {
std::cout << c++;
};
};
return 0;
}
当然,您必须按价值捕获,就像史蒂夫在他的回答中已经解释的那样。
编辑:至于为什么确切的错误是它无法将返回的内部 lambda 转换为void(*)()
(指向void()
函数的指针),我只有一些猜测,因为我对他们的 lambda 实现没有太多了解,我不确定它是否稳定或符合标准。
但我认为 VC 至少尝试推断内部 lambda 的返回类型并意识到它返回一个可调用的。 但是后来它以某种方式错误地假设这个内部 lambda 没有被捕获(或者他们无法确定内部 lambda 的类型),所以他们只是让外部 lambda 返回一个简单的函数指针,如果内部 lambda 不会,这确实可以工作捕捉任何东西。
编辑:就像ecatmur在他的评论中所说的那样,在创建实际的get_counter
函数(而不是 lambda)时,甚至需要返回std::function
,因为普通函数没有任何自动返回类型推导。
您应该知道的第一件事是,即使您获得了要编译的语法,其语义也是不同的。 在通过引用捕获的 C++ lambda 中,只捕获一个普通引用,这不会延长由该引用绑定的对象的生命周期。 也就是说, c
的生命周期绑定到封闭 lambda 的生命周期:
int main()
{
int c;
auto a = [](){
int c = 0;
return [&](){
return ++c;
};
}(); // Note: added () as in the JS case
std::cout << a() << a();
return 0;
}
添加丢失的()
以便评估外部 lambda 后,您的问题是在评估完整表达式后,返回的 lambda 中通过引用持有的c
不再有效。
话虽如此,以额外的动态分配为代价(这相当于 JS 的情况)使其工作并不太复杂:
int main()
{
int c;
auto a = [](){
std::shared_ptr<int> c = std::make_shared<int>(0);
return [=](){
return ++(*c);
};
}(); // Note: added () as in the JS case
std::cout << a() << a();
return 0;
}
那应该编译并按预期工作。 每当内部 lambda 被释放( a
超出范围)计数器将从内存中释放。
这适用于 g++ 4.7
#include <iostream>
#include <functional>
std::function<int()> make_counter() {
return []()->std::function<int()> {
int c=0;
return [=]() mutable ->int {
return c++ ;
};
}();
}
int main(int argc, char * argv[]) {
int i = 1;
auto count1= make_counter();
auto count2= make_counter();
std::cout << "count1=" << count1() << std::endl;
std::cout << "count1=" << count1() << std::endl;
std::cout << "count2=" << count2() << std::endl;
std::cout << "count1=" << count1() << std::endl;
std::cout << "count2=" << count2() << std::endl;
return 0;
}
Valgrind 对此完全没有抱怨。 每次我调用 make_counter 时,valgrind 都会报告一个额外的分配和释放,所以我假设 lambda 元编程代码正在为变量 c 插入内存的分配代码(我想我可以检查调试器)。 我想知道这是否符合 Cxx11 或只是特定于 g++。 Clang 3.0 不会编译它,因为它没有 std::function (也许我可以尝试使用 boost 函数)。
我知道这已经晚了,但是在 C++14 及更高版本中,您现在可以初始化 lambda 捕获,从而生成更简单的代码:
auto a = []() {
return [c=0]() mutable {
cout << c++;
};
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.