J'ai trouvé que les résultats sont différents d'un compilateur à l'autre si j'utilise un lambda pour capturer une référence à une variable globale avec un mot clé mutable, puis modifie la valeur dans la fonction lambda.
#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;
}
Résultat de VS 2015 et GCC (g ++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.12) 5.4.0 20160609):
100 223 100
Résultat de clang ++ (clang version 3.8.0-2ubuntu4 (tags / RELEASE_380 / final)):
100 223 223
Pourquoi cela arrive-t-il? Est-ce autorisé par les normes C ++?
c++
c++11
lambda
language-lawyer
Willy
la source
la source
Réponses:
Un lambda ne peut pas capturer une référence elle-même par valeur (à utiliser
std::reference_wrapper
à cette fin).Dans votre lambda,
[m]
capturem
par valeur (car il n'y a pas&
en a dans la capture), doncm
(étant une référence àn
) est d'abord déréférencé et une copie de la chose à laquelle il fait référence (n
) est capturée. Ce n'est pas différent de faire ceci:Le lambda modifie alors cette copie, pas l'original. C'est ce que vous voyez se produire dans les sorties VS et GCC, comme prévu.
La sortie Clang est incorrecte et doit être signalée comme un bug, si ce n'est pas déjà fait.
Si vous souhaitez modifier votre lambda
n
, la capture à lam
place par référence:[&m]
. Ce n'est pas différent que d'attribuer une référence à une autre, par exemple:Ou, vous pouvez simplement se débarrasser de
m
tout à fait et la capture à lan
place par référence:[&n]
.Bien que,
n
étant de portée mondiale, il n'a vraiment pas besoin d'être capturé du tout, le lambda peut y accéder globalement sans le capturer:la source
Je pense que Clang peut être correct.
Selon [lambda.capture] / 11 , une expression id utilisée dans le lambda fait référence au membre capturé par copie de lambda uniquement si elle constitue une utilisation odr . Si ce n'est pas le cas, cela fait référence à l' entité d'origine . Cela s'applique à toutes les versions C ++ depuis C ++ 11.
Selon [basic.dev.odr] / 3 de C ++ 17, une variable de référence n'est pas odr utilisée si l'application de la conversion lvalue-to-rvalue lui donne une expression constante.
Dans le projet C ++ 20, cependant, l'exigence de conversion lvalue-to-rvalue est supprimée et le passage correspondant a été modifié plusieurs fois pour inclure ou non la conversion. Voir CWG numéro 1472 et CWG numéro 1741 , ainsi que CWG numéro 2083 ouvert .
Puisque
m
est initialisé avec une expression constante (faisant référence à un objet de durée de stockage statique), son utilisation donne une expression constante par exception dans [expr.const] /2.11.1 .Ce n'est cependant pas le cas si des conversions lvalue-to-rvalue sont appliquées, car la valeur de
n
n'est pas utilisable dans une expression constante.Par conséquent, selon que les conversions lvalue-to-rvalue doivent ou non être appliquées pour déterminer l'utilisation odr, lorsque vous utilisez
m
dans le lambda, cela peut ou non se référer au membre du lambda.Si la conversion doit être appliquée, GCC et MSVC sont corrects, sinon Clang l'est.
Vous pouvez voir que Clang change son comportement si vous changez l'initialisation de
m
pour ne plus être une expression constante:Dans ce cas, tous les compilateurs conviennent que la sortie est
car
m
dans le lambda fera référence au membre de la fermeture qui est de typeint
initialisé à la copie à partir de la variable de référencem
dansf
.la source
m
est utilisée par odr par une expression la nommant, sauf si lui appliquer la conversion lvalue-à-rvalue serait une expression constante. Par [expr.const] / (2.7), cette conversion ne serait pas une expression constante de base.m += 123;
Voicim
odr-utilisé.Ce n'est pas autorisé par la norme C ++ 17, mais par certains autres projets de norme, cela pourrait l'être. C'est compliqué, pour des raisons non expliquées dans cette réponse.
[expr.prim.lambda.capture] / 10 :
Les
[m]
moyens que la variablem
enf
est capturé par copie. L'entitém
est une référence à un objet, donc le type de fermeture a un membre dont le type est le type référencé. Autrement dit, le type de membre estint
, et nonint&
.Étant donné que le nom
m
à l'intérieur du corps lambda nomme le membre de l'objet de fermeture et non la variable dansf
(et c'est la partie discutable), l'instructionm += 123;
modifie ce membre, qui est unint
objet différent de::n
.la source