2015-12-01 59 views
4

編譯遞歸拉姆達和捕獲(段錯誤)

g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010

片段1&捕獲)

#include <functional> 

int main() 
{ 
    std::function<void()> func = [&]() { 
    func(); 
    }; 
    return 0; 
} 

片段2func捕獲)

#include <functional> 

int main() 
{ 
    std::function<void()> func = [func]() { 
    func(); 
    }; 
    return 0; 
} 

這兩個代碼片段編譯,但爲什麼運行第二個分段錯誤的結果?

+2

第二個片段複製一個尚未初始化的值,因此它尚未「活着」。 –

+0

@Alex函數沒有被調用,所以它只應該創建lambda函數,然後退出。 –

+1

@KerrekSB啊!因此,使用'&func'可以工作,因爲它只複製lambda函數的地址?直到現在我還不明白捕獲是否真的像函數參數。 –

回答

3

捕獲發生在構建std::function之前。

所以你捕捉std::function<void()> func未初始化(甚至不是默認的構造!)副本。所有其本身捕獲的std::function都是UB(在構建它之前複製一個變量!),並且調用它將甚至是「更多UB」(調用非構造的std::function的副本!)。

的參考情況捕獲參考func,它被初始化之前也被允許的,只要它是隻使用一次初始化。

缺點到參考情況是拉姆達僅保留的func的範圍內有效。一旦func超出範圍,它的副本也是無效的。根據我的經驗,這很糟糕。

要做到真正的「充滿力量」遞歸拉姆達,你需要像Y型組合子。

這裏是短一個C++ 14的y組合子:

template<class F> 
auto y_combinate(F&& f) { 
    return [f = std::forward<F>(f)](auto&&...args) { 
    return f(f, decltype(args)(args)...); 
    }; 
} 

傳遞給它的是期望到自身的引用作爲第一個參數的λ:

std::function<void()> func = y_combinate([](auto&& self) { 
    self(self); 
    } 
); 

和它的休息。

的y組合子的要求是因爲你沒有一個lambda體內訪問自己的this。所以我們添加一個。

上述的y組合子是隻有90%的,因爲它不能處理R/L值和完全通過了功能對象的常量性。但它大部分時間都會用到。

這是一個稍微好一點的y combinate:

template<class F> 
struct y_combinate_t { 
    F f; 
    template<class...Args> 
    decltype(auto) operator()(Args&&...args)const { 
    return f(*this, std::forward<Args>(args)...); 
    } 
}; 
template<class F> 
y_combinate_t<std::decay_t<F>> y_combinate(F&& f) { 
    return {std::forward<F>(f)}; 
} 

這使得使用好一點:

std::function<void()> func = y_combinate([](auto&& self) { 
    self(); 
    } 
); 

調用時在現已通過了self不必傳遞self本身。