Die akzeptierte Antwort von Cort Ammon ist gut, aber ich denke, es gibt noch einen wichtigen Punkt in Bezug auf die Implementierbarkeit.
Angenommen, ich habe zwei verschiedene Übersetzungseinheiten, "one.cpp" und "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);
Die beiden Überladungen foo
verwenden denselben Bezeichner ( foo
), haben jedoch unterschiedliche verstümmelte Namen. (In dem Itanium ABI, das auf POSIX-ähnlichen Systemen verwendet wird, sind die verstümmelten Namen _Z3foo1A
und in diesem speziellen Fall _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) {}
Der C ++ - Compiler muss sicherstellen, dass der verstümmelte Name von void foo(A1)
in "two.cpp" mit dem verstümmelten Namen von extern void foo(A2)
in "one.cpp" übereinstimmt, damit wir die beiden Objektdateien miteinander verknüpfen können. Dies ist die physikalische Bedeutung von zwei Typen, die "der gleiche Typ" sind: Es geht im Wesentlichen um die ABI-Kompatibilität zwischen separat kompilierten Objektdateien.
Der C ++ - Compiler muss dies nicht sicherstellen B1
und B2
ist "vom gleichen Typ". (Tatsächlich muss sichergestellt werden, dass es sich um verschiedene Typen handelt. Dies ist derzeit jedoch nicht so wichtig.)
Was physikalischer Mechanismus ist an der Compiler Verwendung sicherzustellen , dass A1
und A2
ist „die gleiche Art“?
Es gräbt sich einfach durch typedefs und betrachtet dann den vollständig qualifizierten Namen des Typs. Es ist ein Klassentyp namens A
. (Nun, ::A
da es sich im globalen Namespace befindet.) Es ist also in beiden Fällen der gleiche Typ. Das ist leicht zu verstehen. Noch wichtiger ist, dass es einfach zu implementieren ist . Um festzustellen, ob zwei Klassentypen vom gleichen Typ sind, nehmen Sie ihre Namen und führen a aus strcmp
. Um einen Klassentyp in den verstümmelten Namen einer Funktion zu zerlegen, schreiben Sie die Anzahl der Zeichen in den Namen, gefolgt von diesen Zeichen.
So sind benannte Typen leicht zu entstellen.
Was physikalischer Mechanismus könnte der Compiler verwenden , um sicherzustellen , dass B1
und B2
ist „die gleiche Art“ , in einer hypothetischen Welt , in der C ++ sie benötigte die gleiche Art zu sein?
Nun, es kann nicht den Namen des Typs verwenden, da der Typ nicht funktioniert hat einen Namen.
Vielleicht könnte es irgendwie den Text des Körpers des Lambda verschlüsseln . Aber das wäre etwas umständlich, denn tatsächlich unterscheidet sich das b
in "one.cpp" geringfügig von dem b
in "two.cpp": "one.cpp" hat x+1
und "two.cpp" hat x + 1
. Wir müssten uns also eine Regel ausdenken, die besagt, dass entweder dieser Leerzeichenunterschied keine Rolle spielt oder dass dies der Fall ist (was sie schließlich zu unterschiedlichen Typen macht) oder dass dies möglicherweise der Fall ist (möglicherweise ist die Gültigkeit des Programms durch die Implementierung definiert , oder vielleicht ist es "schlecht geformt, keine Diagnose erforderlich"). Wie auch immer,A
Der einfachste Ausweg aus der Schwierigkeit besteht darin, einfach zu sagen, dass jeder Lambda-Ausdruck Werte eines eindeutigen Typs erzeugt. Dann sind zwei Lambda-Typen, die in verschiedenen Übersetzungseinheiten definiert sind, definitiv nicht der gleiche Typ . Innerhalb einer einzelnen Übersetzungseinheit können wir Lambda-Typen "benennen", indem wir nur vom Anfang des Quellcodes an zählen:
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; };
}
Natürlich haben diese Namen nur innerhalb dieser Übersetzungseinheit eine Bedeutung. Diese TUs $_0
sind immer ein anderer Typ als einige andere TUs $_0
, obwohl diese TUs struct A
immer der gleiche Typ sind wie einige andere TUs struct A
.
Beachten Sie übrigens, dass unsere Idee, den Text des Lambda zu kodieren, ein weiteres subtiles Problem hatte: Lambdas $_2
und $_3
bestehen aus genau demselben Text , aber sie sollten eindeutig nicht als der gleiche Typ betrachtet werden!
By the way, hat C ++ die Compiler erforderlich zu wissen , wie Sie den Text eines beliebigen C ++ mangle Ausdruck , wie in
template<class T> void foo(decltype(T())) {}
template void foo<int>(int);
Für C ++ muss der Compiler (noch) nicht wissen, wie eine beliebige C ++ - Anweisung entstellt wird . decltype([](){ ...arbitrary statements... })
ist auch in C ++ 20 noch schlecht geformt.
Beachten Sie auch, dass es einfach ist , einem unbenannten Typ mit / einen lokalen Alias zu geben . Ich habe das Gefühl, dass Ihre Frage möglicherweise durch den Versuch entstanden ist, etwas zu tun, das auf diese Weise gelöst werden könnte.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);
}
BEARBEITET ZUM HINZUFÜGEN: Wenn Sie einige Ihrer Kommentare zu anderen Antworten lesen, fragen Sie sich, warum
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)>);
Das liegt daran, dass Captureless Lambdas standardmäßig konstruierbar sind. (In C ++ nur ab C ++ 20, aber konzeptionell war es immer wahr.)
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);
Wenn Sie es versuchen würden default_construct_and_call<decltype(&add1)>
, t
wäre dies ein standardmäßig initialisierter Funktionszeiger, und Sie würden wahrscheinlich einen Segfault ausführen. Das ist nicht nützlich.