Odpowiedź na to pytanie nie jest możliwa w kodzie. Możesz być w stanie napisać nieco „równoważny” kod, ale standard nie jest określony w ten sposób.
Zejdźmy na bok [expr.prim.lambda]
. Pierwszą rzeczą do odnotowania jest to, że konstruktory są wymienione tylko w [expr.prim.lambda.closure]/13
:
Typ zamknięcia skojarzony z wyrażeniem lambda nie ma domyślnego konstruktora, jeśli wyrażenie lambda ma przechwytywanie lambda, a domyślny domyślny konstruktor jest inny. Ma domyślny konstruktor kopiowania i domyślny konstruktor przenoszenia ([class.copy.ctor]). Ma on operator przypisania usuniętej kopii, jeśli wyrażenie lambda ma funkcję przechwytywania lambda i domyślnie operatory przypisania kopiowania i przenoszenia w przeciwnym razie ([class.copy.assign]). [ Uwaga: Te specjalne funkcje składowe są domyślnie zdefiniowane jak zwykle i dlatego mogą zostać zdefiniowane jako usunięte. - uwaga końcowa ]
Tak więc od samego początku powinno być jasne, że konstruktorzy nie określają formalnie sposobu przechwytywania obiektów. Możesz podejść bardzo blisko (patrz odpowiedź cppinsights.io), ale szczegóły różnią się (zwróć uwagę, że kod w tej odpowiedzi dla przypadku 4 nie kompiluje się).
Oto główne standardowe klauzule potrzebne do omówienia przypadku 1:
[expr.prim.lambda.capture]/10
[...]
Dla każdego obiektu przechwyconego przez kopię deklarowany jest nienazwany niestatyczny element danych w rodzaju zamknięcia. Kolejność deklaracji tych członków jest nieokreślona. Typem takiego elementu danych jest typ odwołania, jeśli encja jest odwołaniem do obiektu, odwołanie do wartości typu referencyjnego typu funkcji, jeśli encja jest odwołaniem do funkcji, lub typem odpowiadającego obiektu przechwyconego w innym przypadku. Członek anonimowego związku nie może zostać schwytany przez kopię.
[expr.prim.lambda.capture]/11
Każde wyrażenie id w wyrażeniu złożonym wyrażenia lambda, które jest odrowym użyciem encji przechwyconej przez kopię, jest przekształcane w dostęp do odpowiedniego nienazwanego elementu danych typu zamknięcia. [...]
[expr.prim.lambda.capture]/15
Gdy ocenia się wyrażenie lambda, jednostki, które są przechwytywane przez kopię, są używane do bezpośredniej inicjalizacji każdego odpowiedniego niestatycznego elementu danych wynikowego obiektu zamknięcia, a niestatyczne elementy danych odpowiadające przechwyceniu init są inicjowane jako wskazane przez odpowiedni inicjator (który może być inicjalizacją kopiowania lub bezpośrednią). [...]
Zastosujmy to do twojego przypadku 1:
Przypadek 1: przechwytywanie według wartości / przechwytywanie domyślne według wartości
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
Typ zamknięcia tej lambdy będzie miał nienazwany element danych niestatycznych (nazwijmy go __x
) typu int
(ponieważ x
nie jest ani odniesieniem, ani funkcją), a dostęp do x
wewnątrz ciała lambda jest przekształcany w dostęp do __x
. Kiedy oceniamy wyrażenie lambda (tj. Przy przypisywaniu do lambda
), inicjalizujemy bezpośrednio za __x
pomocą x
.
Krótko mówiąc, ma miejsce tylko jedna kopia . Konstruktor typu zamknięcia nie jest zaangażowany i nie jest możliwe wyrażenie tego w „normalnym” języku C ++ (należy zauważyć, że typ zamknięcia również nie jest typem zagregowanym ).
Przechwytywanie referencji obejmuje [expr.prim.lambda.capture]/12
:
Jednostka jest przechwytywana przez odniesienie, jeśli jest ona dorozumiana lub jawna, ale nie jest przechwytywana przez kopię. Nie jest określone, czy dodatkowe nienazwane elementy danych niestatycznych są zadeklarowane w typie zamknięcia dla encji przechwyconych przez odniesienie. [...]
Jest jeszcze jeden akapit na temat przechwytywania referencji, ale nigdzie tego nie robimy.
W przypadku 2:
Przypadek 2: przechwytywanie przez odniesienie / domyślne przechwytywanie przez odniesienie
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
Nie wiemy, czy członek jest dodawany do typu zamknięcia. x
w ciele lambda może po prostu bezpośrednio odnosić się na x
zewnątrz. To zależy od kompilatora, aby to rozgryźć i zrobi to w jakiejś formie języka pośredniego (który różni się od kompilatora do kompilatora), a nie w źródłowej transformacji kodu C ++.
Przechwytywanie początkowe opisano szczegółowo w [expr.prim.lambda.capture]/6
:
Przechwytywanie init zachowuje się tak, jakby deklarowało i jawnie przechwytywało zmienną formy, auto init-capture ;
której region deklaratywny jest wyrażeniem złożonym wyrażenia lambda, z wyjątkiem tego, że:
- (6.1) jeśli przechwytywanie odbywa się za pomocą kopiowania (patrz poniżej), element danych niestatycznych zadeklarowany do przechwytywania i zmienna są traktowane jako dwa różne sposoby odwoływania się do tego samego obiektu, który ma okres istnienia danych niestatycznych członka i nie jest wykonywana żadna dodatkowa kopia i zniszczenie, oraz
- (6.2) jeśli przechwytywanie odbywa się przez odniesienie, czas życia zmiennej kończy się, gdy kończy się czas życia obiektu zamknięcia.
Biorąc to pod uwagę, spójrzmy na przypadek 3:
Przypadek 3: Uogólnione przechwytywanie inicjujące
auto lambda = [x = 33]() { std::cout << x << std::endl; };
Jak powiedziano, wyobraź sobie, że jest to zmienna tworzona przez auto x = 33;
i jawnie przechwytywana przez kopię. Ta zmienna jest „widoczna” tylko w ciele lambda. Jak zauważono [expr.prim.lambda.capture]/15
wcześniej, inicjalizacja odpowiedniego elementu typu zamknięcia ( __x
dla potomności) następuje przez podany inicjator po ocenie ekspresji lambda.
Aby uniknąć wątpliwości: nie oznacza to, że rzeczy są tutaj inicjowane dwukrotnie. auto x = 33;
Jest „jak gdyby” dziedziczyć semantyki prostych przechwytywania i opisane inicjalizacji modyfikacja tych semantyki. Następuje tylko jedna inicjalizacja.
Dotyczy to również przypadku 4:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
Element typu zamknięcia jest inicjowany przez __p = std::move(unique_ptr_var)
obliczenie wyrażenia lambda (tj. Kiedy l
jest przypisany). Dostęp do p
w ciele lambda przekształca się w dostęp do __p
.
TL; DR: Wykonywana jest tylko minimalna liczba kopii / inicjalizacji / ruchów (jak można się spodziewać / oczekiwać). Zakładam, że lambdas nie są określone w kategoriach transformacji źródłowej (w przeciwieństwie do innych cukru syntaktycznego) właśnie dlatego , że wyrażanie rzeczy w kategoriach konstruktorów wymagałoby zbędnych operacji.
Mam nadzieję, że to rozwiąże obawy wyrażone w pytaniu :)