为什么不能通用lambda调用本身,但将它包装在类中允许它?

时间:2017-02-18 22:49:19

标签: c++ templates recursion lambda 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
}

为什么包装器可以直接执行,导致以下错误?

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);};
                                  ~~~~^~~~~~

2 个答案:

答案 0 :(得分:16)

这实际上非常棘手。您正在执行的规则是[dcl.spec.auto]:

  

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

这就是错误:

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

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

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

但是,当你 wrap lambda时,你会得到不同的行为。这一行:

w(w);

wrapper类型的对象有效地传递给lambda。那不是它自己的类型。 lambda的主体调用该对象本身,但我们知道该表达式的类型。你声明了它:

template<class T>
void operator()(T& func) {
~~~~~

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

答案 1 :(得分: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
}

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