Dies ist etwas, das mich als Merkmal von C ++ - Lambda-Ausdrücken immer nervt: Der Typ eines C ++ - Lambda-Ausdrucks ist einzigartig und anonym, ich kann ihn einfach nicht aufschreiben. Selbst wenn ich zwei Lambdas erstelle, die syntaktisch genau gleich sind, werden die resultierenden Typen als unterschiedlich definiert. Die Folge ist, dass a) Lambdas nur an Vorlagenfunktionen übergeben werden können, mit denen die Kompilierungszeit, der unaussprechliche Typ zusammen mit dem Objekt übergeben werden kann, und b) dass Lambdas nur dann nützlich sind, wenn sie über gelöscht werden std::function<>
.
Ok, aber so macht es C ++, ich war bereit, es als lästiges Feature dieser Sprache abzuschreiben. Ich habe jedoch gerade erfahren, dass Rust anscheinend dasselbe tut: Jede Rust-Funktion oder jedes Lambda hat einen eindeutigen, anonymen Typ. Und jetzt frage ich mich: Warum?
Meine Frage lautet also:
Was ist aus Sicht des Sprachdesigners der Vorteil, das Konzept eines einzigartigen, anonymen Typs in eine Sprache einzuführen?
quelle
std::function
. Ein Lambda, das an eine Vorlagenfunktion übergeben wurde, kann direkt ohne Beteiligung aufgerufen werdenstd::function
. Der Compiler kann dann das Lambda in die Vorlagenfunktion einbinden, wodurch die Laufzeiteffizienz verbessert wird.{ int i = 42; auto foo = [&i](){ return i; }; } { int i = 13; auto foo = [&i](){ return i; }; }
da die Variable, auf die er sich bezieht, unterschiedlich ist, obwohl sie textlich identisch sind. Wenn Sie nur sagen, dass sie alle einzigartig sind, müssen Sie sich keine Sorgen machen, um es herauszufinden.lambdas_type = decltype( my_lambda);
[](auto) {}
? Sollte es zunächst einen Typ haben?Antworten:
Viele Standards (insbesondere C ++) verfolgen den Ansatz, die Anforderungen an Compiler zu minimieren. Ehrlich gesagt verlangen sie schon genug! Wenn sie nichts angeben müssen, damit es funktioniert, neigen sie dazu, die Implementierung definiert zu lassen.
Wären Lambdas nicht anonym, müssten wir sie definieren. Dies muss viel darüber aussagen, wie Variablen erfasst werden. Betrachten Sie den Fall eines Lambda
[=](){...}
. Der Typ müsste angeben, welche Typen tatsächlich vom Lambda erfasst wurden, was nicht trivial zu bestimmen sein könnte. Was ist auch, wenn der Compiler eine Variable erfolgreich optimiert? Erwägen:static const int i = 5; auto f = [i]() { return i; }
Ein optimierender Compiler könnte leicht erkennen, dass der einzig mögliche Wert
i
davon 5 ist, und diesen durch ersetzenauto f = []() { return 5; }
. Wenn der Typ jedoch nicht anonym ist, kann dies den Typ ändern oder den Compiler dazu zwingen, weniger zu optimieren und zu speicherni
, obwohl er ihn nicht wirklich benötigt. Dies ist eine ganze Menge Komplexität und Nuancen, die für das, was Lambdas tun sollten, einfach nicht benötigt werden.Und für den Fall, dass Sie tatsächlich einen nicht anonymen Typ benötigen, können Sie die Abschlussklasse jederzeit selbst erstellen und mit einem Funktor anstelle einer Lambda-Funktion arbeiten. Auf diese Weise können Lambdas den 99% -Fall bearbeiten und Sie können Ihre eigene Lösung in 1% codieren.
Deduplicator wies in Kommentaren darauf hin, dass ich die Einzigartigkeit weniger ansprach als die Anonymität. Ich bin mir der Vorteile der Einzigartigkeit weniger sicher, aber es ist erwähnenswert, dass das folgende Verhalten klar ist, wenn die Typen eindeutig sind (die Aktion wird zweimal instanziiert).
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; });
Wenn die Typen nicht eindeutig wären, müssten wir angeben, welches Verhalten in diesem Fall auftreten soll. Das könnte schwierig sein. Einige der Fragen, die zum Thema Anonymität aufgeworfen wurden, erheben in diesem Fall auch ihren hässlichen Kopf für die Einzigartigkeit.
quelle
Lambdas sind nicht nur Funktionen, sie sind eine Funktion und ein Zustand . Daher implementieren sowohl C ++ als auch Rust sie als Objekt mit einem Aufrufoperator (
operator()
in C ++ die 3Fn*
Merkmale in Rust).Grundsätzlich
[a] { return a + 1; }
in C ++ Desugars zu so etwas wiestruct __SomeName { int a; int operator()() { return a + 1; } };
Verwenden Sie dann eine Instanz, in der
__SomeName
das Lambda verwendet wird.Während in Rust, wird
|| a + 1
in Rust zu so etwas desugar{ 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 } }
Dies bedeutet, dass die meisten Lambdas unterschiedliche Arten haben müssen .
Nun gibt es einige Möglichkeiten, wie wir das tun können:
Fn*
Merkmalen in Rust verwendet werden können. Keine der beiden Sprachen zwingt Sie jemals dazu, Lambdas zu löschen, um sie zu verwenden (mitstd::function
in C ++ oderBox<Fn*>
in Rust).Beachten Sie auch , dass beide Sprachen nicht darüber einig , dass trivial Lambdas , die nicht capture Kontext tun können zu Funktionszeiger umgewandelt werden.
Das Beschreiben komplexer Funktionen einer Sprache mit einfacheren Funktionen ist weit verbreitet. Zum Beispiel haben sowohl C ++ als auch Rust Range-for-Schleifen und beide beschreiben sie als Syntaxzucker für andere Funktionen.
C ++ definiert
for (auto&& [first,second] : mymap) { // use first and second }
als äquivalent zu
{ init-statement auto && __range = range_expression ; auto __begin = begin_expr ; auto __end = end_expr ; for ( ; __begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }
und Rust definiert
for <pat> in <head> { <body> }
als äquivalent zu
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>); } } };
Die für einen Menschen komplizierter erscheinen, für einen Sprachdesigner oder einen Compiler jedoch einfacher sind.
quelle
std::function
std::function
(Hinzufügen zu Caleths Antwort, aber zu lang, um in einen Kommentar zu passen.)
Der Lambda-Ausdruck ist nur syntaktischer Zucker für eine anonyme Struktur (ein Voldemort-Typ, da Sie seinen Namen nicht sagen können).
Sie können die Ähnlichkeit zwischen einer anonymen Struktur und der Anonymität eines Lambda in diesem Codefragment sehen:
#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"; }
Wenn dies für ein Lambda immer noch unbefriedigend ist, sollte es für eine anonyme Struktur ebenfalls unbefriedigend sein.
Einige Sprachen ermöglichen eine etwas flexiblere Art der Ententypisierung, und obwohl C ++ Vorlagen enthält, die nicht wirklich dazu beitragen, ein Objekt aus einer Vorlage zu erstellen, die ein Mitgliedsfeld enthält, das ein Lambda direkt ersetzen kann, anstatt ein zu verwenden
std::function
Verpackung.quelle
int& operator()(){ return x; }
diese Strukturen sogar ergänzenauto foo(){ struct DarkLord {} tom_riddle; return tom_riddle; }
, weil außerhalb vonfoo
nichts die Kennung verwendet werden kannDarkLord
Weil es Fälle gibt, in denen Namen irrelevant und nicht nützlich oder sogar kontraproduktiv sind. In diesem Fall ist die Fähigkeit, ihre Existenz zu abstrahieren, nützlich, weil sie die Namensverschmutzung verringert und eines der beiden schwierigen Probleme in der Informatik löst (wie man Dinge benennt). Aus dem gleichen Grund sind temporäre Objekte nützlich.
Die Einzigartigkeit ist keine besondere Lambda-Sache oder sogar eine besondere Sache für anonyme Typen. Dies gilt auch für benannte Typen in der Sprache. Beachten Sie Folgendes:
struct A { void operator()(){}; }; struct B { void operator()(){}; }; void foo(A);
Beachten Sie, dass ich nicht passieren kann
B
infoo
, obwohl die Klassen identisch sind. Dieselbe Eigenschaft gilt für unbenannte Typen.Es gibt eine dritte Option für eine Teilmenge von Lambdas: Nicht erfassende Lambdas können in Funktionszeiger konvertiert werden.
Beachten Sie, dass die Lösung einfach ist, wenn die Einschränkungen eines anonymen Typs für einen Anwendungsfall ein Problem darstellen: Stattdessen kann ein benannter Typ verwendet werden. Lambdas tun nichts, was mit einer benannten Klasse nicht möglich ist.
quelle
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".
// 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);
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
.)// 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) {}
Der C ++ - Compiler muss sicherstellen, dass der verstümmelte Name von
void foo(A1)
in "two.cpp" mit dem verstümmelten Namen vonextern 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
undB2
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
undA2
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 ausstrcmp
. 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
undB2
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 demb
in "two.cpp": "one.cpp" hatx+1
und "two.cpp" hatx + 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 = [](){}; // 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 }
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 TUsstruct A
immer der gleiche Typ sind wie einige andere TUsstruct 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); // _Z3fooIiEvDTcvT__EE, not _Z3fooIiEvT_
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; }; } // 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); }
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.quelle
C ++ - Lambdas benötigen unterschiedliche Typen für unterschiedliche Operationen, da C ++ statisch bindet. Sie können nur kopiert / verschoben werden, sodass Sie ihren Typ meistens nicht benennen müssen. Aber das ist alles ein Implementierungsdetail.
Ich bin nicht sicher, ob C # -Lambdas einen Typ haben, da es sich um "anonyme Funktionsausdrücke" handelt, und sie werden sofort in einen kompatiblen Delegatentyp oder Ausdrucksbaumtyp konvertiert. Wenn dies der Fall ist, handelt es sich wahrscheinlich um einen unaussprechlichen Typ.
C ++ hat auch anonyme Strukturen, wobei jede Definition zu einem eindeutigen Typ führt. Hier ist der Name nicht unaussprechlich, er existiert einfach nicht, was den Standard betrifft.
C # verfügt über anonyme Datentypen , die es sorgfältig verhindern, aus dem von ihnen definierten Bereich zu entkommen. Die Implementierung gibt auch diesen einen eindeutigen, unaussprechlichen Namen.
Ein anonymer Typ signalisiert dem Programmierer, dass er nicht in seiner Implementierung herumstöbern sollte.
Beiseite:
Du kannst einem Lambda-Typ einen Namen geben.
auto foo = []{}; using Foo_t = decltype(foo);
Wenn Sie keine Captures haben, können Sie einen Funktionszeigertyp verwenden
void (*pfoo)() = foo;
quelle
Foo_t = []{};
, nurFoo_t = foo
und sonst nichts.Warum anonyme Typen verwenden?
Bei Typen, die vom Compiler automatisch generiert werden, können Sie entweder (1) die Anforderung eines Benutzers nach dem Namen des Typs berücksichtigen oder (2) den Compiler einen eigenen auswählen lassen.
Im ersteren Fall wird vom Benutzer erwartet, dass er jedes Mal, wenn ein solches Konstrukt angezeigt wird, explizit einen Namen angibt (C ++ / Rust: wann immer ein Lambda definiert ist; Rust: wann immer eine Funktion definiert ist). Dies ist ein mühsames Detail, das der Benutzer jedes Mal angeben muss, und in den meisten Fällen wird der Name nie wieder erwähnt. Daher ist es sinnvoll, den Compiler automatisch einen Namen für ihn ermitteln zu lassen und vorhandene Funktionen wie
decltype
oder Typinferenz zu verwenden, um den Typ an den wenigen Stellen zu referenzieren, an denen er benötigt wird.Im letzteren Fall muss der Compiler einen eindeutigen Namen für den Typ auswählen, bei dem es sich wahrscheinlich um einen unklaren, unlesbaren Namen handelt, z
__namespace1_module1_func1_AnonymousFunction042
. Der Sprachdesigner könnte genau angeben, wie dieser Name in prächtigen und feinen Details aufgebaut ist, aber dies legt dem Benutzer unnötig ein Implementierungsdetail offen, auf das sich kein vernünftiger Benutzer verlassen kann, da der Name selbst bei geringfügigen Refaktoren zweifellos spröde ist. Dies schränkt auch die Entwicklung der Sprache unnötig ein: Zukünftige Funktionserweiterungen können dazu führen, dass sich der vorhandene Algorithmus zur Namensgenerierung ändert, was zu Abwärtskompatibilitätsproblemen führt. Daher ist es sinnvoll, dieses Detail einfach wegzulassen und zu behaupten, dass der automatisch generierte Typ für den Benutzer nicht aussprechbar ist.Warum eindeutige (unterschiedliche) Typen verwenden?
Wenn ein Wert einen eindeutigen Typ hat, kann ein optimierender Compiler einen eindeutigen Typ über alle Verwendungsseiten hinweg mit garantierter Genauigkeit verfolgen. Als Konsequenz kann der Benutzer dann sicher sein, an welchen Stellen die Herkunft dieses bestimmten Werts dem Compiler vollständig bekannt ist.
In dem Moment, in dem der Compiler Folgendes sieht:
let f: __UniqueFunc042 = || { ... }; // definition of __UniqueFunc042 (assume it has a nontrivial closure) /* ... intervening code */ let g: __UniqueFunc042 = /* some expression */; g();
Der Compiler hat das volle Vertrauen,
g
das unbedingt entstehen mussf
, ohne die Herkunft von zu kenneng
. Dies würde es ermöglichen, den Anrufg
zu devirtualisieren. Der Benutzer würde dies auch wissen, da der Benutzer sehr darauf geachtet hat, den einzigartigen Typ desf
durch den Datenfluss, der dazu führte, beizubehalteng
.Dies schränkt notwendigerweise ein, was der Benutzer tun kann
f
. Dem Benutzer steht es nicht frei zu schreiben:let q = if some_condition { f } else { || {} }; // ERROR: type mismatch
da dies zur (illegalen) Vereinigung zweier unterschiedlicher Typen führen würde.
Um dies zu umgehen, kann der Benutzer den
__UniqueFunc042
auf den nicht eindeutigen Typ übertragen&dyn Fn()
.let f2 = &f as &dyn Fn(); // upcast let q2 = if some_condition { f2 } else { &|| {} }; // OK
Der Kompromiss, den diese Art des Löschens eingeht, besteht darin, dass Verwendungen
&dyn Fn()
die Argumentation für den Compiler erschweren. Gegeben:let g2: &dyn Fn() = /*expression */;
Der Compiler muss das sorgfältig prüfen,
/*expression */
um festzustellen, ob esg2
von einerf
oder mehreren anderen Funktionen stammt und unter welchen Bedingungen diese Herkunft gilt. Unter vielen Umständen kann der Compiler aufgeben: Vielleicht kann der Mensch erkennen, dass diesg2
wirklichf
in allen Situationen der Fall ist, aber der Pfad vonf
zug2
war zu kompliziert, als dass der Compiler ihn entschlüsseln könnte, was zu einem virtuellen Aufruf von führteg2
mit pessimistischer Leistung führte.Dies wird deutlicher, wenn solche Objekte an generische (Vorlagen-) Funktionen geliefert werden:
fn h<F: Fn()>(f: F);
Wenn man
h(f)
wo anruftf: __UniqueFunc042
,h
ist man auf eine eindeutige Instanz spezialisiert:Auf diese Weise kann der Compiler speziellen Code für
h
das jeweilige Argumentf
und den Versand an generierenf
ist höchstwahrscheinlich statisch, wenn nicht inline.Im umgekehrten Fall, wo man Anrufe
h(f)
mitf2: &Fn()
, dieh
instanziiert alsh::<&Fn()>(f);
welches von allen Funktionen des Typs geteilt wird
&Fn()
. Von innenh
weiß der Compiler sehr wenig über eine undurchsichtige Funktion des Typs&Fn()
und konnte daher nur konservativf
mit einem virtuellen Versand aufrufen . Um statisch zu versenden, müsste der Compiler den Aufrufh::<&Fn()>(f)
an seiner Aufrufstelle einbinden, was nicht garantiert werden kann, wenn erh
zu komplex ist.quelle
void(*)(int, double)
möglicherweise keinen Namen, aber ich kann ihn aufschreiben. Ich würde es einen namenlosen Typ nennen, keinen anonymen Typ. Und ich würde kryptisches Zeug wie__namespace1_module1_func1_AnonymousFunction042
Name Mangling nennen, was definitiv nicht im Rahmen dieser Frage liegt. Bei dieser Frage geht es um Typen, von denen der Standard garantiert, dass sie nicht aufgeschrieben werden können, anstatt eine Typensyntax einzuführen, die diese Typen auf nützliche Weise ausdrücken kann.Erstens kann Lambda ohne Erfassung in einen Funktionszeiger konvertiert werden. Sie bieten also irgendeine Form von Großzügigkeit.
Warum können Lambdas mit Capture nicht in Zeiger konvertiert werden? Da die Funktion auf den Status des Lambda zugreifen muss, muss dieser Status als Funktionsargument angezeigt werden.
quelle
std::function<>
.Um Namenskollisionen mit dem Benutzercode zu vermeiden.
Sogar zwei Lambdas mit derselben Implementierung haben unterschiedliche Typen. Was in Ordnung ist, weil ich auch für Objekte verschiedene Typen haben kann, selbst wenn deren Speicherlayout gleich ist.
quelle
int (*)(Foo*, int, double)
besteht kein Risiko einer Namenskollision mit dem Benutzercode.void(*)(void)
invoid*
und zurück konvertieren.