2015-12-01 32 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本身。