Захват по универсальной ссылке

При передаче выведенного типа в качестве ссылки на r-значение я получаю универсальную справочную функциональность и могу архивировать идеальную пересылку следующим образом:

template <typename T>
void func(T&& t) {
    other_func(std::forward<T>(t));
}

... из-за способа получения T и правил свертывания ссылок стандарта.

Теперь рассмотрим, как other_func принимает функциональный объект.

template <typename T>
void func(T&& t) {
    other_func([](int v) { return t + v; }); // I chose addition for example purposes
}

Теперь, очевидно, это не скомпилируется из-за того, что t не был захвачен. Мой вопрос: как мне его зафиксировать, чтобы захваченное значение было тем, к чему выводится T?

Возможно ли это с помощью новых общих лямбда-захватов? А если... как?

[t = std::forward<T>(t)] ? 

Я до сих пор не очень понимаю механику новых инициализаторов захвата...


person Richard Vock    schedule 20.01.2014    source источник
comment
Краткое примечание: в настоящее время принятое имя — Ссылка на переадресацию, а не универсальная ссылка.   -  person Russell Greene    schedule 14.08.2015


Ответы (3)


Вы можете "захватить по универсальной ссылке" в C++11, так как тип параметра шаблона T доступен лямбда-функции (отвратительный живой пример кода в Coliru):

template <typename T>
void func(T&& t) {
  other_func([&t](int v) {
    return std::forward<T>(t) + v;
  });
}
person Casey    schedule 20.01.2014
comment
Что ж, это интересно - думал, что тип t внутри лямбды рухнул бы до ссылки на l-значение. Еще одно примечание: как называются спецификаторы ссылок, которые вы пишете после объявления оператора? Не знал, что такие существуют - person Richard Vock; 20.01.2014
comment
@RichardVock Это ref-qualifiers. Подобно квалификаторам const в функциях-членах, они ограничивают объекты, для которых может быть вызвана функция. Например, void foo() && вызывается, когда вы вызываете foo для объекта rvalue. - person Casey; 20.01.2014
comment
Возможная проблема: если other_func хранит эту лямбду и она длится дольше, чем область тела func, вызов лямбды является UB, так как захваченная переменная t вышла из области видимости. - person Yakk - Adam Nevraumont; 05.02.2014
comment
Я нашел этот ТАК вопрос, потому что искал что-то вроде [&=t] (или [&=] для всех). Что я подразумеваю под этим - ссылочные типы будут сохранены в this лямбды по ссылке, а переменные типов значений или ссылочных типов rvalue будут сохранены по значению. Возможно ли что-то подобное? Таким образом, возможная проблема с областью действия (в комментарии выше) будет гораздо меньшей проблемой. В частности, в моем случае скажем, что other_func нет, а я возвращаю лямбду в func. Если бы T был ссылочным типом, не было бы копирования, но если бы T&& было rvalue, оно было бы безопасно сохранено в this лямбды. - person Adam; 23.12.2018
comment
Указанный мной вариант использования на самом деле является распространенным при написании функции, возвращающей диапазон (библиотека range/v3). Функция принимает аргументы, либо ссылки, либо типы значений, и преобразование/адаптация определяемых пользователем лямбда-выражений почти всегда требует использования всех аргументов. Таким образом, для каждой лямбда-преобразования текущим решением является захват всех аргументов вручную по ссылке или значению, в основном повторяя аргументы жилищной функции. - person Adam; 23.12.2018

Хорошо, давайте попробуем это. К сожалению, у меня нет под рукой компилятора, поддерживающего эту функцию, поэтому простите меня, если я серьезно неправильно истолковал некоторые вещи.

Предложение по этому вопросу: N3648.

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

Тип этого члена соответствует типу объявления гипотетической переменной формы "auto init-capture ;" [...].

Таким образом, вопрос о том, что вы получаете от списка захвата [c = std::forward<T>(t)], эквивалентен тому, что вы получаете от объявления auto c = std::forward<T>(t).

Выведенный тип здесь будет std::remove_reference<T>::type (ссылочные квалификаторы отбрасываются на auto), поэтому вы всегда будете получать с новым значением здесь. Если t было ссылкой на rvalue, вы будете перемещать-конструировать это новое значение, в противном случае вы будете копировать-конструировать (из-за возвращаемого значения std::forward).

Хорошо, что это новое значение принадлежит лямбде. Таким образом, независимо от того, какие t вы передали изначально, std::move из захваченных c безопасно. Таким образом, даже если вы больше не знаете тип начального t, вы все равно ничего не потеряли по пути.

person ComicSansMS    schedule 20.01.2014
comment
Я думаю, вам понадобится тип оболочки для поддержки идеальной пересылки. Элемент ссылки rvalue запрещает копирование, а конструктор перемещения не обязательно должен быть доступен для лямбда-выражения. - person dyp; 20.01.2014
comment
@dyp Да, подойдет тип оболочки. Однако это также было бы довольно некрасиво (std::auto_ptr_ref, кто-нибудь?). Интересно то, что я не думаю, что мы понесем какие-либо существенные потери, пожертвовав здесь идеальной пересылкой: мы по-прежнему уходим только с ходами, если мы передаем rvalue и копируем в противном случае. - person ComicSansMS; 20.01.2014
comment
Это правда, но ходы тоже могут быть дорогими, например. когда элемент доступен только для копирования (устаревший тип данных, массив и т. д.). Я подумал о чем-то вроде template<class T> wrapper { T m; }; template<class T> auto wrap(T&& p) -> wrapper<T&&> { return {std::forward<T>(p)}; }, а затем [t = wrap(std::forward<T>(t))]) - person dyp; 20.01.2014
comment
Спасибо за ваши исследования UglyFontMS ;) Вместе с предложенной оболочкой dyp это определенно решение для меня. Вы можете рассмотреть возможность включения этого в свой ответ - в любом случае я приму это. - person Richard Vock; 20.01.2014
comment
К вашему сведению, clang++ в coliru — это основная версия, выпущенная вскоре после выпуска 3.4, поэтому она поддерживает большинство/все функции языка C++14. - person Casey; 20.01.2014

Чтобы добиться желаемого поведения при захвате по ссылке, не требуется общего лямбда-захвата C++14 (но, как всегда, при захвате по ссылке требуется осторожность, чтобы не создавать оборванную ссылку):

template <typename T>
void func(T&& t) {
    other_func([&t](int v) { return  std::forward<T>(t) + v; });
}

Напротив, если принято решение использовать захват по значению, лямбда-выражение должно быть помечено как изменяемое, чтобы обеспечить эффективное перемещение (поскольку к лямбда-выражениям неявно добавляется квалификатор const):

template <typename T>
void func(T&& t) {
    other_func([t = std::forward<T>(t)](int v) mutable { return  std::move(t) + v; });
}
person user1115339    schedule 20.01.2014
comment
Лямбда в приведенном выше примере не захватывается по значению, поэтому mutable здесь не нужен (действительно, хороший способ избежать mutable и общих лямбда-захватов С++ 14) - person user1115339; 21.01.2014
comment
@ user1115339: чтобы уточнить, когда вы говорите, что, поскольку квалификатор const неявно добавляется к лямбда-выражениям, вы имеете в виду, что переменные, захваченные значением, неявно константны (в данном случае t, а не само лямбда-выражение)? - person ; 27.06.2018