Как инициализируются лямбда-захваты в случае вложенных лямбда-выражений? - PullRequest
8 голосов
/ 27 мая 2020

Хорошо, это сразу же из [expr.prim.lambda] p16 в n3337.pdf. Ниже приведен код в качестве примера:

int a = 1, b = 1, c = 1;
auto m1 = [a, &b, &c]() mutable
{
    auto m2 = [a, b, &c]() mutable
    {
        std::cout << a << b << c;     // Shouldn't this print 113 or 133?
        a = 4; b = 4; c = 4;
    };
    a = 3; b = 3; c = 3;
    m2();
};
a = 2; b = 2; c = 2;
m1();
std::cout << a << b << c;             // Okay, this prints 234

, и он должен генерировать следующий вывод:

123234

Однако, как у меня понял текст в [expr.prim.lambda] (который каким-то образом явно ошибочен), я чувствую, что вывод должен быть 113234, в частности, значение b напечатано в m2. Ниже мое понимание / объяснение:

Когда std::cout << a << b << c; выполняется внутри m2, согласно [expr.prim.lambda] p16 (выделено мной):

Если лямбда-выражение m2 захватывает объект, и этот объект захватывается непосредственно включающим лямбда-выражением m1, тогда захват m2 преобразуется следующим образом:

- если m1 захватывает объект копией, m2 захватывает соответствующий non-stati c член данных типа закрытия m1;

Следовательно, a внутри m2 должен захватывать член, сгенерированный в соответствующий a, захваченный в закрытии наберите m1. Поскольку a в m1 захватывается копией, а a в m2 также захватывается копией, значение a в m2 должно быть 1.

Стандарт идет чтобы сказать (опять же, выделено мной):

- если m1 захватывает объект по ссылке, m2 захватывает тот же объект , захваченный m1.

Я считаю, что « тот же объект » здесь относится к объекту, захваченному m1 посредством ссылки, а когда он захвачен m2, он должен быть - ссылкой на тот же объект если это захват по ссылке, или его копия, если это захват по копии.

Следовательно, для b в m2 следует ссылаться на b, определенный вне обеих лямбда-выражений. Значение b в m2 тогда должно быть 1, поскольку b также захватывается копией.

Где я ошибаюсь? В частности, когда инициализируется b внутри m2?

Ответы [ 2 ]

4 голосов
/ 27 мая 2020

Во-первых, обратите внимание на то, что захват осуществляется копированием или по ссылке, зависит только от собственного лямбда-интродуктора (начальная [] часть) для C ++ 11 [expr.prim .lambda] параграф 14 (или C ++ 17 [expr.prim.lambda.capture] параграф 10 ).

Части, которые вы цитировали из C ++ 11 [expr.prim. lambda] / 16 (или то же самое в C ++ 17 [expr.prim.lambda.capture] / 13) изменяет только захватываемый объект, но не тип захвата. Итак, в этом примере внутренняя лямбда, используемая для инициализации m2, захватывает b из исходного определения путем копирования.

Затем обратите внимание на C ++ 11 [expr.prim.lambda] / 21:

Когда вычисляется лямбда-выражение , сущности, захваченные копией, используются для прямой инициализации каждого соответствующего нестатического c члена данных результирующего замыкания. объект.

(C ++ 17 [expr.prim.lambda.capture] / 15 начинается так же, но для init- захват синтаксис, например [var=init].)

В примере вычисляется внутреннее лямбда-выражение для инициализации m2, а член закрывающего объекта для b инициализируется каждый раз, когда вызывается m1.operator(), а не в том порядке, в котором лямбда-выражение появляется в коде. Поскольку лямбда для m2 захватывает оригинал b путем копирования, он получает значение этого b во время вызова m1. Если m1 вызывается несколько раз, это начальное значение для b каждый раз может быть другим.

3 голосов
/ 27 мая 2020

- если m1 захватывает объект по ссылке, m2 захватывает тот же объект, захваченный m1.

Да, поэтому b in m2 ' Список захвата захватывает не саму ссылку (то есть захват m1), а объект, на который она указывает.

Но определяется, захватывает ли m2 b по значению или по ссылке исключительно тем, что написано в списке захвата m2. Перед b нет &, поэтому b захватывается по значению.

когда b внутри m2 инициализировано?

Когда control достигает auto m2 = ...;. На этом этапе ссылка на b, хранящаяся в m1, проверяется, и объект, на который она указывает, копируется в m2.


Вот более простое объяснение.

  • Когда вы захватываете ссылку по значению, вы делаете копию объекта, на который она указывает.

  • Когда вы захватываете ссылку по ссылке, вы делаете ссылка на объект, на который он указывает.

Здесь «захват ссылки» одинаково хорошо применяется как для фиксации фактических ссылок, так и для захвата ссылок-захватов включающих лямбда-выражений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...