Capture-Regeln bei Lambda-Funktionen
-
Das "ziemlich eingeschränkt" kann ich in Deinen Beispielen nicht erkennen. Ich sehe da nur Benutzerfehler. Wenn man in C++ Indirektion haben will, muss man das sagen. Das ist hier nicht anders. Wenn sich Funktionsobjekte irgendwelche Zustände teilen sollen, schreit das nach shared_ptr:
auto sp = make_shared<dings>(...); ... [sp](int x){ ... sp->daten ...} ...Du kannst Dich ja für mehr Syntaxzucker stark machen und vorschlagen, wie man das mit dem shared_ptr abkürzen können soll.
-
krümelkacker schrieb:
Wenn man in C++ Indirektion haben will, muss man das sagen.
Ja, das ist mir auch klar, daß ich das mit Smart-Pointern immer erzwingen kann. Ich wollte nur wissen, ob ich die Capturing-Möglichkeiten recht verstehe.
krümelkacker schrieb:
Du kannst Dich ja für mehr Syntaxzucker stark machen und vorschlagen, wie man das mit dem shared_ptr abkürzen können soll.
[@]aka capture by lifetime/location zusätzlich zu[=]und[&]einführen.
-
audacia schrieb:
hustbaer schrieb:
Was hättest du dir erwarte
"Capture by lifetime". Ein lokales Symbol lebt so lange, bis keine Lambda-Funktion mehr darauf verweist.
Da wären wir wieder bei "C++ hat keine echten Closures".
-
Das ist dann aber nicht einfach nur ein capture, sondern verändert die Semantik vorhergehenden Codes. Wie man das mit überladenen operator new und operator delete in Einklang bringen soll, ist nicht trivial. Interessant auch die Frage, wie
std::function<void()> foo(my_type x) { return [@]() { x.foo(); } }verarbeitet werden sollte - der aufrufende Code hätte ja keine Ahnung, dass er die Parameter auf den Heap legen muss.
Wenn du referenzgezählte Heap-Variablen haben willst, ist std::auto_ptr stumpf die falsche Wahl. Ich halte das ehrlich gesagt nicht für ein Problem, und dein Vorschlag würde ohne erkennbaren Gewinn eine Reihe echter Probleme einführen.
-
audacia schrieb:
krümelkacker schrieb:
Du kannst Dich ja für mehr Syntaxzucker stark machen und vorschlagen, wie man das mit dem shared_ptr abkürzen können soll.
[@]aka capture by lifetime/location zusätzlich zu[=]und[&]einführen.Das @ gehört leider nicht zum source code character set. Bitte denke den Vorschlag auch zu Ende. Du musst ja nicht gleich etwas druckreifes abgeben, aber ein bissel mehr Gedanken solltest Du Dir schon gemacht haben. Gibt es vielleicht Einschränkungen darüber, auf welche Variablen man sich da beziehen kann? Wann/wie/wo soll sollen Variablen auf'm Heap angelegt werden? Sollen die schon vorher dort leben. Sollen die kopiert werden beim Erzeugen des Funktionsobjekts? Erzähl mal, was das genau für ein Programmverhalten da sein soll in so einer Situation...
Das Problem scheint zu sein, dass Du das "Closure-Verhalten" einer anderen Sprache, nachahmen willst, die aber völlig anders funktioniert. Ich denke da jetzt mal an Python als Beispiel. Da leben Alle Objekte sowieso im Heap und werden "referenzgezählt". Da kann man an das Funktionsobjekt ohne Probleme alle möglichen Referenzen dranhängen. So funktioniert C++ aber nicht.
-
Bashar schrieb:
Da wären wir wieder bei "C++ hat keine echten Closures".
"Echte Closures" aus "echten Sprachen" werden hinter'm Vorhang mit shared_ptrs implementiert.

