Каким будет режим захвата лямбда по умолчанию через захват инициализации?

С лямбда-выражениями в С++ 11 мы можем установить режим захвата по умолчанию по значению/по ссылке, например. [=]/[&], за которыми могут следовать явные захваты, по ссылке/по значению, для некоторых переменных, например. [=,&this_is_by_ref] или [&,this_is_by_value].

В С++ 14 у нас также может быть явный захват по ходу, например. [y = std::move(x)].

В Effective Modern C++, пункт 32, 3-й абзац, я прочитал

Единственное, что вы не можете выразить с помощью захвата инициализации, — это режим захвата по умолчанию, […]

Что, скорее всего, имеет в виду автор?

У нас уже есть способ захватить все необходимые нам переменные путем копирования или ссылки. Зачем нам выражать это с помощью формы x = y?

Может быть, автор имеет в виду только взятие ходом по умолчанию? Что-то, что будет работать как [x = std::move(x), y = std::move(y), …] со всеми перечисленными переменными, используемыми в теле?


person Enlico    schedule 31.12.2020    source источник
comment
На этот вопрос трудно ответить без дополнительного контекста (и не у всех из нас есть книга).   -  person aschepler    schedule 05.01.2021
comment
@aschepler Сложность ответа не делает вопрос недействительным. ;)   -  person Yakk - Adam Nevraumont    schedule 06.01.2021


Ответы (3)


Ответ @OutOfBound уже хорош. Я лишь выскажу аналогичные мысли под другим углом.

Скотт Мейерс в первых нескольких абзацах пункта 32 отмечает, что:

  • Синтаксис захвата, введенный в C++11, считался неудовлетворительным даже на момент принятия стандарта.
  • Причиной этого было отсутствие средств захвата с помощью семантики перемещения (или, например, по ссылке const), кроме, разве что, каких-то неуклюжих, малопонятных обходных путей.
  • Комитет по стандартам мог бы улучшить возможности синтаксиса C++11, просто расширив его (думаю, например, введя [&&, &&x]), но в итоге выбрал другой, более общий подход: они ввели новый синтаксис (C++14 синтаксис).
  • Затем Скотт замечает, что в принципе вы можете ПОЧТИ полностью забыть о старом синтаксисе C++11 и использовать исключительно синтаксис C++14 (тот, что с инициализацией захвата).
  • Единственное, что вы действительно можете сделать только со старым синтаксисом, — это определить режим захвата по умолчанию.
  • Однако Скотт все равно не рекомендует программистам на C++ использовать режим захвата по умолчанию (статья 31).
  • Таким образом, смысл предложения таков: на практике программисту C++ (или языка C++) не нужен старый синтаксис, я бы хотел, чтобы новый синтаксис был принят уже в C++11.

Обратите также внимание на то, что новый синтаксис захвата настолько гибок, что позволяет не только выражать ваше намерение использовать семантику перемещения, но также использовать константные ссылки (Скотт опускает этот последний пункт) и изменяемые лямбда-выражения.

person zkoza    schedule 06.01.2021
comment
На что похож захват const&? +1, кстати. - person Enlico; 06.01.2021
comment
[&cr = std::as_const(x)] см. en.cppreference.com/w/cpp/language/lambda. ; также: stackoverflow.com/questions/3772867/ - person zkoza; 06.01.2021
comment
std::as_const поставляется с C++17, так что Скотт Мейерс, конечно же, не упоминал об этом. В любом случае, хорошо знать это использование. - person Enlico; 06.01.2021
comment
Да, он поставляется с стандартной библиотекой C++17, но мог быть уже реализован в C++11. Я думаю, что большинство людей могут жить без ссылок на константы в лямбда-выражениях, потому что лямбда-выражения обычно короткие и используются в ограниченных средах, и, следовательно, их ручное управление не повредит. - person zkoza; 08.01.2021
comment
Ммм... Часто бывает, что я пишу довольно длинную лямбду. И я пишу лямбда вместо функции, потому что я не могу передать последнюю. И я пишу лямбда вместо созданного вручную struct-с-operator(), потому что читателю ясно, что первое предназначено для вызова (тогда как им нужно просмотреть второе, чтобы operator() понять, что это вызываемый). Тем не менее, это будет OT, извините. - person Enlico; 08.01.2021

В параграфе говорится, что нет возможности комбинировать захват инициализации с режимом захвата по умолчанию ([&] или [=]), и нет нового режима захвата по умолчанию для семантики перемещения.

У нас уже есть способ захватить все необходимые нам переменные путем копирования или ссылки. Зачем нам выражать это в форме x = y?

Ваша мысль правильная. Комбинация захвата инициализации с захватом по умолчанию будет вести себя так же, как и старый способ захвата по умолчанию. Возможно, именно по этой причине он опущен. Однако init-capture может делать новые вещи, которые нельзя было делать раньше. (например, присвоение значения новой переменной)

Может быть, автор имеет в виду только взятие ходом по умолчанию? Что-то, что будет работать как [x = std::move(x), y = std::move(y), …] со всеми перечисленными переменными, используемыми в теле?

Это тоже верно.

Чтобы лучше понять, какие новые возможности дает захват init, я приведу вам этот небольшой пример. Предположим, вы хотите заполнить вектор значениями от 0 до n. Старый способ сделать это с лямбдой:

std::vector<int> vec;
int i = 0;
std::generate_n(std::back_inserter(vec), n, [i]()mutable{
    return i++;
});

И теперь с захватом инициализации вы можете инициализировать значение вашей счетной переменной в захвате лямбда:

std::vector<int> vec;
std::generate_n(std::back_inserter(vec), n, [i=0]()mutable{
    return i++;
});

Это было невозможно раньше и полезно во многих местах.

person OutOfBound    schedule 06.01.2021

захват init - это [z = y] стиль захвата.

Единственный тип захвата, который вы не можете выразить с помощью этого стиля захвата, — это режим захвата по умолчанию. То есть:

[&, z=y]{ /* some code */ }

здесь & использует синтаксис захвата в старом стиле. Это означает, что у нас есть режим захвата по умолчанию. z=y — это захват инициализации в новом стиле.

Невозможно использовать захват init для выражения режима захвата по умолчанию.

Все, что вы делаете с синтаксисом захвата в старом стиле, вы можете дублировать с синтаксисом захвата инициализации.

Ie:

[x=x]{ /* some code */ }

это еще один способ выразить

[x]{ /* some code */ }

и

[&x=x]{ /* some code */ }

это еще один способ выразить

[&x]{ /* some code */ }

теперь это не так глубоко.

Но если ваш аргумент не использует старый синтаксис захвата, вы должны доказать, что он вам больше не нужен. И вам не нужен старый синтаксис захвата, за исключением случаев, когда вы определяете режим захвата по умолчанию.

person Yakk - Adam Nevraumont    schedule 06.01.2021