如何解决为什么使用代码的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
是显而易见的候选人,但是float
和double
也是一样。您不能指望编译器会考虑所有可能的返回类型并选择最佳候选者。
要解决此问题,而不要使用三元表达式,请使用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 举报,一经查实,本站将立刻删除。