Nenn' es wie Du willst ... "C++ closures" sind Objekte, die zusätzliche Dinge, (u.a. schlaue Zeiger) speichern können. Das reicht mir.
-
Bashar schrieb:
Da wären wir wieder bei "C++ hat keine echten Closures".
Das war also damit gemeint.
seldon schrieb:
Interessant auch die Frage, wie
std::function<void()> foo(my_type x) { return [@]() { x.foo(); } }verarbeitet werden sollte - der aufrufende Code hätte ja keine Ahnung, dass er die Parameter auf den Heap legen muss.
Gutes Argument. Gegenvorschlag: mit
[@]kann ich von den Funktionsargumenten nur POD-Typen capturen sowie solche, die by-value übergeben werden und einen rvalue-reference-Kopierkonstruktor haben. Hoppla, da wird's schon kompliziert. Wenn es nicht solche Fehlbildungen wie std::auto_ptr<> gäbe, könnte man auch noch alles erlauben, was by-const-ref oder by-val übergeben wird und einen explizit implementierten, öffentlichen Kopierkonstruktor und Zuweisungsoperator hat, indem man deren Existenz die Absicht entnimmt, Wertsemantik zu implementieren. By-ref (und "by-pointer", was auch unter by-ref fällt) könnte man einfach als Zeiger und mithin als POD betrachten.krümelkacker schrieb:
Das @ gehört leider nicht zum source code character set.
Krümelkacker?

krümelkacker schrieb:
Bitte denke den Vorschlag auch zu Ende. Du musst ja nicht gleich etwas druckreifes abgeben, aber ein bissel mehr Gedanken solltest Du Dir schon gemacht haben.
Na komm, so unqualifiziert ist meine Einlassung jetzt auch nicht, daß du sie von so weit oben abbügeln mußt
Ich wollte nur ergründen, ob ich die Capture-Regeln richtig verstehe. Ich habe auch gar nicht vor, sie zu ändern; du wolltest einen Änderungsvorschlag sehen.krümelkacker schrieb:
Gibt es vielleicht Einschränkungen darüber, auf welche Variablen man sich da beziehen kann?
Muß es offenbar; s.o.
krümelkacker schrieb:
Wann/wie/wo soll sollen Variablen auf'm Heap angelegt werden? Sollen die schon vorher dort leben. Sollen die kopiert werden beim Erzeugen des Funktionsobjekts? Erzähl mal, was das genau für ein Programmverhalten da sein soll in so einer Situation...
Wenn ich in einem Funktions-Scope eine Lambda-Funktion mit
[@]-Capturing verwende, werden alle solchermaßen referenzierten Locals, von den Funktionsargumenten abgesehen, in einem gemeinsam referenzgezählten Blob auf dem Heap angelegt. Wenn nicht, dann nicht.krümelkacker schrieb:
Das Problem scheint zu sein, dass Du das "Closure-Verhalten" einer anderen Sprache, nachahmen willst, die aber völlig anders funktioniert.
Fast richtig, bis aufs Nachahmen. Ich wollte bestätigt wissen, daß das in C++ nicht so geht, wie ich mir das vorgestellt habe. Veranlassung des Threads war, daß mir eben erst klargeworden ist, daß die Capturing-Regeln von C++ deutlich anders funktionieren, als ich eigentlich dachte. (Bevor sich jemand ob meines Dilettantismus empört: ich habe Lambdas bisher nicht produktiv eingesetzt, weil mein Compiler sie noch nicht versteht. Ich habe also noch keinen Code für den Realeinsatz produziert, von dem ich nicht genau weiß, was er tut; ihr könnt also weiter nachts ruhig schlafen.)
Vielleicht hätte ich auf die Verwendung des Wortes "einschränkend" im Eröffnungspost verzichten sollen, denn das schien gewissen C++-Apologeten Anlaß zu sein, vorsichtshalber gleich mit "Benutzerfehler[n]", "ehrlich gesagt nicht für ein Problem" oder mit ironischen Verweisen auf "'echte Sprachen'" zu kontern
Meine Feststellung hat natürlich überhaupt keine Konsequenzen für C++. Du sagst es selbst: "so funktioniert C++ aber nicht". Mir lag daran zu klären, wie es denn funktioniert.
-
krümelkacker schrieb:
"Echte Closures" aus "echten Sprachen" werden hinter'm Vorhang mit shared_ptrs implementiert.
Nein, die Implementationen, die ich mir angesehen habe, kennen kein
shared_ptr.Nenn' es wie Du willst ... "C++ closures" sind Objekte, die zusätzliche Dinge, (u.a. schlaue Zeiger) speichern können. Das reicht mir.
Nein, nenn es einfach nicht Closure. Annonyme Funktionen oder Lambda-Ausdruecke sind besser.
-
audacia schrieb:
Wenn ich in einem Funktions-Scope eine Lambda-Funktion mit
[@]-Capturing verwende, werden alle solchermaßen referenzierten Locals, von den Funktionsargumenten abgesehen, in einem gemeinsam referenzgezählten Blob auf dem Heap angelegt. Wenn nicht, dann nicht.Geht nicht. Denn im lokalen Scope koennen mehr Variablen sein als von der Funktion auf den Stack gepusht wurden. zB eine Memberfunktion einer Klasse kann zB ein einer Lambda Funktion eine Membervariable capturen. Und somit ist das ganze nicht mehr moeglich.
Ich sehe uebrigens absolut kein Problem mit dem aktuellen System. Einfach per & capturen. Die Variable muss eh lange genug existieren - das muss man in C++ eben garantieren. Ansonsten einfach shared_ptr nehmen und fertig.
-
audacia schrieb:
hustbaer schrieb:
Nachdem C++ (die Core-Language) keinerlei GC oder andere automatische Speicherverwaltung hat mit der shared ownership möglich wäre...
Referenzzählung geht immer, oder?
Ne, eben nicht.
Beispiel Parameter wurde ja schon gebracht.
Anderes Beispiel: Member von irgendwelchen grösseren Objekten auf dem Stack.Und selbst wenn du einen Weg findest das zu lösen, und ein nettes Proposal mit Reference-Counting zu formulieren...
Willst du wirklich die Sprache um einen IMO entschieden nicht-trivialen Punkt wie "automatische Shared-Ownership" per Ref-Counting erweitern, nur für Lambda-Expressions?Ich persönlich fände das reichlich beknackt. Vor allem würde ich mich dann ärgern, dass C++ (core-language) etwas mit Lambda-Expressions kann, an das man so nicht drankommt. Nämlich Reference-Counting.
-
Shade Of Mine schrieb:
Geht nicht. Denn im lokalen Scope koennen mehr Variablen sein als von der Funktion auf den Stack gepusht wurden. zB eine Memberfunktion einer Klasse kann zB ein einer Lambda Funktion eine Membervariable capturen. Und somit ist das ganze nicht mehr moeglich.
Dafür muss die Lambda sowieso den this-Pointer capturen. Allerdings würde es zu argen Problemen kommen, wenn das refernezierte Objekt dann plötzlich ge[@]-captured würde, weil sich die Referenzzählung dann möglicherweise mit der eines eventuell Objektbesitzenden shared_ptr beißt...
-
knivil schrieb:
krümelkacker schrieb:
"Echte Closures" aus "echten Sprachen" werden hinter'm Vorhang mit shared_ptrs implementiert.
Nein, die Implementationen, die ich mir angesehen habe, kennen kein
shared_ptr.Du hast meinen Smiley ignoriert.
knivil schrieb:
Nenn' es wie Du willst ... "C++ closures" sind Objekte, die zusätzliche Dinge, (u.a. schlaue Zeiger) speichern können. Das reicht mir.
Nein, nenn es einfach nicht Closure. Annonyme Funktionen oder Lambda-Ausdruecke sind besser.
Zu spät. (1) Das Wort "closure" taucht mehrfach im kommenden C++ Standard auf. (2) anonyme Funktion impliziert für mich "zustandslos" -- ist mir daher zu wenig.
-
Shade Of Mine schrieb:
Geht nicht. Denn im lokalen Scope koennen mehr Variablen sein als von der Funktion auf den Stack gepusht wurden. zB eine Memberfunktion einer Klasse kann zB ein einer Lambda Funktion eine Membervariable capturen. Und somit ist das ganze nicht mehr moeglich.
Der
this-Zeiger wird, sofern erforderlich, by-value referenziert (als Zeiger, wohlgemerkt). Schon geht's.Shade Of Mine schrieb:
Ansonsten einfach shared_ptr nehmen und fertig.
Ja, habe ich mittlerweile auch gelernt, danke

