簡體   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