Осмысление вложенного лямбда-выражения - PullRequest
1 голос
/ 29 сентября 2019

Мой вопрос относится к следующему вложенному лямбда-выражению, приведенному в качестве примера в разделе Лямбда-выражения

// generic lambda, operator() is a template with one parameter
auto vglambda = [](auto printer) {
    return [=](auto&&... ts) // generic lambda, ts is a parameter pack
    { 
        printer(std::forward<decltype(ts)>(ts)...);
        return [=] { printer(ts...); }; // nullary lambda (takes no parameters)
    };
};
auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });
auto q = p(1, 'a', 3.14); // outputs 1a3.14
q();                      // outputs 1a3.14

Ниже описан способ интерпретации вышеприведенного выражения:

В выражении

auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });

объект замыкания vglambda инициализируется объектом замыкания printer, тип которого соответствует лямбда-выражению

[](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; }

Внутри printer,вложенное (анонимное) лямбда-выражение

return [=](auto&&... ts){}

захватывает printer копией и своим пакетом параметров как rvalue ссылка.

Внутри тела (анонимного) лямбда-выражениявыражение

printer(std::forward<decltype(ts)>(ts)...);

перенаправляет пакет параметров в printer [что, по сути, является вызовом printer с использованием operator ()]

в последнем выражении внутри тела(анонимное) лямбда-выражение, (анонимное) нулевое лямбда-выражение, по-видимому, захватывает закрывающий объект printer из охватывающей области путем копирования вместе с пакетом параметров и ввызывает объект замыкания printer с перенаправленным пакетом параметров.

return [=] { printer(ts...); };

Теперь очень очевидно, что я не получаю что-то прямо здесь.По сути, почему две разные строки вызова объекта замыкания printer предусмотрены в теле (анонимного) лямбда-выражения, одна без (анонимного) нулевого лямбда-выражения и одна внутри?

Может ли любой изэксперты проливают больше света?

1 Ответ

1 голос
/ 29 сентября 2019

В выражении

auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });

объект закрытия vglambda инициализируется принтером объекта закрытия, тип которого соответствует лямбда-выражению

[](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; }

Мне кажется более правильным сказать, что vglamba вызывается , вызывается с помощью лямбды, которая использует std::cout.vglambda инициализируется универсальной лямбда-функцией, которая получает универсальное (auto) значение (printer).

Внутри printer, вложенное (анонимное)) лямбда-выражение

return [=](auto&&... ts){}

захватывает принтер по копии и его пакет параметров в качестве ссылки на значение.

Не внутри printer (это только аргумент лямбды), но внутрилямбда, которая сохраняется в переменной vglambda.

Да, анонимная вложенная универсальная и переменная функция захватывает printer по значению, но не является точным, что ... ts фиксируются (являются аргументами) ипо ссылке rvalue.

Вложенная лямбда почти эквивалентна шаблонной функции (ну ... структуре с шаблоном operator() внутри нее ... но для упрощения ...)

template <typename ... Ts>
auto func (Ts && ... ts)
 { /*...*/ } 

В этом случае && не являются ссылками rvalue, а forwarding ссылками (см. эту страницу для получения дополнительной информации), так какВы можете увидеть изнутри использование std::forward.

Это важныйточка, но посмотри дальше.

Внутри тела (анонимного) лямбда-выражения выражение

printer(std::forward<decltype(ts)>(ts)...);

перенаправляет пакет параметров в printer [что, по сути, похоже на вызов принтераиспользование operator ()]

Это мне кажется правильным.

В последнем выражении внутри тела (анонимного) лямбда-выражения (анонимного) нулевая лямбдаПохоже, выражение захватывает объект закрытия printer из охватывающей области путем копирования вместе с пакетом параметров и вызывает объект закрытия printer с перенаправленным пакетом параметров.

return [=] { printer(ts...); };

Thisмне кажется правильным, но вы должны увидеть здесь проблему (вместе с предыдущим вызовом printer().

, почему в теле (анонимного) есть две разные строки вызова объекта закрытия принтералямбда-выражение, одно без (анонимного) нулевого лямбда-выражения, а другое внутри?

Посмотрите, как используется p

auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });
auto q = p(1, 'a', 3.14);

q();   

Как инициализируется p?

p инициализируется с помощью общей и вариационной лямбды, определенной внутри лямбды, которая инициализирует vglambda.Поэтому, когда он называется

auto q = p(1, 'a', 3.14);

, у вас получается, что универсальная лямбда-переменная вызывается с помощью набора переменных ts..., который расширяется до 1, 'a' и 3.14.

* 1095.* Итак, вызывая p(1, 'a', 3.14), у вас есть то, что вызывается (игнорируя пересылающую часть)
printer(1, 'a', 3.14);

(где printer() - это лямбда, что std::cout a, b и c) и , что возвращается [=] { printer(1, 'a', 3.14); }.

Итак, q инициализируется с помощью [=] { printer(1, 'a', 3.14); }, и вызывается

q();

printer(1, 'a', 3.14) снова.

Таким образом, идея универсальной и вариационной лямбды состоит в том, чтобы вызывать print() с полученным аргументом вариады, в первый раз и возвращают другую лямбду, которая снова print() при вызове.

Таким образом, с p(1, 'a', 3.14) вы активируете первый print() (тот, у которого std::forward), и каждый раз, когда вы вызываете возвращаемое значение (q, в вашем примере), вы активируете второе print()(без std::forward).

Но в вашем коде есть большой дефект.Дефект, который не вызывает проблем при вызове p() с примитивными типами как int, char и double.Но дефект, который опасен при использовании сложных объектов, поддерживающих семантику перемещения.

Проблема в том, что, используя std::forward, вы можете активировать семантику перемещения.

Итак, в этом коде

return [=](auto&&... ts) // generic lambda, ts is a parameter pack
{ 
    printer(std::forward<decltype(ts)>(ts)...);
    return [=] { printer(ts...); }; // <- DANGER: unsafe use of `ts...`
};

первый printer() вызов безопасен и корректен (используется std::forward), но второй вызов printer() опасен, потому что мы не знаем, можно ли по-прежнему использовать ts....

Используя ts... два раза, я предлагаю переписать лямбду следующим образом

return [=](auto const & ... ts)
{ 
    printer(ts...);
    return [=] { printer(ts...); };
};
...