Stwierdziłem, że wyniki są różne w różnych kompilatorach, jeśli użyję lambda do przechwycenia odwołania do zmiennej globalnej za pomocą słowa kluczowego podlegającego modyfikacji, a następnie zmodyfikowania wartości w funkcji 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;
}
Wynik z VS 2015 i GCC (g ++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.12) 5.4.0 20160609):
100 223 100
Wynik z clang ++ (wersja clang 3.8.0-2ubuntu4 (tagi / RELEASE_380 / wersja ostateczna)):
100 223 223
Dlaczego to się dzieje? Czy jest to dozwolone przez standardy C ++?
c++
c++11
lambda
language-lawyer
Willy
źródło
źródło
Odpowiedzi:
Sonda lambda nie może przechwycić samego odwołania według wartości (użyj
std::reference_wrapper
do tego celu).W twojej lambda,
[m]
przechwytywaniem
według wartości (ponieważ nie ma&
przechwytywania), więcm
(jako odniesienien
) jest najpierw usuwane z odniesienia i przechwytywana jest kopia rzeczy, do której się odnosi (n
). Nie różni się to od robienia tego:Następnie lambda modyfikuje tę kopię, a nie oryginał. Tak właśnie się dzieje, zgodnie z oczekiwaniami, na wyjściach VS i GCC.
Dane wyjściowe Clanga są nieprawidłowe i powinny zostać zgłoszone jako błąd, jeśli jeszcze tego nie zrobił.
Jeśli chcesz, aby lambda do modyfikowania
n
, przechwytywaniem
przez odniesienie zamiast:[&m]
. Nie różni się to niczym od przypisania jednego odwołania do drugiego, np .:Albo może po prostu pozbyć się
m
całkowicie i uchwycenian
przez odniesienie zamiast:[&n]
.Chociaż skoro
n
ma zasięg globalny, naprawdę nie musi być wcale przechwytywany, lambda może uzyskać do niego dostęp globalny bez przechwytywania:źródło
Myślę, że Clang może mieć rację.
Według [lambda.capture] / 11 , wyrażenie id użyte w lambda odnosi się do elementu przechwyconego przez lambda po kopii, tylko jeśli stanowi użycie nieprzydatne . Jeśli nie, oznacza to pierwotny byt . Dotyczy to wszystkich wersji C ++ od C ++ 11.
Według [ ++. dev.odr] / 3 C ++ 17 zmienna referencyjna nie jest używana odr, jeśli zastosowanie do niej konwersji wartości z wartości na wartość daje stałe wyrażenie.
W wersji roboczej C ++ 20 jednak wymóg dotyczący konwersji wartości z wartości na wartość jest odrzucany, a odpowiedni fragment wielokrotnie zmieniany, aby uwzględnić konwersję lub nie. Patrz wydanie CWG 1472 i wydanie CWG 1741 , a także wydanie otwarte CWG 2083 .
Ponieważ
m
jest inicjowany stałym wyrażeniem (odnoszącym się do obiektu o czasie trwania przechowywania statycznego), użycie go daje stałe wyrażenie na wyjątek w [expr.const] /2.11.1 .Nie dzieje się tak jednak w przypadku zastosowania konwersji wartości z wartości na wartość, ponieważ wartość parametru
n
nie jest użyta w wyrażeniu stałym.W związku z tym, w zależności od tego, czy konwersje wartości z wartości na wartość mają być stosowane przy określaniu użycia odry, podczas korzystania
m
z lambda może, ale nie musi odnosić się do członka lambda.Jeśli konwersja powinna być zastosowana, GCC i MSVC są poprawne, w przeciwnym razie Clang jest.
Możesz zobaczyć, że Clang zmienia to zachowanie, jeśli zmienisz inicjalizację,
m
aby nie była już stałym wyrażeniem:W tym przypadku wszystkie kompilatory zgadzają się, że dane wyjściowe są
ponieważ
m
w lambda będzie odnosić się do elementu zamknięcia, który jest typuint
inicjalizowany kopiowaniem ze zmiennej odniesieniam
wf
.źródło
m
jest używana przez odr przez wyrażenie nazywające ją wyrażenie, chyba że zastosowanie do niej konwersji lvalue na rvalue byłoby ciągłym wyrażeniem. Według [expr.const] / (2.7) ta konwersja nie byłaby podstawowym stałym wyrażeniem.m += 123;
Otom
używane odr.Nie jest to dozwolone w standardzie C ++ 17, ale może być w niektórych innych wersjach standardowych. Jest to skomplikowane, z powodów nie wyjaśnionych w tej odpowiedzi.
[expr.prim.lambda.capture] / 10 :
Te
[m]
środki, które zmiennam
wf
chwyta kopii. Obiektm
jest odniesieniem do obiektu, więc typ zamknięcia ma element członkowski, którego typ jest typem odwołania. Oznacza to, że typ członka jestint
, a nieint&
.Ponieważ nazwa
m
wewnątrz ciała lambda nazywa element członkowski obiektu zamknięcia, a nie zmienną wf
(i jest to wątpliwa część), instrukcjam += 123;
modyfikuje ten element, który jest innymint
obiektem niż::n
.źródło