(1)中lambda表达式的类型是什么?
为什么这段代码可以编译?
#include<functional>
#include<iostream>
int main() {
std::cout <<
[](auto&& f0,auto&& a0){return f0(f0,a0);}
(
[](auto& f,auto&& a)->int{ return (a>1) ? f(f,a-1)*a : 1; }, // (1)
5
)
<< std::endl;
}
我认为无限递归是由此代码中lambda表达式(1)的类型推断引起的。
我认为auto& f
被替换为类型名称,例如std::function<int(std::function<int(std::function<int(......)>)>)>
。
请指出我的错误。
答案 0 :(得分:3)
第一个错误:std::function
是一种与任何lambda无关的类型。
lambda是一种匿名类型,具有operator()
和一些其他已知属性。
std::function<R(Args...)>
是复制构造的类型擦除类,使用Args...
销毁和调用并返回R
。它可以用lambda构造,但不是相关的类型。
由于您无法命名lambda的类型,因此使用std::function
存储它很常见。然而,lambda不是std::function
。 std::function
在类型擦除和多态性方面几乎不可避免地产生了开销:lambdas缺乏任何多态性,这使编译器很容易理解()
在调用时的作用。
在你的情况下,你有两个lambdas。
你的第一个lambda是:
[](auto&& f0,auto&& a0){return f0(f0,a0);}
这看起来像是y-combinator或变体的一种形式,用于递归的帮助。在这种情况下operator()
有签名:
template<class F0, class A0>
auto operator()(F0&&,A0&&)const
-> std::result_of_t<F0&(F0&,A0&)>
大致。
更有用的版本(在我看来)是:
[](auto&& f0){
return [f0=std::forward<decltype(f0)>(f0)]
(auto&&...args) {
return f0(f0, std::forward<decltype(args)>(args)...);
};
}
接受f0
,存储它,并使用先传递f0
的任何参数调用它。这可以让你将递归“看不见”。使内部lambda mutable
是可选的(取决于你是否想要在const
上下文中调用)
无论如何,下一个lambda:
[](auto& f,auto&& a)->int{ return (a>1) ? f(f,a-1)*a : 1; }
的operator()
签名为:
template<class F, class A>
auto operator()(F&,A&&)const
-> int
然后将第二个lambda的实例传递给第一个lambda,加上一个参数,然后计算n!
。
template
运算符()
推导出的类型不依赖于参数本身推导出的类型,因此没有无限类型推导问题。内部lambda的返回类型被硬编码为int
,因此您不必推断()
递归返回以知道它返回int
。
但是,如果你想将第一个lambda存储在std::function
中,你会感到很失望。 std::function
无法删除template operator()
:它只能删除固定的签名,template
成员是方法的工厂,而不是方法本身。
但是,请记住我上面的y组合的更好版本吗?
调用你的第一个lambda g
,你的第二个h
和我的lambda y
以及lambda我的lambda返回z
。
然后g(h,x)
= y(h)(x)
- 和y(h)
可以存储在std::function<int(int)>
没问题。我们隐藏了基本上需要递归类型签名的递归部分,std::function
不支持 1 。剩下的内容虽然有template operator()
,但可以绑定到一个简单的签名。
1 请注意,您可以编写std::function
来支持递归签名,例如std::function< std::vector<SELF_TYPE>(int) >
。您可以看到这对boost::variant
如何使用递归变体的方式有用。
答案 1 :(得分:1)
来自[expr.prim.lambda],强调我的:
在 lambda返回类型为
auto
,由 trailing-return-type 替换,如果提供和/或从 返回7.1.6.4中描述的语句。
您提供了 trailing-return-type ,即代码中的->int
,因此不必进行类型扣除。返回类型只是int
。
但是,即使没有->int
,如果您只提供if
语句而不是使用条件运算符,仍然可以编译函数:
auto f = [](auto& f0, auto&& a) {
if (a <= 1) {
return 1; // this *must* be the first return case.
}
else {
return f0(f0, a-1) * a;
}
};
std::cout << f(f, 5) << std::endl; // prints 120
这种情况,只有这种情况符合上述§7.1.6.4[dcl.spec.auto]中提到的规则之一:
如果需要具有undeduced占位符类型的实体类型来确定表达式的类型, 该计划格式不正确。但是,在函数中看到return语句后,返回类型 从该语句中推导出的可用于函数的其余部分,包括其他返回语句 [例如:
auto sum(int i) { if (i == 1) return i; // sum’s return type is int else return sum(i-1)+i; // OK, sum’s return type has been deduced }
-end example]