C++ 11 lambda返回lambda

Ali*_*232 22 c++ lambda visual-c++ c++11 visual-studio-2012

这段代码并不是JS开发人员所不知道的

function get_counter()
{
    return (
        function() {
            var c = 0;
            return function() { return ++c; };
        })();
}
Run Code Online (Sandbox Code Playgroud)

它基本上创建了一个创建不同的枚举器.所以我想知道在使用新的lambda语义的C++ 11中是否可以做同样的事情?我最终写了这篇C++,遗憾的是它不能编译!

int main()
{
    int c;
    auto a = [](){
        int c = 0;
        return [&](){
            cout << c++;
        };
    };
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

所以我想知道是否有一个解决方法来编译它,如果有编译器如何使这个代码正确运行?我的意思是它必须创建单独的枚举器,但它也应该收集垃圾(未使用的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
Run Code Online (Sandbox Code Playgroud)

eca*_*mur 20

你的代码有一个错误,它包含一个悬空参考; 的c引用将是指在外部拉姆达局部变量,当外拉姆达返回其将被破坏.

你应该使用mutableby-value lambda capture 来编写它:

auto a = []() {
    int c = 0;
    return [=]() mutable {
        cout << c++;
    };
};
Run Code Online (Sandbox Code Playgroud)

这依赖于标准后扩展,允许在返回类型推导lambda中使用多个语句; 如果包含多个语句,是否有理由不允许lambda推断返回类型?解决它的最简单方法是提供一个参数,以便lambda只包含一个语句:

auto a = [](int c) {
    return [=]() mutable {
        cout << c++;
    };
};
Run Code Online (Sandbox Code Playgroud)

不幸的是,lambdas中不允许使用默认参数,因此您必须将其称为a(0).或者以可读性为代价,您可以使用嵌套的lambda调用:

auto a = []() {
    return ([](int c) {
        return [=]() mutable {
            cout << c++;
        };
    })(0);
};
Run Code Online (Sandbox Code Playgroud)

这种方法的工作方式是,当a执行内部lambda 时,将所有引用的变量复制到其闭包类型的实例中,这里的内容类似于:

struct inner_lambda {
    int c;
    void operator()() { cout << c++; }
};
Run Code Online (Sandbox Code Playgroud)

然后,外部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};
    }
};
Run Code Online (Sandbox Code Playgroud)

如果您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};
    }
};
Run Code Online (Sandbox Code Playgroud)

inner_lambda::c是对本地参数变量的悬空引用c.


Ste*_*sop 9

一旦变量不再存在,通过引用捕获的lambda不再使用捕获的变量,这是C++的一个自然限制.因此,即使你将它编译,你也不能从它出现的函数返回这个lambda(这也恰好是一个lambda,但这是无关紧要的),因为自动变量c在返回时被销毁.

我认为你需要的代码是:

return [=]() mutable {
    cout << c++;
};
Run Code Online (Sandbox Code Playgroud)

我没有测试它,我不知道什么编译器版本支持它,但这是一个按值捕获,mutable也就是说可以通过lambda修改捕获的值.

因此,每次打电话给a你时,都会得到一个不同的计数器,其计数从0开始.每次调用该计数器时,它都会增加自己的副本c.据我所知Javascript(不远),这就是你想要的.


Chr*_*ica 7

我认为问题在于编译器无法推断出外部lambda(分配给a)的返回类型,因为它包含的不仅仅是简单的一行返回.但不幸的是,也没有办法明确说明内部lambda的类型.所以你必须返回一个std::function,它带来一些额外的开销:

int main()
{
    int c;
    auto a = []() -> std::function<void()> {
        int c = 0;
        return [=]() mutable {
            std::cout << c++;
        };
    };
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

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

但我认为VC至少会尝试推断内部lambda的返回类型,并意识到它返回一个可调用的.但是它以某种方式错误地假设这个内部lambda没有捕获(或者它们无法确定内部lambda的类型),所以它们只是使外部lambda返回一个简单的函数指针,如果内部lambda不会抓住什么.

编辑:和他的评论中的ecatmur状态一样,std::function在创建实际get_counter函数(而不是lambda)时返回a 甚至是必要的,因为正常函数没有任何自动返回类型推导.