Ich habe in einer Vorlage, die @R verwendet, einen gewundenen Code. Martinho Fernandes Trick, um einige gepackte Parameter in einer variadischen Vorlage zu loopen und denselben Code für jedes Argument in der Argumentliste aufzurufen.
Allerdings scheint so zu sein, als ob die Lambdas nicht richtig initialisiert würden und stattdessen Variablen über funktor (?) -Instanzen verteilt würden, was falsch erscheint.
Gegeben dieser Code:
%Vor%Ich bekomme folgende Ausgabe:
%Vor% Ich glaube also, dass die beiden Funktorinstanzen die gleiche Adresse für die erfasste Variable bar
haben, und nach dem Aufruf des ersten Funktors wird bar
auf nullptr
und auf dann der zweite funktor seg'-fault, wenn er versucht, die gleiche bar
-Variable (in der exakt gleichen Adresse) zu dereferenzieren.
Zu Ihrer Information: Ich kann dieses Problem umgehen, indem ich den Funktor [bar](){...
in eine Variable std::function
verschiebe und diese Variable dann erfasse. Ich würde jedoch gerne verstehen, warum die zweite Funktorinstanz die exakt gleiche bar
-Adresse verwendet und warum sie einen nullptr
-Wert erhält.
Ich habe das mit GNUs g ++ gegen ihre Stammversion ausgeführt, die gestern abgerufen und kompiliert wurde.
Parameter Packs mit Lambda in ihnen neigen dazu, Compiler passt zu geben. Eine Möglichkeit, dies zu vermeiden, ist, den Expansionsteil und den Lambda-Teil zu trennen.
%Vor% Dies nimmt ein Lambda f
und gibt ein Objekt zurück, das f
für jedes seiner Argumente aufruft.
Wir können dann foo
neu schreiben, um es zu benutzen:
Ich dachte zuerst, dass es mit dem Konstruktor std::function
zu tun hat. Es tut nicht. Ein einfacheres Beispiel ohne std::function
, das auf die gleiche Weise abstürzt:
wir können den segfault ohne cerr
aufrufen, was uns eine leichter zu lesende Zerlegung gibt:
Ich habe die Disassemblierung noch nicht analysiert, aber offensichtlich zerstört sie den Zustand des zweiten Lambda beim Spielen mit dem ersten.
Zuerst habe ich keine Lösung, ich möchte diese zusätzlichen Informationen als Kommentar hinzufügen, aber leider kann ich noch nichts dazu sagen.
Ich habe Ihren vorherigen Code mit dem Intel 17 C ++ Compiler ausprobiert und es hat gut funktioniert:
%Vor% In einigen Fällen war die &bar
(die Adresse der neuen Variablen, die zum Speichern des erfassten Werts verwendet wurde) unterschiedlich zwischen dem ersten Aufruf und der zweiten, aber es hat auch funktioniert.
Ich habe auch Ihren Code mit GNU's g ++ ausprobiert und dabei den Typ von bar
von int*
auf int
geändert. Der erfasste Wert war in den zweiten und nachfolgenden Aufrufen auch in diesem Fall falsch:
Schließlich habe ich versucht, den Code etwas zu ändern und nach Wert und Objekt zu übergeben, so dass der Kopierkonstruktor aufgerufen werden muss:
%Vor% Meine aktuelle Version von g ++ ( g++ (GCC) 6.1.0
) kann diesen Code nicht kompilieren. Ich habe es auch mit Intel versucht und es hat funktioniert, obwohl ich nicht ganz verstehe, warum der Kopierkonstruktor so oft aufgerufen wird:
Das ist alles, was ich bisher getestet habe.
Nach ein paar Tests stellte ich fest, dass alles über die Bewertung der Lambdas und nicht der Packexpansion ging.
Was Sie haben, ist eine Menge von Lambdas, die erst ausgeführt werden, wenn die Packexpansion abgeschlossen ist, so dass alle zum Zeitpunkt der Ausführung die gleiche Instanz der Variablen beobachten, die sich unterscheiden würden, wenn die Ausführung jedes Lambdas übereinstimmt die Reihenfolge der Erweiterung, dann wird jede Erweiterung ihre eigene Kopie der Variable bekommen und das Lambda würde als materialisierte prvalue
betrachtet werden, deren Lebensdauer abgelaufen ist:
Der Compiler ist jedoch in der Lage, eine kleine Optimierung durchzuführen, selbst wenn ausgewertete Lambdas erweitert werden und nicht ausgeführt werden, während für trivial classes
unter capture by copy
expandiert wird.
Nehmen wir ein einfacheres Beispiel:
%Vor% Gibt die gleiche Adresse von a
für jedes erweiterte Lambda aus, auch wenn Kopien von a
erwartet werden. Da capture by copy
eine konstante Version der kopierten Variablen erzeugt und keine Mutation erstellt werden kann, ist trivial classes
eine Art Leistung, um dieselbe Instanz über alle expandierten Lambdas zu teilen (da keine Änderung garantiert ist).
Aber wenn der Typ jetzt kein trivialer Typ ist, wurde diese Optimierung durchbrochen und für jedes erweiterte Lambda sind verschiedene Kopien erforderlich:
%Vor% Diese Änderung an A
bewirkt, dass in der Ausgabe eine andere Adresse für a
erscheint.
Tags und Links c++ gcc lambda c++11 variadic-templates