73
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?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 4
    `[mStuff = std::forward(mStuff)] {...}` – David G Nov 09 '14 at 18:02
  • 1
    @0x499602D2: what if it's a parameter pack? `mArgs = std::forward(mArgs)...` doesn't compile – Vittorio Romeo Nov 09 '14 at 18:07
  • That would be `[&mArgs...]`. – David G Nov 09 '14 at 18:16
  • @0x499602D2 That is capturing by reference, not by value. – Columbo Nov 09 '14 at 18:17
  • @0x499602D2: I'm confused - `&mSingleArg` doesn't forward but `&mParameterPack` does forward? – Vittorio Romeo Nov 09 '14 at 18:18
  • @Columbo [Actually it seems to perfect forward.](http://rextester.com/AZIPYJ29305) – David G Nov 09 '14 at 18:19
  • 1
    @0x499602D2 Of course it forwards. But you are capturing by reference. Your first comment captures by value. – Columbo Nov 09 '14 at 18:20
  • @0x499602D2: [It seems to do the same for single arguments as well.](http://rextester.com/MEXUGC70803) - so, is `&mArg` sufficient after all? – Vittorio Romeo Nov 09 '14 at 18:20
  • 1
    @VittorioRomeo If you want to capture by reference, of course it is. – Columbo Nov 09 '14 at 18:21
  • @Columbo: yeah, that was my intent, sorry it isn't clear from the question. I just want to forward everything to the lambda's body, as I would do if the lambda was a named function. – Vittorio Romeo Nov 09 '14 at 18:23
  • 1
    What are you doing with the lambda after you create it? Capture does NOT work like passing arguments: you simply **cannot** do something that matches perfect forwarding exactly due to quirks in the standard. – Yakk - Adam Nevraumont Nov 09 '14 at 19:09
  • 5
    @VittorioRomeo I saw that [you wrote an article about this](https://vittorioromeo.info/index/blog/capturing_perfectly_forwarded_objects_in_lambdas.html), linked from http://isocpp.org - which I'd recommended summarising in an answer here, as (by my limited understanding!) it seems considerably more detailed/accurate than any of the existing answers. – underscore_d Dec 12 '16 at 16:35
  • Good question, but I'm just curious: Could you please provide an example, where this scheme is really required? The only thing, that came to my mind a priori, is a further forwarding scheme within your lambda for instance. But since this lambda is very likely to be a "very local one", I still have a bit the feeling of a design squiggling... – Secundi Feb 17 '22 at 07:36

5 Answers5

41

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.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 6
    In the first case, within the lambda you have lvalue references to everything. And you have the strange fact that your lambda is only valid until you leave the current scope. Both mean that it isn't a general solution to the OP's problem. In the second and third case, you capture by-value, which also isn't the same as perfect forwarding (you do perfect forward into the by-value capture). In the forth case, it is similar, but no perfect forwarding occurs. In short, none of them are perfect analogues for "perfect forwarding". Try `forward_as_tuple` maybe? – Yakk - Adam Nevraumont Nov 09 '14 at 19:12
  • @Yakk I did rewrite my answer. However: Saving lvalue references is exactly what should be done here, `forward_as_tuple` is out of place. The "strange" fact that the lambda is only valid until I leave the scope is somehow self-explanatory considering the fact that it captures by reference. – Columbo Nov 10 '14 at 00:20
  • even capturing references by reference has that problem: technically the lambda is only valid until thr variable, not the referenced value, expires! I suspect some `std::ref` shenanigans are required die to annoyances around lambda quirks. – Yakk - Adam Nevraumont Nov 10 '14 at 01:05
  • @Yakk What exactly do you mean by "technically the lambda is only valid until thr variable, not the referenced value, expires!"? I have the vague feeling I missed some important nonsensical rule about Lambdas here – Columbo Nov 10 '14 at 01:19
  • 2
    the reference capture lifetime rule in the standard references captured variables, not data, and their scope. This allows an actual practical optimization (capture stack pointer only), which makes it a slight question if it is a defect or not. – Yakk - Adam Nevraumont Nov 10 '14 at 01:34
  • @Yakk Ahh, now I understand what you mean (I knew of this optimization earlier). – Columbo Nov 10 '14 at 01:39
  • @Yakk So where exactly lies the problem? `doSomething` can take rvalues, so using the lambda outside is, in general, problematic anyway. And I mentioned that you cannot use it outside `doSomething`. – Columbo Nov 10 '14 at 16:09
  • 1
    `std::tuple` where `T&&` is deduced gives you values for rvalues and references for lvalues and can be safely returned, and corresponds to how manual function objects returned from functions with perfect forwarding are usually implemented. – Yakk - Adam Nevraumont Nov 10 '14 at 16:48
  • @Yakk Yes, but in this case it's not clear from the question whether he wants to actually take the lambda outside. If he doesn't, unnecessary moves could occur. – Columbo Nov 10 '14 at 16:55
  • 1
    @Yakk Are we talking about http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2011 here? – T.C. Mar 29 '15 at 02:23
  • @Yakk-AdamNevraumont one of your comments made me ask https://stackoverflow.com/q/55559308/1088790 – haelix Apr 07 '19 at 13:42
8

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

}
Hui
  • 337
  • 2
  • 5
7

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

Eric Cousineau
  • 1,944
  • 14
  • 23
  • 1
    Or even shorter `[cap = capture {std::forward (value)}] { /* use cap.value */ }` – levzettelin May 26 '19 at 15:04
  • 3
    Can also use `std::tuple` instead of the bespoke `capture` type: `[cap = std::tuple (std::forward (value))] { /* use std::get<0> (cap) */ }` – levzettelin May 26 '19 at 15:09
5

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();
}
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
  • With C++14, the new lambda capture expression will be helpful. Check out the use of wrapper in curry at http://nvwa.cvs.sourceforge.net/viewvc/nvwa/nvwa/functional.h?view=markup. My test shows using this reduces the number of constructor calls. – Yongwei Wu Jan 10 '15 at 03:12
  • Updated link (I think): https://github.com/adah1972/nvwa/blob/4907694/nvwa/functional.h#L167 – Eric Cousineau Nov 04 '18 at 20:14
4

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";
    };
};
Fabio A.
  • 2,517
  • 26
  • 35