Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capturing perfectly-forwarded variable in lambda

template<typename T> void doSomething(T&& mStuff)
{
    auto lambda([&mStuff]{ doStuff(std::forward<T>(mStuff)); });
    lambda();
}

Is it correct to capture the perfectly-forwarded mStuff variable with the &mStuff syntax?

Or is there a specific capture syntax for perfectly-forwarded variables?

EDIT: What if the perfectly-forwarded variable is a parameter pack?

like image 643
Vittorio Romeo Avatar asked Nov 09 '14 18:11

Vittorio Romeo


People also ask

How do you capture a member variable in lambda?

To capture the member variables inside lambda function, capture the “this” pointer by value i.e. std::for_each(vec. begin(), vec. end(), [this](int element){ //.... }

What is perfect forwarding?

What is Perfect Forwarding. Perfect forwarding allows a template function that accepts a set of arguments to forward these arguments to another function whilst retaining the lvalue or rvalue nature of the original function arguments.

Does lambda capture reference by value?

The mutable keyword is used so that the body of the lambda expression can modify its copies of the external variables x and y , which the lambda expression captures by value. Because the lambda expression captures the original variables x and y by value, their values remain 1 after the lambda executes.

Is lambda capture a const?

By default, variables are captured by const value . This means when the lambda is created, the lambda captures a constant copy of the outer scope variable, which means that the lambda is not allowed to modify them.


5 Answers

Is it correct to capture the perfectly-forwarded mStuff variable with the &mStuff syntax?

Yes, assuming that you don't use this lambda outside doSomething. Your code captures mStuff per reference and will correctly forward it inside the lambda.

For mStuff being a parameter pack it suffices to use a simple-capture with a pack-expansion:

template <typename... T> void doSomething(T&&... mStuff)
{
    auto lambda = [&mStuff...]{ doStuff(std::forward<T>(mStuff)...); };
}

The lambda captures every element of mStuff per reference. The closure-object saves an lvalue reference for to each argument, regardless of its value category. Perfect forwarding still works; In fact, there isn't even a difference because named rvalue references would be lvalues anyway.

like image 133
Columbo Avatar answered Oct 18 '22 20:10

Columbo


To make the lambda valid outside the scope where it's created, you need a wrapper class that handles lvalues and rvalues differently, i.e., keeps a reference to an lvalue, but makes a copy of (by moving) an rvalue.

Header file 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));
}

Example/test code that shows it works. Note that the "bar" example shows how one can use std::tuple<...> to work around the lack of pack expansion in lambda capture initializer, useful for variadic capture.

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

}
like image 29
Hui Avatar answered Oct 18 '22 19:10

Hui


TTBOMK, for C++14, I think the above solutions for lifetime handling can be simplified to:

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

or more anonymous:

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

Used it here (admittedly, this particular block of code is rather useless :P)

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

like image 26
Eric Cousineau Avatar answered Oct 18 '22 19:10

Eric Cousineau


Yes you can do perfect capturing, but not directly. You will need to wrap the type in another class:

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

Then pass them as parameters to a lambda that returns a nested lambda that captures the parameters by value:

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();
}
like image 36
Paul Fultz II Avatar answered Oct 18 '22 20:10

Paul Fultz II


Here's a solution for C++17 that uses deduction guides to make it easy. I'm elaborating on Vittorio Romeo's (the OP) blog post, where he provides a solution to his own question.

std::tuple can be used to wrap the perfectly forwarded variables, making a copy or keeping a reference of each of them on a per-variable basis, as needed. The tuple itself is value-captured by the lambda.

To make it easier and cleaner, I'm going to create a new type derived from std::tuple, so to provide guided construction (that will let us avoid the std::forward and decltype() boilerplate) and pointer-like accessors in case there's just one variable to capture.

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

And then one can use it like following.

The variadic version:

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

The non-variadic version:

// 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";
    };
};
like image 3
Fabio A. Avatar answered Oct 18 '22 18:10

Fabio A.