На це питання не можна повністю відповісти в коді. Можливо, ви зможете написати дещо "еквівалентний" код, але стандарт не вказаний таким чином.
Коли це не вийде, давайте зануримось [expr.prim.lambda]
. Перше, що слід зазначити, конструктори згадуються лише у [expr.prim.lambda.closure]/13
:
Тип закриття, пов'язаний з лямбда-виразом , не має конструктора за замовчуванням, якщо лямбда-вираз має ламбда-захоплення, а конструктор за замовчуванням за замовчуванням - в іншому випадку. У ній є конструктор копій, що не використовується, та конструктор переміщення за умовчанням ([class.copy.ctor]). У нього є видалений оператор присвоєння копії, якщо лямбда-вираз має лямбда-захоплення та дефолт, копіюючи та переміщуючи оператори присвоєння інакше ([class.copy.assign]). [ Примітка: Ці спеціальні функції членів неявно визначені як зазвичай, і тому можуть бути визначені як видалені. - кінцева примітка ]
Тож одразу ж у біту має бути зрозуміло, що конструктори формально не визначають спосіб захоплення об'єктів. Ви можете наблизитись (див. Відповідь cppinsights.io), але деталі відрізняються (зауважте, як код у цій відповіді для випадку 4 не компілюється).
Це основні стандартні пункти, необхідні для обговорення випадку 1:
[expr.prim.lambda.capture]/10
[...]
Для кожного об'єкта, захопленого копією, неназваний нестатичний член даних оголошується у типі закриття. Порядок декларування цих членів не визначений. Тип такого члена даних є посилальним типом, якщо суб'єктом є посилання на об'єкт, значення посилання на значення типу посилається, якщо сутність є посиланням на функцію, або тип відповідного захопленого об'єкта в іншому випадку. Член анонімного союзу не може бути захоплений копією.
[expr.prim.lambda.capture]/11
Кожен вираз id у складеному операторі лямбда-виразу, який є odr-використанням об'єкта, захопленого копією, перетворюється на доступ до відповідного неназваного члена даних типу закриття. [...]
[expr.prim.lambda.capture]/15
Коли оцінюється лямбда-вираз, об'єкти, захоплені копією, використовуються для прямої ініціалізації кожного відповідного нестатичного члена даних об'єкта закриття, а нестатичні члени даних, що відповідають init-captures, ініціалізуються як позначається відповідним ініціалізатором (який може бути ініціалізацією копіювання чи прямої). [...]
Давайте застосуємо це до вашої справи 1:
Випадок 1: захоплення за значенням / захоплення за замовчуванням за значенням
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
Тип закриття цієї лямбда матиме неназваний нестатичний член даних (назвемо це __x
) типу int
(оскільки x
це не є посиланням і не функцією), а доступ до x
тіла лямбда перетворюється на доступ до __x
. Коли ми оцінюємо лямбда-вираз (тобто при призначенні lambda
), ми пряме-ініціалізуємо __x
з x
.
Словом, має місце лише одна копія . Конструктор типу закриття не задіяний, і це неможливо виразити "нормальним" C ++ (зауважте, що тип закриття також не є агрегованим типом ).
Захоплення рефератів включає [expr.prim.lambda.capture]/12
:
Суб'єкт охоплюється за посиланням, якщо він неявно або явно захоплений, але не зафіксований копією. Не визначено, чи оголошуються додаткові неназвані нестатичні члени даних у типі закриття для об'єктів, захоплених посиланням. [...]
Є ще один абзац щодо збору посилань, але ми цього ніде не робимо.
Отже, для випадку 2:
Випадок 2: захоплення посиланням / захоплення за замовчуванням за посиланням
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
Ми не знаємо, чи додається член до типу закриття. x
в тілі лямбда може просто безпосередньо посилатися на x
зовнішню сторону. Це залежить від компілятора, і він зробить це в якійсь формі проміжної мови (яка відрізняється від компілятора до компілятора), а не в вихідному перетворенні коду C ++.
Захоплення Init детально описано у [expr.prim.lambda.capture]/6
:
Захоплення init поводиться так, ніби він оголошує і явно фіксує змінну форми auto init-capture ;
, декларативною областю якої є сполука-вираз лямбда-виразу, за винятком того, що:
- (6.1) якщо захоплення здійснюється копією (див. Нижче), нестатичний член даних, оголошений для захоплення, та змінна трактуються як два різні способи посилання на один і той же об'єкт, який має термін експлуатації нестатичних даних член, і додаткові копії та знищення не виконуються, і
- (6.2) якщо захоплення відбувається за посиланням, час змінної закінчується, коли закінчується термін експлуатації об'єкта закриття.
Враховуючи це, давайте розглянемо випадок 3:
Випадок 3: Узагальнений захоплення init
auto lambda = [x = 33]() { std::cout << x << std::endl; };
Як зазначено, уявіть це як змінну, яка створюється auto x = 33;
копією та явно фіксується. Ця змінна є "видимою" лише в тілі лямбда. Як зазначалося [expr.prim.lambda.capture]/15
раніше, ініціалізація відповідного члена типу закриття ( __x
для нащадків) здійснюється даним ініціалізатором після оцінки виразу лямбда.
Щоб уникнути сумнівів: це не означає, що тут ініціалізуються двічі. Це auto x = 33;
"як би" успадковувати семантику простих фіксацій, а описана ініціалізація є модифікацією цієї семантики. Буває лише одна ініціалізація.
Це стосується також випадку 4:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
Член типу закриття ініціалізується __p = std::move(unique_ptr_var)
тоді, коли оцінюється вираз лямбда (тобто коли l
призначено). Доступи до p
тіла лямбда перетворюються на доступ до __p
.
TL; DR: Виконується лише мінімальна кількість копій / ініціалізації / ходи (як можна було б сподіватися / очікувати). Я б припустив, що лямбда не визначені з точки зору трансформації джерела (на відміну від інших синтаксичних цукрів) саме тому , що вираження речей через конструктори потребує зайвих операцій.
Я сподіваюся, що це вирішує побоювання, висловлені у запитанні :)