C ++ Lambda nicht Variable auf 2. Erweiterung in Vorlage erfassen?

8

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.

    
Ross Rogers 24.08.2016, 16:19
quelle

3 Antworten

2

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:

%Vor%

Live-Beispiel .

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:

%Vor%

wir können den segfault ohne cerr aufrufen, was uns eine leichter zu lesende Zerlegung gibt:

%Vor%

Ich habe die Disassemblierung noch nicht analysiert, aber offensichtlich zerstört sie den Zustand des zweiten Lambda beim Spielen mit dem ersten.

    
Yakk 24.08.2016, 18:57
quelle
2

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:

%Vor%

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:

%Vor%

Das ist alles, was ich bisher getestet habe.

    
smateo 24.08.2016 18:53
quelle
1

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:

%Vor%

Live-Beispiel.

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.

    
xhamr 25.08.2016 17:51
quelle