【问题标题】:Why can't a generic lambda call itself, but wrapping it in a class allows it?为什么通用 lambda 不能调用自己,但将它包装在一个类中允许它?
【发布时间】:2017-07-08 09:00:30
【问题描述】:

这是完整的例子:

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
}

为什么用wrapper可以,而直接做会导致如下错误?

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

【问题讨论】:

  • 你没问题的情况应该仍然有一个错误,因为callSelf 有一个捕获,当它不是本地的任何东西时。您将代码简化为引入新错误的示例。
  • @chris 哦,对不起,这里不需要 caputre,正在编辑。但是通过捕获它应该会产生编译时错误或程序格式错误?
  • 错误消息告诉您确切的原因。有什么不清楚的地方?
  • @n.m.:该错误消息根本不清楚。
  • @BenjaminLindley 抱歉,我没有深入思考这条消息。您只需通过auto callself2 = []() { callself2(); } 就可以在没有任何通用 lambda 的情况下获得相同的消息“在扣除‘auto’之前”中的 auto 指的是返回类型。确实不是很清楚。

标签: c++ templates recursion lambda c++14


【解决方案1】:

这实际上相当棘手。您违反的规则在 [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.

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

w(w);

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

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

这个函数与void一起工作(对于工作的一些定义)与我们添加-&gt; void时lambda工作的原因相同。它不再是一个未推断的占位符。我们已经知道返回类型。要获得与 lambda 相同的行为,请将 operator() 的声明更改为 auto

【讨论】:

    【解决方案2】:

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

    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,所以编译出错。

    【讨论】:

    • 如果没有return 语句,推导返回类型的函数为什么不是返回类型void?否则,你怎么能通过推断它来让编译器相信 lambda 具有返回类型 void?看来,如果正文是if(0) return void(); func(func);,它可以工作。但有更好的解决方案吗?
    • 是的,但是为了检查是否有return语句,编译器还是要检查函数体,而且整个函数体必须定义好。由于在函数体中使用的函数的返回类型尚未确定,因此函数体格式错误,编译失败。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-28
    • 2023-04-05
    • 1970-01-01
    相关资源
    最近更新 更多