为什么要设计具有唯一匿名类型的语言? 这是一直困扰我的C++ C++ lambda表达式的一个特性:C++ lambda表达式的类型是唯一的和匿名的,我根本无法写下来。即使我创建了两个语法完全相同的lambda,结果类型也被定义为不同的。结果是,a)lambda只能传递给允许将编译时、无法描述的类型与对象一起传递的模板函数,b)lambda只有在通过std::function进行类型擦除后才有用 当然,但是这只是C++的方式,我准备把它写出来,只是语言的一个令人讨厌的特征。然而,我刚刚了解到,Rust的作用似乎是相同的:每个Rust函数或lambda都有一个唯一的匿名类型。现在我想知道:为什么
所以,我的问题是:为什么要设计具有唯一匿名类型的语言? 这是一直困扰我的C++ C++ lambda表达式的一个特性:C++ lambda表达式的类型是唯一的和匿名的,我根本无法写下来。即使我创建了两个语法完全相同的lambda,结果类型也被定义为不同的。结果是,a)lambda只能传递给允许将编译时、无法描述的类型与对象一起传递的模板函数,b)lambda只有在通过std::function进行类型擦除后才有用 当然,但是这只是C++的方式,我准备把它写出来,只是语言的一个令人讨厌的特征。然而,我刚刚了解到,Rust的作用似乎是相同的:每个Rust函数或lambda都有一个唯一的匿名类型。现在我想知道:为什么,c++,types,rust,language-design,C++,Types,Rust,Language Design,所以,我的问题是: 从语言设计者的角度来看,在语言中引入唯一匿名类型的概念有什么好处?首先,没有捕获的lambda可以转换为函数指针。因此,它们提供了某种形式的泛型 现在为什么带有捕获的lambda不能转换为指针?因为函数必须访问lambda的状态,所以这个状态需要作为函数参数出现。 p> C++ +lambdas需要不同类型的不同操作,因为C++静态绑定。它们只是可复制/移动的,所以大多数情况下不需要命名它们的类型。但这些都是一些实现细节 我不确定C#lambda是否有类型,因为它们是“匿名函
从语言设计者的角度来看,在语言中引入唯一匿名类型的概念有什么好处?首先,没有捕获的lambda可以转换为函数指针。因此,它们提供了某种形式的泛型 现在为什么带有捕获的lambda不能转换为指针?因为函数必须访问lambda的状态,所以这个状态需要作为函数参数出现。
auto foo = []{};
using Foo_t = decltype(foo);
如果没有任何捕获,可以使用函数指针类型
void (*pfoo)() = foo;
(补充了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";
}
#包括
#包括
使用std::cout;
int main(){
结构{intx;}foo{5};
结构{intx;}bar{6};
CUT< P> lambdas不只是函数,它们是函数和状态。因此C++和Rig都将它们作为对象调用,在C++中调用操作符(<代码>操作程序)> />代码,3代码> FN*<代码>生锈特性。
基本上,<代码> [a] {C++ +DugARs中的A+1;} /Cube >类似于
struct __SomeName {
int a;
int operator()() {
return a + 1;
}
};
然后使用使用lambda的\uu SomeName
实例
生锈时,|a+1
生锈时会脱胶成类似的东西
struct __SomeName {
int a;
int operator()() {
return a + 1;
}
};
{
结构uu SomeName{
a:i32,
}
为uuu SomeName执行一次{
类型输出=i32;
extern“rust call”fn call_一次(self,args:())->self::Output{
自我评价a+1
}
}
//以及必要时的FnMut和Fn
__SomeName{a}
}
这意味着大多数lambda必须具有不同的类型
现在,我们有几种方法可以做到这一点:
- 使用匿名类型,这是两种语言都实现的。这样做的另一个结果是,所有lambda都必须具有不同的类型。但对于语言设计者来说,这有一个明显的优势:lambda可以简单地使用语言中其他已经存在的较简单部分进行描述。它们只是已经存在的语法糖语言的点点滴滴。
- 用一些特殊的语法来命名lambda类型:但是,由于lambda已经可以与C++中的模板或泛型和
fn*/COD>字符一起使用,所以这不是必要的。语言都不会强迫您使用擦除lambda来使用它们(在C++中使用<代码> STD::函数< />代码>或<代码> box <代码>生锈)。
还请注意,这两种语言都同意,不捕获上下文的普通lambda可以转换为函数指针
使用简单的特征描述语言的复杂特征是很常见的。例如C++和RISE都有循环的范围,它们都把它们描述为其他特征的语法糖。
C++定义
for(自动&&[first,second]:mymap){
//使用第一个和第二个
}
等同于
{
init-statement
auto && __range = range_expression ;
auto __begin = begin_expr ;
auto __end = end_expr ;
for ( ; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
锈决定了一切
{}中的
等同于
{
init-statement
auto && __range = range_expression ;
auto __begin = begin_expr ;
auto __end = end_expr ;
for ( ; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
let result=match::std::iter::IntoIterator::into_iter(){
mut iter=>{
环路{
let=match::std::iter::Iterator::next(&mut-iter){
::std::option::option::Some(val)=>val,
::std::option::option::None=>中断
};
SemiExpr();
}
}
};
对于人类来说,它们似乎更复杂,但对于语言设计者或编译器来说都更简单
为什么要设计具有唯一匿名类型的语言
因为在某些情况下,名称是不相关的,没有用处,甚至没有反作用。在这种情况下,抽象出它们的存在是有用的,因为它减少了名称污染,解决了计算机科学中两个难题之一(如何命名事物)。出于同样的原因,临时对象是有用的
兰姆达
唯一性不是一个特殊的lambda对象,甚至对于匿名类型来说也不是一个特殊的对象
int counter()
{
static int count = 0;
return count++;
}
template <typename FuncT>
void action(const FuncT& func)
{
static int ct = counter();
func(ct);
}
...
for (int i = 0; i < 5; i++)
action([](int j) { std::cout << j << std::endl; });
for (int i = 0; i < 5; i++)
action([](int j) { std::cout << j << std::endl; });
// one.cpp
struct A { int operator()(int x) const { return x+1; } };
auto b = [](int x) { return x+1; };
using A1 = A;
using B1 = decltype(b);
extern void foo(A1);
extern void foo(B1);
// two.cpp
struct A { int operator()(int x) const { return x + 1; } };
auto b = [](int x) { return x + 1; };
using A2 = A;
using B2 = decltype(b);
void foo(A2) {}
void foo(B2) {}
auto a = [](){}; // a has type $_0
auto b = [](){}; // b has type $_1
auto f(int x) {
return [x](int y) { return x+y; }; // f(1) and f(2) both have type $_2
}
auto g(float x) {
return [x](int y) { return x+y; }; // g(1) and g(2) both have type $_3
}
template<class T> void foo(decltype(T())) {}
template void foo<int>(int); // _Z3fooIiEvDTcvT__EE, not _Z3fooIiEvT_
auto f(int x) {
return [x](int y) { return x+y; };
}
// Give the type an alias, so I can refer to it within this translation unit
using AdderLambda = decltype(f(0));
int of_one(AdderLambda g) { return g(1); }
int main() {
auto f1 = f(1);
assert(of_one(f1) == 2);
auto f42 = f(42);
assert(of_one(f42) == 43);
}
int add1(int x) { return x + 1; }
int add2(int x) { return x + 2; }
static_assert(std::is_same_v<decltype(add1), decltype(add2)>);
auto add3 = [](int x) { return x + 3; };
auto add4 = [](int x) { return x + 4; };
static_assert(not std::is_same_v<decltype(add3), decltype(add4)>);
template<class T>
int default_construct_and_call(int x) {
T t;
return t(x);
}
assert(default_construct_and_call<decltype(add3)>(42) == 45);
assert(default_construct_and_call<decltype(add4)>(42) == 46);
let f: __UniqueFunc042 = || { ... }; // definition of __UniqueFunc042 (assume it has a nontrivial closure)
/* ... intervening code */
let g: __UniqueFunc042 = /* some expression */;
g();
let q = if some_condition { f } else { || {} }; // ERROR: type mismatch
let f2 = &f as &dyn Fn(); // upcast
let q2 = if some_condition { f2 } else { &|| {} }; // OK
let g2: &dyn Fn() = /*expression */;
fn h<F: Fn()>(f: F);
h::<__UniqueFunc042>(f);
h::<&Fn()>(f);