Захват универсальной ссылкой
При передаче выведенного типа в качестве ссылки на 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)] ?
Я до сих пор не получаю механику новых инициализаторов захвата...
Ответы
Ответ 1
Вы можете "захватывать по универсальной ссылке" в С++ 11, так как тип параметра шаблона T
доступен для функции лямбда (отвратительный пример живого кода в Coliru):
template <typename T>
void func(T&& t) {
other_func([&t](int v) {
return std::forward<T>(t) + v;
});
}
Ответ 2
Хорошо, попробуй это. К сожалению, у меня нет компилятора, который поддерживает эту функцию под рукой, поэтому простите меня, если я серьезно неверно истолковал вещи на этом пути.
Предложение, касающееся этого, N3648.
Интересная часть здесь заключается в том, что тип переменной в захвате init выводится так, как если бы использовалось 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
, вы все равно ничего не потеряли на этом пути.
Ответ 3
Для того, чтобы достичь желаемого поведения при захвате в качестве ссылки, не общей лямбды захвата С++ 14 не требуется (но как всегда с захватом ссылкой, следует соблюдать осторожность, чтобы не создавать оборванные ссылки):
template <typename T>
void func(T&& t) {
other_func([&t](int v) { return std::forward<T>(t) + v; });
}
Напротив, если решение принято, чтобы использовать захват значения лямбда должна быть помечена как изменяемые, чтобы обеспечить эффективное перемещение (поскольку константный классификатор неявно добавляется к лямбде):
template <typename T>
void func(T&& t) {
other_func([t = std::forward<T>(t)](int v) mutable { return std::move(t) + v; });
}