繁体   English   中英

C++11 lambda 返回 lambda

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM