Captura de variables perfectamente reenviadas en lambda
template<typename T> void doSomething(T&& mStuff)
{
auto lambda([&mStuff]{ doStuff(std::forward<T>(mStuff)); });
lambda();
}
¿Es correcto capturar la variable mStuff
perfectamente reenviada con la sintaxis &mStuff
?
¿O hay una sintaxis de captura específica para variables perfectamente reenviadas?
EDIT: ¿Qué pasa si la variable perfectamente reenviada es un paquete de parámetros?
3 answers
¿Es correcto capturar la variable mStuff perfectamente reenviada con ¿la sintaxis de & mStuff?
Sí, suponiendo que no uses esta lambda fuera de doSomething
. Su código captura mStuff
por referencia y lo reenviará correctamente dentro de la lambda.
Para que mStuff sea un paquete de parámetros basta con usar una simple-capture con una expansión de paquete:
template <typename... T> void doSomething(T&&... mStuff)
{
auto lambda = [&mStuff...]{ doStuff(std::forward<T>(mStuff)...); };
}
La lambda captura cada elemento de mStuff
por referencia. El objeto closure guarda una referencia lvalue para cada argumento, independientemente de su categoría de valor. El reenvío perfecto todavía funciona; De hecho, ni siquiera hay una diferencia porque las referencias con nombre rvalue serían lvalues de todos modos.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-10 00:26:07
Para hacer que la lambda sea válida fuera del ámbito donde se creó, necesita una clase wrapper que maneje lvalues y rvalues de manera diferente, es decir, mantenga una referencia a un lvalue, pero haga una copia de (moviendo) un rvalue.
Captura de archivo de encabezado.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));
}
Ejemplo/código de prueba que muestra que funciona. Tenga en cuenta que el ejemplo "bar" muestra cómo se puede usar std::tuple<...>
para evitar la falta de expansión de paquetes en lambda capture initializer, útil para la captura variádica.
#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);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-10 01:27:27
Sí se puede hacer una captura perfecta, pero no directamente. Necesitará envolver el tipo en otra clase:
#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));
}
Luego pasarlos como parámetros a un lambda que devuelve un lambda anidado que captura los parámetros por valor:
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();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-20 13:56:36