hustbaer schrieb:
Anderes Beispiel: Member von irgendwelchen grösseren Objekten auf dem Stack.
Das Ding liegt ja dann auf dem Heap, nicht auf dem Stack.
hustbaer schrieb:
Willst du wirklich die Sprache um einen IMO entschieden nicht-trivialen Punkt wie "automatische Shared-Ownership" per Ref-Counting erweitern, nur für Lambda-Expressions?
Ich persönlich fände das reichlich beknackt. Vor allem würde ich mich dann ärgern, dass C++ (core-language) etwas mit Lambda-Expressions kann, an das man so nicht drankommt. Nämlich Reference-Counting.
Darüber kann man streiten. Ich gebe aber zu, daß es deutlich schöner ist, so eine Implementation vorzunehmen in einer Umgebung, die schon über nennenswerte intrinsische Speicherverwaltungsmechanismen verfügt.
pumuckl schrieb:
Allerdings würde es zu argen Problemen kommen, wenn das refernezierte Objekt dann plötzlich ge[@]-captured würde, weil sich die Referenzzählung dann möglicherweise mit der eines eventuell Objektbesitzenden shared_ptr beißt...
Auf
thissollte sich das Closure nicht lebensverlängernd auswirken - wie auch, ist ja nur ein Zeiger.krümelkacker schrieb:
knivil schrieb:
krümelkacker schrieb:
"Echte Closures" aus "echten Sprachen" werden hinter'm Vorhang mit shared_ptrs implementiert.
Nein, die Implementationen, die ich mir angesehen habe, kennen kein
shared_ptr.Du hast meinen Smiley ignoriert.
Krümel um Krümel?

