ventajas tutorial sintaxis que expresiones espaƱol entre diferencia aprender c++ c++11 lambda c++14 perfect-forwarding

c++ - tutorial - que es expresiones lambda



Capturando variables perfectamente reenviadas en lambda (5)

¿Es correcto capturar la variable mStuff perfectamente reenviada con la sintaxis & 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, es suficiente usar una captura simple 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 de cierre guarda una referencia de valor para cada argumento, independientemente de su categoría de valor. El reenvío perfecto aún funciona; De hecho, ni siquiera hay una diferencia porque las referencias rvalue nombradas serían valores de todos modos.

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?

EDITAR: ¿Qué pasa si la variable perfectamente reenviada es un paquete de parámetros?


Aquí hay una solución para C ++ 17 que utiliza guías de deducción para facilitarlo. Estoy elaborando la publicación de blog de Vittorio Romeo (OP), donde proporciona una solución a su propia pregunta.

std::tuple se puede usar para ajustar las variables perfectamente reenviadas, hacer una copia o mantener una referencia de cada una de ellas por variable, según sea necesario. La tupla en sí es capturada por el valor de la lambda.

Para hacerlo más fácil y limpio, voy a crear un nuevo tipo derivado de std::tuple , para proporcionar una construcción guiada (que nos permita evitar el decltype() std::forward y decltype() y decltype() puntero en caso de que solo haya una variable para capturar.

// 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)))...>;

Y luego uno puede usarlo como sigue.

La versión variadic:

// 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); }; };

La versión no variadic:

// 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"; }; };


Para que la lambda sea válida fuera del ámbito donde se creó, necesita una clase de contenedor que maneje los valores y los valores de manera diferente, es decir, mantenga una referencia a un valor l, pero haga una copia (moviendo) un valor r.

Encabezado del archivo 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)); }

Ejemplo / código de prueba que muestra que funciona. Tenga en cuenta que el ejemplo de "barra" muestra cómo se puede usar std::tuple<...> para evitar la falta de expansión del paquete en el inicializador de captura lambda, útil para la captura variadic.

#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); }


Sí, puedes hacer una captura perfecta, pero no directamente. Deberá ajustar 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, páselos como parámetros a una lambda que devuelva una lambda anidada que capture 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(); }


TTBOMK, para C ++ 14, creo que las soluciones anteriores para el manejo de por vida se pueden simplificar para:

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 *; }; };

o más anónimo:

template <typename T> auto capture_example(T&& value) { struct { T value; } cap{std::forward<T>(value)}; return [cap = std::move(cap)]() { /* use cap.value *; }; };

Lo usé aquí (ciertamente, este bloque de código en particular es bastante inútil: P)

https://github.com/EricCousineau-TRI/repro/blob/3fda1e0/cpp/generator.cc#L161-L176