Ich habe festgestellt, dass die Ergebnisse bei Compilern unterschiedlich sind, wenn ich ein Lambda verwende, um einen Verweis auf eine globale Variable mit einem veränderlichen Schlüsselwort zu erfassen und dann den Wert in der Lambda-Funktion zu ändern.
#include <stdio.h>
#include <functional>
int n = 100;
std::function<int()> f()
{
int &m = n;
return [m] () mutable -> int {
m += 123;
return m;
};
}
int main()
{
int x = n;
int y = f()();
int z = n;
printf("%d %d %d\n", x, y, z);
return 0;
}
Ergebnis von VS 2015 und GCC (g ++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.12) 5.4.0 20160609):
100 223 100
Ergebnis von clang ++ (clang Version 3.8.0-2ubuntu4 (tags / RELEASE_380 / final)):
100 223 223
Warum passiert das? Ist dies nach den C ++ - Standards zulässig?
c++
c++11
lambda
language-lawyer
Willy
quelle
quelle
Antworten:
Ein Lambda kann eine Referenz selbst nicht nach Wert erfassen (
std::reference_wrapper
für diesen Zweck verwenden).In Ihrem Lambda werden
[m]
Erfassungenm
nach Wert erfasst (da&
die Erfassung keine enthält ). Daher wirdm
(als Verweis aufn
) zuerst dereferenziert und eine Kopie des Objekts, auf das es verweist (n
), erfasst. Dies ist nicht anders als dies:Das Lambda ändert dann diese Kopie, nicht das Original. Dies geschieht erwartungsgemäß in den VS- und GCC-Ausgängen.
Die Clang-Ausgabe ist falsch und sollte als Fehler gemeldet werden, falls dies noch nicht geschehen ist.
Wenn Sie möchten, dass Ihr Lambda geändert wird
n
, erfassen Siem
stattdessen anhand der Referenz :[&m]
. Dies unterscheidet sich nicht von der Zuweisung einer Referenz zu einer anderen, z.Oder Sie können es einfach
m
ganz loswerden undn
stattdessen als Referenz erfassen :[&n]
.Obwohl
n
es sich im globalen Bereich befindet, muss es überhaupt nicht erfasst werden, aber das Lambda kann global darauf zugreifen, ohne es zu erfassen:quelle
Ich denke, Clang kann tatsächlich richtig sein.
Nach [lambda.capture] / 11 , eine ID-Expression in dem Lambda verwendete , bezieht sich auf das von kopier erfaßt Mitglied lambda nur , wenn es einen ausmacht ODR-use . Wenn dies nicht der Fall ist, bezieht es sich auf die ursprüngliche Entität . Dies gilt für alle C ++ - Versionen seit C ++ 11.
Gemäß C ++ 17s [basic.dev.odr] / 3 wird eine Referenzvariable nicht odr-verwendet, wenn die Konvertierung von lWert zu rWert einen konstanten Ausdruck ergibt.
Im C ++ 20-Entwurf entfällt jedoch die Anforderung für die Konvertierung von Wert zu Wert, und die entsprechende Passage wurde mehrmals geändert, um die Konvertierung einzuschließen oder nicht einzuschließen. Siehe CWG-Ausgabe 1472 und CWG-Ausgabe 1741 sowie offene CWG-Ausgabe 2083 .
Da
m
es mit einem konstanten Ausdruck initialisiert wird (der sich auf ein Objekt mit statischer Speicherdauer bezieht), ergibt die Verwendung einen konstanten Ausdruck pro Ausnahme in [expr.const] /2.11.1 .Dies ist jedoch nicht der Fall, wenn l-Wert-zu-Wert-Konvertierungen angewendet werden, da der Wert von
n
in einem konstanten Ausdruck nicht verwendet werden kann.Abhängig davon, ob bei der Bestimmung der odr-Verwendung Umrechnungen von lwert zu rwert angewendet werden sollen oder nicht, kann sich dies bei Verwendung
m
im Lambda auf das Mitglied des Lambda beziehen oder nicht.Wenn die Konvertierung angewendet werden soll, sind GCC und MSVC korrekt, andernfalls ist Clang korrekt.
Sie können sehen, dass Clang sein Verhalten ändert, wenn Sie die Initialisierung so ändern
m
, dass sie kein konstanter Ausdruck mehr ist:In diesem Fall stimmen alle Compiler darin überein, dass die Ausgabe ist
denn
m
im Lambda wird auf das Mitgliedint
des Abschlusses verwiesen, das vom Typ kopiert ist, der aus der Referenzvariablenm
in kopiert wurdef
.quelle
m
von einem Ausdruck, der sie benennt, odr-verwendet wird, es sei denn, die Konvertierung von lvalue in rvalue wäre ein konstanter Ausdruck. Nach [expr.const] / (2.7) wäre diese Konvertierung kein konstanter Kernausdruck.m += 123;
Hierm
wird odr verwendet.Dies ist im C ++ 17-Standard nicht zulässig, in einigen anderen Standardentwürfen jedoch möglicherweise. Es ist aus Gründen, die in dieser Antwort nicht erläutert werden, kompliziert.
[expr.prim.lambda.capture] / 10 :
Dies
[m]
bedeutet, dass die Variablem
inf
durch Kopieren erfasst wird. Die Entitätm
ist eine Referenz auf ein Objekt, daher hat der Schließungstyp ein Element, dessen Typ der referenzierte Typ ist. Das heißt, der Typ des Mitglieds istint
und nichtint&
.Da der Name
m
im Lambda-Body das Element des Abschlussobjekts und nicht die Variable inf
(und dies ist der fragliche Teil) benennt ,m += 123;
ändert die Anweisung dieses Element, das ein anderesint
Objekt ist als::n
.quelle