-
audacia schrieb:
hustbaer schrieb:
Anderes Beispiel: Member von irgendwelchen grösseren Objekten auf dem Stack.
Das Ding liegt ja dann auf dem Heap, nicht auf dem Stack.
Ach du willst immer das äusserste Objekt automatisch in den Heap verfrachten?
Was machst du mit Parametern? Woher soll die aufrufende Funktion wissen dass das Objekt mit Ref-Counting angelegt werden muss?
Was machst du wenn Member von "*this" Referenziert werden?
Im Endeffekt läuft es darauf hinaus, dass du entweder ein ganz greisliche Bastellösung bekommst, in der es 100 Ausnahmen gibt was alles nicht erlaubt ist, und 100 komische Effekte, mit denen keiner rechnet.
Oder aber du bastelst C++ in eine Sprache um, die - nicht nur für Lambda-Expressions - ne automatische Speicherverwaltung hat. Entweder klassisch mit GC und nicht deterministischer Finalisierung. Oder halt mit Ref-Counting (und allen sich darauf ergebenden Nachteilen), dafür aber mit deterministischer Finalisierung.
Vermutlich wäre es aber einfacher, wenn du stattdessen gleich C++/CLI oder D nimmst. Oder eine der anderen, "klassischen" GC-Sprachen.
-
hustbaer schrieb:
audacia schrieb:
hustbaer schrieb:
Anderes Beispiel: Member von irgendwelchen grösseren Objekten auf dem Stack.
Das Ding liegt ja dann auf dem Heap, nicht auf dem Stack.
Ach du willst immer das äusserste Objekt automatisch in den Heap verfrachten?
Ja. Alle (oder auch: alle direkt oder indirekt durch Capturing betroffenen) lokalen Variablen einer Funktion, in der
[@]verwendet wird, werden auf dem Heap angelegt.hustbaer schrieb:
Was machst du mit Parametern? Woher soll die aufrufende Funktion wissen dass das Objekt mit Ref-Counting angelegt werden muss?
Hatten wir oben. Das
[@]-Capturing von Parametern muß man offenbar restringieren.hustbaer schrieb:
Was machst du wenn Member von "*this" Referenziert werden?
Habe ich auch schon gesagt:
thisby-value kopieren (den Zeiger!).hustbaer schrieb:
Im Endeffekt läuft es darauf hinaus, dass du entweder ein ganz greisliche Bastellösung bekommst, in der es 100 Ausnahmen gibt was alles nicht erlaubt ist, und 100 komische Effekte, mit denen keiner rechnet.
Stimmt. Nicht daß das mit
[=]und[&]irgendwie anders wäre
hustbaer schrieb:
Oder aber du bastelst C++ in eine Sprache um, die - nicht nur für Lambda-Expressions - ne automatische Speicherverwaltung hat.
So weit wollen wir im Rahmen dieses Gedankenexperiments lieber nicht gehen

hustbaer schrieb:
Vermutlich wäre es aber einfacher, wenn du stattdessen gleich C++/CLI oder D nimmst. Oder eine der anderen, "klassischen" GC-Sprachen.
Oder wenn ich mich einfach damit abfinde und das beste daraus mache.
-
Stimmt. Nicht daß das mit
[=]und[&]irgendwie anders wäre ;)[/quote]
Nö, du kannst alles per value capturen, was kopierbar ist, und auf alles ne Referenz halten, was erreichbar ist. Ob du das jetzt per Lambda oder mit ner selbstgeschraubten lokalen Klasse machst, ist wurscht. Lambdas haben in der Hinsicht nichts eingeführt, was man ohne sie nicht hinbekommen hätte (in C++0x), sie sind nur syntaktischer Zucker. Die [@]-Capture hätte aber nicht nur die Sprache selbst sondern auch das Speichermodell grundlegend verändert, das spielt also wirklich in einer ganz anderen Liga.