Cort Ammon接受的答案很好,但是我认为关于可实现性还有一个更重要的观点。
假设我有两个不同的翻译单元“ one.cpp”和“ two.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);
这两个重载foo
使用相同的标识符(foo
),但名称不同。(在POSIX-ish系统上使用的Itanium ABI中,错误的名称为_Z3foo1A
,在这种情况下为_Z3fooN1bMUliE_E
。)
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) {}
C ++编译器必须确保void foo(A1)
“ two.cpp”中的错误名称与“ one.cpp”中的错误名称相同extern void foo(A2)
,以便我们可以将两个目标文件链接在一起。这是“相同类型”这两种类型的物理含义:本质上是关于单独编译的目标文件之间的ABI兼容性。
C ++编译器是不必需的,以确保B1
与B2
属于“相同类型”。(实际上,需要确保它们是不同的类型;但是现在这并不重要。)
什么物理机制并编译使用,以确保A1
和A2
“相同类型”?
它只是遍历typedef,然后查看该类型的完全限定名称。这是一个名为的类类型A
。(好吧,::A
因为它在全局名称空间中。)因此在两种情况下都是相同的类型。这很容易理解。更重要的是,它很容易实现。要查看两个类类型是否相同,请使用它们的名称并执行strcmp
。要将类类型转换为函数的转换名称,请在其名称中写入字符数,然后输入这些字符。
因此,命名类型很容易处理。
什么物理机制可能编译器使用,以确保B1
和B2
“相同类型”,在一个假想的世界里,C ++要求他们具有相同的类型?
那么,它不能使用类型的名称,因为该类型不具有名称。
也许可以以某种方式对lambda主体的文本进行编码。但这有点尴尬,因为实际上b
“ one.cpp”中的in与“ two.cpp”中的有细微差别b
:“ one.cpp”具有x+1
和“ two.cpp”具有x + 1
。因此,我们不得不拿出,说要么这个空白差异的规则没有的事,或者说,它不会(毕竟使它们不同类型的),或者也许它(也许该程序的有效性是指实现的,或者“格式错误,无需诊断”)。无论如何,A
摆脱困难的最简单方法就是说每个lambda表达式都产生唯一类型的值。那么,在不同翻译单元中定义的两个lambda类型肯定不是同一类型。在单个翻译单元中,我们可以通过仅从源代码的开头算起“命名” lambda类型:
auto a = [](){};
auto b = [](){};
auto f(int x) {
return [x](int y) { return x+y; };
}
auto g(float x) {
return [x](int y) { return x+y; };
}
当然,这些名称仅在该翻译单元内具有含义。这恩氏$_0
总是从其他一些TU的不同类型$_0
,即使该TU的struct A
永远是同类型其他一些TU的struct A
。
顺便说一句,请注意我们的“编码lambda文本”的想法还有一个细微的问题:lambda$_2
和$_3
由完全相同的文本组成,但显然不应将它们视为同一类型!
顺便说一句,C ++确实要求编译器知道如何处理任意C ++表达式的文本,例如
template<class T> void foo(decltype(T())) {}
template void foo<int>(int);
但是C ++并不需要编译器知道如何处理任意C ++语句。decltype([](){ ...arbitrary statements... })
即使在C ++ 20中仍然格式不正确。
还要注意,使用/可以为未命名的类型赋予本地别名很容易。我有一种感觉,您的问题可能是由于试图做一些可以解决的事情而引起的。typedef
using
auto f(int x) {
return [x](int y) { return x+y; };
}
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)>);
这是因为不可捕获的lambda是默认可构造的。(仅从C ++ 20开始在C ++中使用,但是从概念上讲,这始终是正确的。)
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);
如果尝试使用default_construct_and_call<decltype(&add1)>
,t
它将是默认初始化的函数指针,并且可能会出现段错误。那样就没用了。