C++11 lambda returning lambda

C++11 lambda returning lambda

本文关键字:lambda returning C++11      更新时间:2023-10-16

这段代码对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:usersalidocumentsvisual studio 2012projectstesttestmain.cpp   25  1   Test

您的代码有一个错误,因为它包含一个悬空引用;c引用将引用外部lambda中的局部变量,当外部lambda返回时,该局部变量将被销毁。

您应该使用mutable按值lambda捕获来编写它:

auto a = []() {
int c = 0;
return [=]() mutable {
cout << c++;
};
};

这依赖于后标准扩展,以允许在推导lambda的返回类型中有多个语句;如果lambdas包含多个语句,那么不允许它推导返回类型有什么原因吗?修复它的最简单方法是提供一个参数,使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时,将所有引用的变量复制到其闭包类型的实例中,这里的情况类似于:

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

这里CCD_ 7是对局部参数变量CCD_。

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

当然,你必须通过价值来捕捉,就像史蒂夫·已经在他的回答中解释的那样。

EDIT:至于为什么确切的错误是它无法将返回的内部lambda转换为void(*)()(指向void()函数的指针),我只有一些猜测,因为我对它们的lambda实现没有太多了解,我不确定它是否稳定或符合标准。

但我认为VC至少试图推导内部lambda的返回类型,并意识到它返回了一个可调用的。但是,它以某种方式错误地假设内部lambda没有捕获(或者他们无法确定内部lambda的类型),所以他们只是让外部lambda返回一个简单的函数指针,如果内部lambda不捕获任何东西,这确实会起作用。

EDIT:就像ecatmur在他的评论中所说的那样,在生成实际的get_counter函数(而不是lambda)时,返回std::function甚至是必要的,因为正常函数没有任何自动返回类型推导。

您应该知道的第一件事是,即使您获得了要编译的语法,语义也是不同的。在C++lambdas中,通过引用捕获只捕获一个普通引用,这不会延长该引用绑定的对象的生存期。也就是说,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::函数(也许我可以尝试使用boost函数)。

我知道这已经很晚了,但在C++14及以后的版本中,您现在可以初始化lambda捕获,从而生成更简单的代码:

auto a = []() {
return [c=0]() mutable {
cout << c++;
};
};