Захват отлично пересылаемой переменной в лямбда
template<typename T> void doSomething(T&& mStuff)
{
auto lambda([&mStuff]{ doStuff(std::forward<T>(mStuff)); });
lambda();
}
Правильно ли зафиксировать отлично переправленную переменную mStuff
с синтаксисом &mStuff
?
Или существует конкретный синтаксис захвата для отлично пересылаемых переменных?
EDIT: Что делать, если отлично переправленная переменная является пакетом параметров?
Ответы
Ответ 1
Правильно ли зафиксировать отлично переправленную переменную mStuff с синтаксис & mStuff?
Да, предполагая, что вы не используете эту лямбду вне doSomething
. Ваш код захватывает mStuff
за ссылку и будет правильно перенаправлять ее внутри лямбда.
Для того, чтобы mStuff являлся пакетом параметров, достаточно использовать простой захват с расширением пакета:
template <typename... T> void doSomething(T&&... mStuff)
{
auto lambda = [&mStuff...]{ doStuff(std::forward<T>(mStuff)...); };
}
Лямбда фиксирует каждый элемент mStuff
для каждой ссылки. Объект закрытия сохраняет значение lvalue для каждого аргумента независимо от его категории значений. Совершенная пересылка все еще работает; Фактически, нет даже разницы, потому что именованные ссылки rvalue будут lvalues в любом случае.
Ответ 2
Чтобы сделать лямбду действительной вне области ее создания, вам нужен класс-оболочка, который обрабатывает значения lvalues и rvalues по-разному, то есть сохраняет ссылку на lvalue, но делает копию (путем перемещения) rvalue.
Файл заголовка capture.h:
#pragma once
#include <type_traits>
#include <utility>
template < typename T >
class capture_wrapper
{
static_assert(not std::is_rvalue_reference<T>{},"");
std::remove_const_t<T> mutable val_;
public:
constexpr explicit capture_wrapper(T&& v)
noexcept(std::is_nothrow_move_constructible<std::remove_const_t<T>>{})
:val_(std::move(v)){}
constexpr T&& get() const noexcept { return std::move(val_); }
};
template < typename T >
class capture_wrapper<T&>
{
T& ref_;
public:
constexpr explicit capture_wrapper(T& r) noexcept : ref_(r){}
constexpr T& get() const noexcept { return ref_; }
};
template < typename T >
constexpr typename std::enable_if<
std::is_lvalue_reference<T>{},
capture_wrapper<T>
>::type
capture(std::remove_reference_t<T>& t) noexcept
{
return capture_wrapper<T>(t);
}
template < typename T >
constexpr typename std::enable_if<
std::is_rvalue_reference<T&&>{},
capture_wrapper<std::remove_reference_t<T>>
>::type
capture(std::remove_reference_t<T>&& t)
noexcept(std::is_nothrow_constructible<capture_wrapper<std::remove_reference_t<T>>,T&&>{})
{
return capture_wrapper<std::remove_reference_t<T>>(std::move(t));
}
template < typename T >
constexpr typename std::enable_if<
std::is_rvalue_reference<T&&>{},
capture_wrapper<std::remove_reference_t<T>>
>::type
capture(std::remove_reference_t<T>& t)
noexcept(std::is_nothrow_constructible<capture_wrapper<std::remove_reference_t<T>>,T&&>{})
{
return capture_wrapper<std::remove_reference_t<T>>(std::move(t));
}
Пример/тестовый код, который показывает, что он работает. Обратите внимание, что пример "bar" показывает, как можно использовать std::tuple<...>
, чтобы обойти отсутствие расширения пакета в инициализаторе лямбда-захвата, полезного для вариационного захвата.
#include <cassert>
#include <tuple>
#include "capture.h"
template < typename T >
auto foo(T&& t)
{
return [t = capture<T>(t)]()->decltype(auto)
{
auto&& x = t.get();
return std::forward<decltype(x)>(x);
// or simply, return t.get();
};
}
template < std::size_t... I, typename... T >
auto bar_impl(std::index_sequence<I...>, T&&... t)
{
static_assert(std::is_same<std::index_sequence<I...>,std::index_sequence_for<T...>>{},"");
return [t = std::make_tuple(capture<T>(t)...)]()
{
return std::forward_as_tuple(std::get<I>(t).get()...);
};
}
template < typename... T >
auto bar(T&&... t)
{
return bar_impl(std::index_sequence_for<T...>{}, std::forward<T>(t)...);
}
int main()
{
static_assert(std::is_same<decltype(foo(0)()),int&&>{}, "");
assert(foo(0)() == 0);
auto i = 0;
static_assert(std::is_same<decltype(foo(i)()),int&>{}, "");
assert(&foo(i)() == &i);
const auto j = 0;
static_assert(std::is_same<decltype(foo(j)()),const int&>{}, "");
assert(&foo(j)() == &j);
const auto&& k = 0;
static_assert(std::is_same<decltype(foo(std::move(k))()),const int&&>{}, "");
assert(foo(std::move(k))() == k);
auto t = bar(0,i,j,std::move(k))();
static_assert(std::is_same<decltype(t),std::tuple<int&&,int&,const int&,const int&&>>{}, "");
assert(std::get<0>(t) == 0);
assert(&std::get<1>(t) == &i);
assert(&std::get<2>(t) == &j);
assert(std::get<3>(t) == k and &std::get<3>(t) != &k);
}
Ответ 3
Да, вы можете сделать идеальный захват, но не напрямую. Вам нужно будет обернуть тип в другом классе:
#define REQUIRES(...) class=std::enable_if_t<(__VA_ARGS__)>
template<class T>
struct wrapper
{
T value;
template<class X, REQUIRES(std::is_convertible<T, X>())>
wrapper(X&& x) : value(std::forward<X>(x))
{}
T get() const
{
return std::move(value);
}
};
template<class T>
auto make_wrapper(T&& x)
{
return wrapper<T>(std::forward<T>(x));
}
Затем передайте их как параметры в лямбда, которая возвращает вложенную лямбду, которая захватывает параметры по значению:
template<class... Ts>
auto do_something(Ts&&... xs)
{
auto lambda = [](auto... ws)
{
return [=]()
{
// Use `.get()` to unwrap the value
some_other_function(ws.get()...);
};
}(make_wrapper(std::forward<Ts>(xs)...));
lambda();
}
Ответ 4
TTBOMK, для С++ 14, я думаю, что приведенные выше решения для обработки времени жизни могут быть упрощены до:
template <typename T> capture { T value; }
template <typename T>
auto capture_example(T&& value) {
capture<T> cap{std::forward<T>(value)};
return [cap = std::move(cap)]() { /* use cap.value *; };
};
или более анонимно:
template <typename T>
auto capture_example(T&& value) {
struct { T value; } cap{std::forward<T>(value)};
return [cap = std::move(cap)]() { /* use cap.value *; };
};
Использовал это здесь (по общему признанию, этот конкретный блок кода довольно бесполезен: P)
https://github.com/EricCousineau-TRI/repro/blob/3fda1e0/cpp/generator.cc#L161-L176
Ответ 5
Вот решение для С++ 17, которое использует руководства по удержанию, чтобы упростить его. Я разрабатываю статью в блоге Витторио Ромео (ОП) blog post, где он предлагает решение своего вопроса.
std::tuple
может использоваться, чтобы обернуть идеально перенаправляющие переменные, создавая копию или сохраняя ссылку на каждую из них для каждой переменной, по мере необходимости. Сам кортеж фиксируется лямбда-значением.
Чтобы сделать его проще и чище, я собираюсь создать новый тип, производный от std::tuple
, чтобы обеспечить управляемую конструкцию (которая позволит нам избегать шаблонов std::forward
и decltype()
) и указатели в случае доступа есть только одна переменная для захвата.
// This is the generic case
template <typename... T>
struct forwarder: public std::tuple<T...> {
using std::tuple<T...>::tuple;
};
// This is the case when just one variable is being captured.
template <typename T>
struct forwarder<T>: public std::tuple<T> {
using std::tuple<T>::tuple;
// Pointer-like accessors
auto &operator *() {
return std::get<0>(*this);
}
const auto &operator *() const {
return std::get<0>(*this);
}
auto *operator ->() {
return &std::get<0>(*this);
}
const auto *operator ->() const {
return &std::get<0>(*this);
}
};
// std::tuple_size needs to be specialized for our type,
// so that std::apply can be used.
namespace std {
template <typename... T>
struct tuple_size<forwarder<T...>>: tuple_size<tuple<T...>> {};
}
// The below two functions declarations are used by the deduction guide
// to determine whether to copy or reference the variable
template <typename T>
T forwarder_type(const T&);
template <typename T>
T& forwarder_type(T&);
// Here comes the deduction guide
template <typename... T>
forwarder(T&&... t) -> forwarder<decltype(forwarder_type(std::forward<T>(t)))...>;
А потом можно использовать его следующим образом.
Вариантная версия:
// Increment each parameter by 1 at each invocation and print it.
// Rvalues will be copied, Lvalues will be passed as references.
auto variadic_incrementer = [](auto&&... a)
{
return [a = forwarder(a...)]() mutable
{
std::apply([](auto &&... args) {
(++args._value,...);
((std::cout << "variadic_incrementer: " << args._value << "\n"),...);
}, a);
};
};
Версия без вариадности:
// Increment the parameter by 1 at each invocation and print it.
// Rvalues will be copied, Lvalues will be passed as references.
auto single_incrementer = [](auto&& a)
{
return [a = forwarder(a)]() mutable
{
++a->_value;
std::cout << "single_incrementer: " << a->_value << "\n";
};
};