微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么使用代码的Y组合器无法编译?

如何解决为什么使用代码的Y组合器无法编译?

我已经读了三天关于组合器的信息,我终于开始用代码编写它们(更像是从地方复制东西并弄清事物的意思)。

以下是我要运行的一些代码

#include <iostream>
#include <utility>

template <typename Lambda>
class y_combinator {
  private:
    Lambda lambda;
  public:
    template <typename T>
    constexpr explicit y_combinator (T&& lambda)
        : lambda (std::forward <T> (lambda))
    { }

    template <typename...Args>
    decltype(auto) operator () (Args&&... args) {
        return lambda((decltype(*this)&)(*this),std::forward <Args> (args)...);
    }
};

template <typename Lambda>
decltype(auto) y_combine (Lambda&& lambda) {
    return y_combinator <std::decay_t <Lambda>> (std::forward <Lambda> (lambda));
}

int main () {
    auto factorial = y_combine([&] (auto self,int64_t n) {
        return n == 1 ? (int64_t)1 : n * self(n - 1);
    });
    
    int n;
    std::cin >> n;

    std::cout << factorial(n) << '\n';
}

如果我将lambda的返回类型明确声明为-> int64_t,则一切正常。但是,当我删除它时,编译器会抱怨。错误

main.cpp|16|error: use of 'main()::<lambda(auto:11,int64_t)> [with auto:11 = y_combinator<main()::<lambda(auto:11,int64_t)> >; int64_t = long long int]' before deduction of 'auto'

为什么编译器无法弄清楚返回类型并推断出auto?我首先想到的是,也许我需要将... ? 1 : n * self(n - 1)更改为... ? int64_t(1) : n * self(n - 1),以便两个返回值的类型最终都为int64_t,并且不再存在任何歧义。似乎并非如此。我想念什么?

此外,在y_combinator类中,将lambda声明为类型Lambda&&的对象似乎也会引起问题。为什么会这样呢?这仅在我将operator ()重载中的强制类型转换为(decltype(*this)&)而不是std::ref(*this)时发生。他们在做不同的事情吗?

解决方法

类型推导

n == 1 ? (int64_t)1 : n * self(n - 1)的类型取决于self的返回类型,因此无法推论得出。您可能会认为int64_t是显而易见的候选人,但是floatdouble也是一样。您不能指望编译器会考虑所有可能的返回类型并选择最佳候选者。

要解决此问题,而不要使用三元表达式,请使用if-else块:

int main () {
    auto factorial = y_combine([&] (auto self,int64_t n) {
        if (n == 1) {
            return (int64_t)1;
        } else {
            return n * self(n - 1);
        }
    });
    // ...
}

使用此return语句之一不依赖于self的返回类型,因此可以进行类型推导。

在推断函数的返回类型时,编译器将依次查看函数主体中的所有return语句,并尝试推断其类型。如果失败,则会出现编译错误。

使用三元运算符,返回语句return n == 1 ? (int64_t)1 : n * self(n - 1);的类型取决于self的返回类型,目前尚不知道。因此,您会收到编译错误。

在使用if语句和多个return语句时,编译器可以从遇到的第一个语句中推断出返回类型,因为

如果有多个return语句,它们必须全部推导为相同类型。

一旦在函数中看到return语句,从该语句推断出的返回类型就可以在该函数的其余部分中使用,包括在其他return语句中。

,如cppreference所示。这就是为什么

        if (n == 1) {
            return (int64_t)1;
        } else {
            return n * self(n - 1);
        }
可以推断

返回一个int64_t

作为旁注

        if (n == 1) {
            return 1; // no explicit cast to int64_t
        } else {
            return n * self(n - 1);
        }

将无法编译,因为将从第一个return语句的功能返回类型推导为int,从第二个return语句的安全类型推导为int64_t

        if (n != 1) {
            return n * self(n - 1);
        } else {
            return (int64_t)1;
        }

也会失败,因为它在计数器中遇到的第一个return语句取决于函数的返回类型,因此无法推论得出。

第二个问题

发生该错误的原因是,由于lambda的lambda参数,您在调用*this时试图复制auto self。这是由于具有右值引用成员。 (请参见godbolt上的clang错误消息)

要解决此问题,请使用原始问题中提到的std::ref或使lambda具有一个auto &&self参数(并使用std::forward<decltype(self)>(self))。

还要注意,成员Lambda &&lambda是右值引用,因此您只能使用临时或移动的lambda构造y_combinator的实例。在一般情况下,复制函子并不多,标准库也通过复制获取函子参数。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。