26

When passing a deduced type as r-value reference I get universal reference functionality and can archieve perfect forwarding like this:

template <typename T>
void func(T&& t) {
    other_func(std::forward<T>(t));
}

...due to the way T is derived and the standard's reference collapse rules.

Now consider other_func takes a function object

template <typename T>
void func(T&& t) {
    other_func([](int v) { return t + v; }); // I chose addition for example purposes
}

Now obviously this won't compile due to t not being captured. My question is: How do I capture it so the captured value will be whatever T is deduced to?

Is this possible using the new generic lambda captures? And if... how?

[t = std::forward<T>(t)] ? 

I still don't really get the mechanics of the new capture initializers...

Richard Vock
  • 1,286
  • 10
  • 23

3 Answers3

10

You can "capture by universal reference" in C++11, since the type of the template parameter T is available to the lambda function (hideous live code example at Coliru):

template <typename T>
void func(T&& t) {
  other_func([&t](int v) {
    return std::forward<T>(t) + v;
  });
}
Casey
  • 41,449
  • 7
  • 95
  • 125
  • Well that is interesting - thought the type of t inside the lambda would have collapsed to an l-value reference. On another note: What is the name of the reference specifiers you write after the operator declaration? Didn't know these exist – Richard Vock Jan 20 '14 at 18:25
  • @RichardVock Those are [ref-qualifiers](http://stackoverflow.com/questions/17521238/what-are-rvalue-references-for-this-for). Much like `const`-qualifiers on member functions, they constrain the objects upon which the function may be called. E.g., `void foo() &&`is invoked when you call `foo` on an rvalue object. – Casey Jan 20 '14 at 18:29
  • 8
    A possible problem: if `other_func` stores that lambda and it lasts longer than `func`'s body scope, invoking the lambda is UB as the captured variable `t` has left scope. – Yakk - Adam Nevraumont Feb 04 '14 at 21:47
  • 1
    I found this SO question because I was searching for something like `[&=t]` (or `[&=]` for all). What I mean by that – reference types would be saved in lambda's `this` by reference and variables of value types or rvalue reference types would be saved by value. Is something like that possible? That way, the possible scope problem (in the comment above) would be much less of a problem. Specifically in my case say there is no `other_func` but rather I return the lambda in `func`. If T was a reference type, there'd be no copying, but if T&& was an rvalue, it'd safely stored in lambda's `this`. – Adam Dec 23 '18 at 12:07
  • 1
    The use case I specified is actually a common one when writing a function that returns a range (`range/v3` library). The function takes arguments, either references or value types, and the transforming/adapting user-defined lambdas almost always would want to use all the arguments. So for every transform lambda, the current solution is to capture all the arguments manually by reference or value, basically repeating arguments of the housing function. – Adam Dec 23 '18 at 13:30
9

Okay, let's try this. Unfortunately I don't have a compiler that supports this feature at hand, so forgive me if I gravely misinterpret things along the way.

The proposal covering this is N3648.

The interesting part here is that the type of the variable in the init capture is deduced as if using auto:

The type of that member corresponds to the type of a hypothetical variable declaration of the form "auto init-capture ;" [...].

So the question of what you get from a capture list [c = std::forward<T>(t)] is equivalent to what you get from a declaration auto c = std::forward<T>(t).

The deduced type here will be std::remove_reference<T>::type (reference qualifiers are dropped by auto), so you will always end up with a new value here. If t was an rvalue reference, you will move-construct that new value, otherwise you will copy-construct (due to the return value of std::forward).

The good thing is that this new value is owned by the lambda. So no matter what t you passed in initially, it is safe to std::move from the captured c. So even though you do not know the type of the initial t any longer you still didn't lose anything along the way.

ComicSansMS
  • 51,484
  • 14
  • 155
  • 166
  • I think you'd need a wrapper type to support perfect forwarding. An rvalue reference member would disallow copying, and a move constructor doesn't need to be available for a lambda. – dyp Jan 20 '14 at 16:59
  • @dyp Yes, a wrapper type would work. However, it would also be quite ugly (`std::auto_ptr_ref`, anyone?). The interesting thing is, I don't think we suffer any substantial loss by sacrificing perfect forwarding here: We still get away with only moves if we pass in an rvalue and a copy otherwise. – ComicSansMS Jan 20 '14 at 17:06
  • 1
    That's true, but moves can be expensive too, e.g. when a member is copy-only (legacy data type, array, ..). I thought of something like `template wrapper { T m; }; template auto wrap(T&& p) -> wrapper { return {std::forward(p)}; }` and then `[t = wrap(std::forward(t))]`) – dyp Jan 20 '14 at 17:09
  • Thanks for your research effort UglyFontMS ;) Together with dyp's suggested wrapper this is definitely a solution for me. You might consider incorporating that into your answer - either way I'll accept it. – Richard Vock Jan 20 '14 at 17:26
  • FYI, the clang++ at coliru is a trunk rev from shortly after 3.4 release, so it supports most/all C++14 language features. – Casey Jan 20 '14 at 18:35
4

In order to achieve the desired behavior with capture by reference, no generic lambda capture of C++14 is required (but as always with capture by reference, caution is required not to create a dangling reference):

template <typename T>
void func(T&& t) {
    other_func([&t](int v) { return  std::forward<T>(t) + v; });
}

On the contrary if the decision is made to use capture by value, the lambda should be marked as mutable to allow effective moving (because a const qualifier is implicitly added to lambdas):

template <typename T>
void func(T&& t) {
    other_func([t = std::forward<T>(t)](int v) mutable { return  std::move(t) + v; });
}
user1115339
  • 509
  • 3
  • 4
  • The lambda in the example above does not capture by value - thus mutable is not necessary there (indeed a good way to avoid mutable and C++14 generic lambda captures) – user1115339 Jan 20 '14 at 20:23
  • @user1115339: to clarify, when you say "because a const qualifier is implicitly added to lambdas", you mean that the variables captured by value are implicitly const (t in this case, not the lambda expression itself)? –  Jun 27 '18 at 13:21