На этот вопрос нельзя ответить полностью в коде. Возможно, вам удастся написать несколько «эквивалентный» код, но стандарт не указывается таким образом.
С этим из пути, давайте углубимся в [expr.prim.lambda]
. Первое, что следует отметить, это то, что конструкторы упоминаются только в [expr.prim.lambda.closure]/13
:
Тип замыкания, связанный с лямбда-выражением , не имеет конструктора по умолчаниюесли лямбда-выражение имеет лямбда-захват и конструктор по умолчанию в противном случае. Он имеет конструктор копирования по умолчанию и конструктор перемещения по умолчанию ([class.copy.ctor]). У него есть оператор присваивания удаленной копии, если лямбда-выражение имеет лямбда-захват , в противном случае по умолчанию используются операторы копирования и перемещения ([class.copy.assign]). [ Примечание: Эти специальные функции-члены неявно определены как обычно и поэтому могут быть определены как удаленные. - примечание к концу ]
Итак, сразу должно быть ясно, что конструкторы не являются формально тем, как определяется захват объектов. Вы можете подойти довольно близко (см. Ответ cppinsights.io), но детали различаются (обратите внимание, что код в этом ответе для случая 4 не компилируется).
Это основные стандартные предложения, необходимыеобсудить случай 1:
[expr.prim.lambda.capture]/10
[...]
Для каждого объекта, захваченного при копировании, неназванные нестатические данныечлен объявлен в типе замыкания. Порядок объявления этих членов не уточняется. Тип такого члена данных является ссылочным типом, если объект является ссылкой на объект, lvalue-ссылкой на ссылочный тип функции, если объект является ссылкой на функцию, или типом соответствующего захваченного объекта в противном случае. Член анонимного союза не может быть захвачен копией.
[expr.prim.lambda.capture]/11
Каждое id-выражение в составном операторе лямбда-выражения , который представляет собой использование odr объекта, захваченного копией, преобразуется в доступ к соответствующему безымянному элементу данных типа замыкания. [...]
[expr.prim.lambda.capture]/15
Когда вычисляется лямбда-выражение, объекты, захваченные копией, используются дляпрямая инициализация каждого соответствующего элемента не статических данных результирующего объекта замыкания, и элементы нестатических данных, соответствующие перехватам инициализации, инициализируются, как указано соответствующим инициализатором (который может быть инициализацией копирования или прямой инициализацией). [...]
Давайте применим это к вашему случаю 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 ++.
Захваты инициализацииподробно описаны в [expr.prim.lambda.capture]/6
:
Захват init ведет себя так, как будто он объявляет и явно захватывает переменную вида auto init-capture ;
, чья декларативная область является лямбда-выражениемсоставной оператор, за исключением того, что:
- (6.1), если захват производится копией (см. ниже), элемент нестатических данных, объявленный для захвата, и переменная обрабатываются как два разных способассылаясь на тот же объект, у которого есть время жизни элемента нестатических данных, и при этом дополнительное копирование и уничтожение не выполняется, и
- (6.2), если захват выполняется по ссылке, время жизни переменной заканчивается, когдавремя жизни объекта замыкания заканчивается.
Учитывая это, давайте рассмотрим случай 3:
Случай 3: Обобщенный захват инициализации
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: выполняется только минимальное количество копий / инициализаций / ходов (как было бынадежда / ожидание). Я бы предположил, что лямбды не определены в терминах преобразования источника (в отличие от другого синтаксического сахара) точно , потому что выражение вещей в терминах конструкторов потребовало быЛишние операции.
Я надеюсь, что это уладит страхи, выраженные в вопросе:)