为什么一般的lambda不能调用自己,但将它包装在类中允许它?

xin*_*aiz 13 c++ recursion lambda templates c++14

这是完整的例子:

auto callSelf = [](auto& func) {func(func);};

class wrapper : public decltype(callSelf) {
    using base = decltype(callSelf);
public:
    wrapper() : base(callSelf) {}

    template<class T>
    void operator()(T& func) {
        base::operator()(func);
    }
};

int main()
{
    //callSelf(callSelf); // Error
    wrapper w;
    w(w); // OK, nice endless recursion
}
Run Code Online (Sandbox Code Playgroud)

为什么可以使用包装器,而直接执行它会导致以下错误?

main.cpp:30: error: use of '<lambda(auto:1&)> [with auto:1 = <lambda(auto:1&)>]' before deduction of 'auto'
 auto callSelf = [&](auto& func) {func(func);};
                                  ~~~~^~~~~~
Run Code Online (Sandbox Code Playgroud)

Bar*_*rry 16

这实际上非常棘手.你遇到的规则是[dcl.spec.auto]:

如果需要具有未减少占位符类型的实体的类型来确定表达式的类型,则该程序是不正确的.

那就是这里出了问题:

auto callSelf = [](auto& func) {func(func);};
callSelf(callSelf);
Run Code Online (Sandbox Code Playgroud)

我们需要知道callSelf确定表达式类型的类型func(func),它是自动循环的.只需指定返回类型即可轻松解决:

auto callSelf = [](auto& func) -> void {func(func);};
callSelf(callSelf); // ok. I mean, infinite recursion, but otherwise ok. ish.
Run Code Online (Sandbox Code Playgroud)

但是,当你包装 lambda时,你会得到不同的行为.这一行在这里:

w(w);
Run Code Online (Sandbox Code Playgroud)

将一个类型的对象wrapper有效地传递给lambda.那不是它自己的类型.lambda的主体调用该对象本身,但我们知道该表达式的类型.你宣布它:

template<class T>
void operator()(T& func) {
~~~~~
Run Code Online (Sandbox Code Playgroud)

这个函数适用于(对于某些工作的定义),void因为lambda在我们添加时工作的原因相同-> void.它不再是一个不受限制的占位符.我们已经知道了返回类型.要获得与lambda相同的行为,请将声明更改operator()auto.


Gui*_*cot 8

在您的情况下,只需定义返回类型,编译器应接受它:

auto callSelf = [](auto& func) -> void {func(func);};

class wrapper : public decltype(callSelf) {
    using base = decltype(callSelf);
public:
    wrapper() : base(callSelf) {}

    template<class T>
    void operator()(T& func) {
        base::operator()(func);
    }
};

int main()
{
    callSelf(callSelf); //works
    wrapper w;
    w(w); //ok, nice endless recursion
}
Run Code Online (Sandbox Code Playgroud)

使用返回类型推导,编译器不能在lambda本身中使用lambda,因为编译器必须查看函数体以推导返回类型.编译器必须检查函数体的事实使它看到使用lambda本身的lambda的内容.由于编译器处于演绎过程中,因此无法使用lambda,因此编译错误.