作为C++lambda表达式的一个特性,这一直困扰着我:C++lambda表达式的类型是唯一的和匿名的,我根本无法将其写下来。 即使我创建了两个语法上完全相同的lambda,生成的类型也被定义为不同的。 其结果是,a)lambda只能传递给允许编译时间,不可描述的类型与对象一起传递的模板函数,b)lambda只有在通过std::function<>
进行类型擦除后才有用。
好的,但这只是C++的方式,我已经准备好把它作为那种语言的一个讨厌的特性来写了。 然而,我刚刚了解到Rust似乎也是这么做的:每个Rust函数或lambda都有一个唯一的匿名类型。 现在我在想:为什么?
所以,我的问题是:
从语言设计者的角度来看,在语言中引入唯一,匿名类型的概念有什么好处?
Lambdas不仅仅是功能,它是一种功能,也是一种状态。 因此,C++和Rust都将它们实现为一个带有调用运算符的对象(C++中的operator()
,Rust中的3个fn*
特征)。
基本上,在C++中,[a]{return a+1;}
可以将类似于
struct __SomeName {
int a;
int operator()() {
return a + 1;
}
};
然后使用__somename
的实例,其中使用lambda。
在锈蚀状态下,a+1
在锈蚀状态下将会去糖,类似于
{
struct __SomeName {
a: i32,
}
impl FnOnce<()> for __SomeName {
type Output = i32;
extern "rust-call" fn call_once(self, args: ()) -> Self::Output {
self.a + 1
}
}
// And FnMut and Fn when necessary
__SomeName { a }
}
这意味着大多数lambdas必须具有不同的类型。
现在,我们有几种方法可以做到这一点:
fn*
特征一起使用。 这两种语言都不会强制您键入删除lambdas来使用它们(在C++中使用std::function
或在Rust中使用box
)。还要注意,两种语言都同意,不捕获上下文的琐碎lambda可以转换为函数指针。
使用简单的特性描述语言的复杂特性是非常常见的。 例如,C++和Rust都有range-for循环,它们都将它们描述为其他特性的语法糖。
C++定义
for (auto&& [first,second] : mymap) {
// use first and second
}
相当于
{
init-statement
auto && __range = range_expression ;
auto __begin = begin_expr ;
auto __end = end_expr ;
for ( ; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
而Rust定义了
for <pat> in <head> { <body }
相当于
let result = match ::std::iter::IntoIterator::into_iter(<head>) {
mut iter => {
loop {
let <pat> = match ::std::iter::Iterator::next(&mut iter) {
::std::option::Option::Some(val) => val,
::std::option::Option::None => break
};
SemiExpr(<body>);
}
}
};
虽然它们对人类来说似乎更复杂,但对语言设计者或编译器来说都更简单。
(补充了Caleth的回答,但太长了,不适合评论。)
lambda表达式只是匿名结构(一种伏地魔类型,因为你不能说出它的名字)的语法糖。
您可以在以下代码片段中看到匿名结构和lambda的匿名性之间的相似性:
#include <iostream>
#include <typeinfo>
using std::cout;
int main() {
struct { int x; } foo{5};
struct { int x; } bar{6};
cout << foo.x << " " << bar.x << "\n";
cout << typeid(foo).name() << "\n";
cout << typeid(bar).name() << "\n";
auto baz = [x = 7]() mutable -> int& { return x; };
auto quux = [x = 8]() mutable -> int& { return x; };
cout << baz() << " " << quux() << "\n";
cout << typeid(baz).name() << "\n";
cout << typeid(quux).name() << "\n";
}
如果这对于lambda仍然不满意,那么对于匿名结构也应该同样不满意。
有些语言允许一种更灵活的duck typing,尽管C++的模板并不能真正帮助从具有成员字段的模板生成对象,该成员字段可以直接替换lambda,而不是使用std::function
包装器。
C++lambda需要唯一的类型,因为它们是对象,而不是函数。 它们只是可复制/移动构造的,所以大多数情况下不需要命名它们的类型。 但这都是一些实现细节。
我不确定C#lambda是否有一个类型,因为它们是“匿名函数表达式”,并且它们会立即转换为兼容的委托类型或表达式树类型。 如果是这样,那可能是一种难以发音的类型。
C++也有匿名结构,其中每个定义导致一个唯一的类型。 在这里,这个名字并不难听,它只是不存在,就标准而言。
C#具有匿名数据类型,它小心地禁止这些数据类型从定义的作用域中逃逸。 该实现还为这些对象提供了一个唯一的,难以发音的名称。
使用匿名类型向程序员发出信号,他们不应该在实现中四处查看。
旁白:
您可以为lambda的类型命名。
auto foo = []{};
using Foo_t = decltype(foo);
如果没有任何捕获,可以使用函数指针类型
void (*pfoo)() = foo;