How do I write a generic forwarding lambda in C++14?
Try #1
[](auto&& x) { return x; }
Inside the function body, x is an lvalue, so this doesn't work.
Try #2
[](auto&& x) { return std::forward<decltype(x)>(x); }
This properly forwards references inside the lambda, but it will always return by value (unless the compiler elides the copy).
Try #3
[](auto&& x) -> decltype(x) { return std::forward<decltype(x)>(x); }
This returns the same type as the argument (probably -> auto&& would work as well) and appears to work properly.
Try #4
[](auto&& x) noexcept -> decltype(x) { return std::forward<decltype(x)>(x); }
Does adding noexcept make this lambda more applicable and thus strictly better than #3?
A return type of decltype(x) is insufficient.
Local variables and function parameters taken by value can be implicitly moved into the return value, but not function parameters taken by rvalue reference (x is an lvalue, even though decltype(x) == rvalue reference if you pass an rvalue). The reasoning the committee gave is that they wanted to be certain that when the compiler implicitly moves, no one else could possibly have it. That is why we can move from a prvalue (a temporary, a non-reference qualified return value) and from function-local values. However, someone could do something silly like
std::string str = "Hello, world!";
f(std::move(str));
std::cout << str << '\n';
And the committee didn't want to silently invoke an unsafe move, figuring that they should start out more conservative with this new "move" feature. Note that in C++20, this issue will be resolved, and you can simply do return x and it will do the right thing. See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0527r0.html
For a forwarding function, I would attempt to get the noexcept correct. It is easy here since we are just dealing with references (it is unconditionally noexcept). Otherwise, you break code that cares about noexcept.
This makes the final ideal forwarding lambda look as follows:
auto lambda = [](auto && x) noexcept -> auto && { return std::forward<decltype(x)>(x); };
A return type of decltype(auto) will do the same thing here, but auto && better documents that this function always returns a reference. It also avoids mentioning the variable name again, which I suppose makes it slightly easier to rename the variable.
As of C++17, the forwarding lambda is also implicitly constexpr where possible.
Your first two tries don't work because return type deduction drops top-level cv-qualifiers and references, which makes generic forwarding impossible. Your third is exactly correct just unnecessarily verbose, the cast is implicit in the return type. And noexcept is unrelated to forwarding.
Here are a few more options worth throwing out for completeness:
auto v0 = [](auto&& x) -> decltype(x) { return x; };
auto v1 = [](auto&& x) -> auto&& { return x; };
auto v2 = [](auto&& x) -> auto&& { return std::forward<decltype(x)>(x); };
auto v3 = [](auto&& x) -> decltype(auto) { return std::forward<decltype(x)>(x); };
v0 will compile and look as if it returns the correct type, but will fail at compilation if you call it with an rvalue reference due to the requested implicit conversion from lvalue reference (x) to rvalue reference (decltype(x)). This fails on both gcc and clang.
v1 will always return an lvalue reference. If you call it with temporary, that gives you a dangling reference.
Both v2 and v3 are correct, generic forwarding lambdas. Thus, you have three options for your trailing return type (decltype(auto), auto&&, or decltype(x)) and one option for the body of your lambda (a call to std::forward).
The fewest characters, yet fully featured, version I can generate is:
[](auto&&x)noexcept->auto&&{return decltype(x)(x);}
this uses an idiom I find useful -- when forwarding auto&& parameters in a lambda, do decltype(arg)(arg). Forwarding the decltype through forward is relatively pointless if you know that the arg is of reference type.
If x was of value type, decltype(x)(x) actually produces a copy of x, while std::forward<decltype(x)>(x) produces an rvalue reference to x. So the decltype(x)(x) pattern is less useful in the general case than forward: but this isn't the general case.
auto&& will allow references to be returned (matching the incoming references). Sadly, reference lifetime extension won't work properly with the above code -- I find that forwarding rvalue references into rvalue references is often the wrong solution.
template<class T>struct tag{using type=T;};
template<class Tag>using type=typename Tag::type;
template<class T> struct R_t:tag<T>{};
template<class T> struct R_t<T&>:tag<T&>{};
template<class T> struct R_t<T&&>:tag<T>{};
template<class T>using R=type<R_t<T>>;
[](auto&&x)noexcept->R<decltype(x)>{return decltype(x)(x);}
gives you that behavior. lvalue references become lvalue references, rvalue references become values.
Related
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3255.html defines decay_copy as follows:
template<typename T>
std::decay_t<T> decay_copy(T&& v)
{
return std::forward<T>(v);
}
I just wonder:
Is it equivalent to the following simpler one?
auto decay_copy(auto&& v)
{
return v;
}
It wasn't in 2011, because:
We didn't have auto return type deduction for functions (that's a C++14 feature), and
We didn't have auto&& parameters for functions (that's a C++20 feature), and
Rvalue references were not implicitly moved from in return statements (that's also a C++20 feature)
But in C++20, yes, that is now a valid way to implement decay_copy. auto deduction does decay, return v; implicitly forwards, and everything else is the same.
I guess technically there's an edge case like:
struct X {
X();
X(X&);
};
With the original formulation of decay_copy(X{}), this is ill-formed (there's no viable constructor for constructing an X from an rvalue X). With the new formulation under the existing C++20 rules, it becomes well formed and invokes the non-const copy constructor (because we do this two-step overload resolution).
If P2266 is adopted though, then they would be exactly equivalent because return v; would always treat v as an rvalue, exactly as the existing formulation does.
The following code:
#include <tuple>
int main ()
{
auto f = [] () -> decltype (auto)
{
return std::get<0> (std::make_tuple (0));
};
return f ();
}
(Silently) generates code with undefined behaviour - the temporary rvalue returned by make_tuple is propagated through the std::get<> and through the decltype(auto) onto the return type. So it ends up returning a reference to a temporary that has gone out of scope. See it here https://godbolt.org/g/X1UhSw.
Now, you could argue that my use of decltype(auto) is at fault. But in my generic code (where the type of the tuple might be std::tuple<Foo &>) I don't want to always make a copy. I really do want to extract the exact value or reference from the tuple.
My feeling is that this overload of std::get is dangerous:
template< std::size_t I, class... Types >
constexpr std::tuple_element_t<I, tuple<Types...> >&&
get( tuple<Types...>&& t ) noexcept;
Whilst propagating lvalue references onto tuple elements is probably sensible, I don't think that holds for rvalue references.
I'm sure the standards committee thought this through very carefully, but can anyone explain to me why this was considered the best option?
Consider the following example:
void consume(foo&&);
template <typename Tuple>
void consume_tuple_first(Tuple&& t)
{
consume(std::get<0>(std::forward<Tuple>(t)));
}
int main()
{
consume_tuple_first(std::tuple{foo{}});
}
In this case, we know that std::tuple{foo{}} is a temporary and that it will live for the entire duration of the consume_tuple_first(std::tuple{foo{}}) expression.
We want to avoid any unnecessary copy and move, but still propagate the temporarity of foo{} to consume.
The only way of doing that is by having std::get return an rvalue reference when invoked with a temporary std::tuple instance.
live example on wandbox
Changing std::get<0>(std::forward<Tuple>(t)) to std::get<0>(t) produces a compilation error (as expected) (on wandbox).
Having a get alternative that returns by value results in an additional unnecessary move:
template <typename Tuple>
auto myget(Tuple&& t)
{
return std::get<0>(std::forward<Tuple>(t));
}
template <typename Tuple>
void consume_tuple_first(Tuple&& t)
{
consume(myget(std::forward<Tuple>(t)));
}
live example on wandbox
but can anyone explain to me why this was considered the best option?
Because it enables optional generic code that seamlessly propagates temporaries rvalue references when accessing tuples. The alternative of returning by value might result in unnecessary move operations.
IMHO this is dangerous and quite sad since it defeats the purpose of the "most important const":
Normally, a temporary object lasts only until the end of the full expression in which it appears. However, C++ deliberately specifies that binding a temporary object to a reference to const on the stack lengthens the lifetime of the temporary to the lifetime of the reference itself, and thus avoids what would otherwise be a common dangling-reference error.
In light of the quote above, for many years C++ programmers have learned that this was OK:
X const& x = f( /* ... */ );
Now, consider this code:
struct X {
void hello() const { puts("hello"); }
~X() { puts("~X"); }
};
auto make() {
return std::variant<X>{};
}
int main() {
auto const& x = std::get<X>(make()); // #1
x.hello();
}
I believe anyone should be forgiven for thinking that line #1 is OK. However, since std::get returns a reference to an object that is going to be destroyed, x is a dangling reference. The code above outputs:
~X
hello
which shows that the object that x binds to is destroyed before hello() is called. Clang gives a warning about the issue but gcc and msvc don't. The same issue happens if (as in the OP) we use std::tuple instead of std::variant but, sadly enough, clang doesn't issues a warning for this case.
A similar issue happens with std::optional and this value overload:
constexpr T&& value() &&;
This code, which uses the same X above, illustrates the issue:
auto make() {
return std::optional{X{}};
}
int main() {
auto const& x = make().value();
x.hello();
}
The output is:
~X
~X
hello
Brace yourself for more of the same with C++23's std::except and its methods value() and error():
constexpr T&& value() &&;
constexpr E&& error() && noexcept;
I'd rather pay the price of the move explained in Vittorio Romeo's post. Sure, I can avoid the issue by removing & from lines #1 and #2. My point is that the rule for the "most important const" just became more complicated and we need to consider if the expression involves std::get, std::optional::value, std::expected::value, std::expected::error, ...
What is the point of defining a local variable to be an rvalue reference or a forwarding (universal) reference? As far as I understand, any variable that has a name is an lvalue and will be treated as such moving forward.
Example:
Widget&& w1 = getWidget();
auto&& w2 = getWidget();
w1 and w2 are both lvalues, and will be treated as such if they're passed as arguments later on. Their decltype is probably not, but what difference does this make? Why would anyone need to define variables that way?
If you have a function returning a temporary which cannot be moved.
Foo some_function();
auto&& f = some_function();
This is legal. auto f = some_function(); will either copy (which could be expensive), or fail to compile (if the class also cannot be copied).
In general, auto&& deduces down to either an r or lvalue reference depending on what it is initialized with, and if initialized with a temporary extends its lifetime while giving you access to it as an lvalue.
A classic use is in the 'just-loop' pattern:
for( auto&& x : some_range )
where there is actually a auto&& x = *it; in the code generated.
You cannot bind a non-constant lvalue reference to a temporary, so your other choice is Widget const&, which doesn't let you modify the temporary during its lifetime.
This technique is also useful to decompose a complex expression and see what is going on. So long as you aren't working with extremely fragile expression templates, you can take the expression a+b+c*d and turn it into
auto&& c_times_d = d*d;
auto&& b_plus_c_times_d = b + decltype(c_times_d)c_times_d;
auto&& c_plus_b_plus_c_times_d = c + decltype(b_plus_c_times_d)b_plus_c_times_d;
and now you have access to the temporary objects whose lifetime is extended, and you can easily step through the code, or introduce extra steps between steps in a complex expression: and this happens mechanically.
The concern about fragile expression templates only holds if you fail to bind every sub-expression. (Note that using -> can generate a myriad of sub-expressions you might not notice.)
I use auto&& when I want to say "I'm storing the return value of some function, as is, without making a copy", and the type of the expression is not important. auto is when I want to make a local copy.
In generic code it can be highly useful.
Foo const& a(Foo*);
Bar a(Bar*);
template<class T>
auto do_stuff( T*ptr ) {
auto&& x = a(ptr);
}
here if you pass a Bar* it stores the temporary, but if you pass a Foo* to do_stuff it stores the const&.
It does the least it can.
Here is an example of a function returning a non-movable non-copyable object, and how auto&& lets you store it. It is otherwise useless, but it shows how it works:
struct Foo {
Foo(&&)=delete;
Foo(int x) { std::cout << "Foo " << x << " constructed\n";
};
Foo test() {
return {3};
}
int main() {
auto&& f = test();
}
As far as I know, there is no real, aka widely used purpose to define a local rvalue reference, since their nature, to not to bind to lvalues, is only helpful in overloading and deduction, so to define them as parameters of a function.
One could use them, to bind them to temporary values like
int &&rref = 5*2;
but since almost all compilers are optimizing the expression
int i = 5*2;
there is no real need, in therm of performance or avoid copying.
One example could be an array
template<class T, int N> using raw_array = T[N];
then
auto && nums = raw_array<int,4>{101, 102, 103, 104};
this allows the temporary to be used as if it was an ordinary array.
A variable declared auto&& will follow perfect forwarding rules. Try it for yourself. This is even how perfect forwarding is done with lambdas in c++14.
const Type& fun1();
Type&& fun2();
auto&& t1 = fun1(); // works
auto&& t2 = fun2(); // works too
I'm having trouble with std::functions created from lambdas if the function returns a reference but the return type isn't explicitly called out as a reference. It seems that the std::function is created fine with no warnings, but upon calling it, a value is returned when a reference is expected, causing things to blow up. Here's a very contrived example:
#include <iostream>
#include <vector>
#include <functional>
int main(){
std::vector<int> v;
v.push_back(123);
std::function<const std::vector<int>&(const std::vector<int>&)> callback =
[](const std::vector<int> &in){return in;};
std::cout << callback(v).at(0) << std::endl;
return 0;
}
This prints out garbage, however if the lambda is modified to explicitly return a const reference it works fine. I can understand the compiler thinking the lambda is return-by-value without the hint (when I originally ran into this problem, the lambda was directly returning the result from a function that returned a const reference, in which case I would think that the const reference return of the lambda would be deducible, but apparently not.) What I am surprised by is that the compiler lets the std::function be constructed from the lambda with mismatched return types. Is this behavior expected? Am I missing something in the standard that allows this mismatch to occur? I'm seeing this with g++ (GCC) 4.8.2, haven't tried it with anything else.
Thanks!
Why is it broken?
When the return type of a lambda is deduced, reference and cv-qualifications are dropped. So the return type of
[](const std::vector<int> &in){return in;};
is just std::vector<int>, not std::vector<int> const&. As a result, if we strip out the lambda and std::function part of your code, we effectively have:
std::vector<int> lambda(std::vector<int> const& in)
{
return in;
}
std::vector<int> const& callback(std::vector<int> const& in)
{
return lambda(in);
}
lambda returns a temporary. It effectively is just copied its input. This temporary is bound the reference return in callback. But temporaries bound to a reference in a return statement do not have their lifetime extended, so the temporary is destroyed at the end of the return statement. Thus, at this point:
callback(v).at(0)
-----------^
we have a dangling reference to a destroyed copy of v.
The solution is to explicitly specify the return type of the lambda to be a reference:
[](const std::vector<int> &in)-> const std::vector<int>& {return in;}
[](const std::vector<int> &in)-> decltype(auto) {return in;} // C++14
Now there are no copies, no temporaries, no dangling references, and no undefined behavior.
Who's at fault?
As to whether this is expected behavior, the answer is actually yes. The conditions for constructibility of a std::function are [func.wrap.func.con]:
f is Callable (20.9.12.2) for argument types ArgTypes... and return type R.
where, [func.wrap.func]:
A callable object f of type F is Callable for argument types ArgTypes and return type R if the expression
INVOKE (f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well
formed (20.9.2).
where, [func.require], emphasis mine:
Define INVOKE(f, t1, t2, ..., tN, R) as static_cast<void>(INVOKE (f, t1, t2, ..., tN)) if R is cv void, otherwise INVOKE(f, t1, t2, ..., tN) implicitly converted to R.
So, if we had:
T func();
std::function<T const&()> wrapped(func);
That actually meets all the standard requirements: INVOKE(func) is well-formed and while it returns T, T is implicitly convertible to T const&. So this isn't a gcc or clang bug. This is likely a standard defect, as I don't see why you would ever want to allow such a construction. This will never be valid, so the wording should likely require that if R is a reference type then F must return a reference type as well.
I did a bit of my own searching regarding the std::function constructor. It seems this part is an oversight in the interaction of std::function and the standard's Callable concept. std::function<R(Args...)>::function<F>(F) requires F to be Callable as R(Args...), which in itself seems reasonable. Callable for R(Args...) requires F's return type (when given arguments of types Args... to be implicitly convertible to R, which also in itself seems reasonable. Now when R is const R_ &, this will an allow implicit conversion of R_ to const R_ & because const references are allowed to bind to rvalues. This is not necessarily unsafe. E.g. consider a function f() that returns an int, but is considered callable as const int &().
const int &result = f();
if ( f == 5 )
{
// ...
}
There is no issue here because of C++'s rules for extending the lifetime of a temporary. However, the following has undefined behavior:
std::function<const int &()> fWrapped{f};
if ( fWrapped() == 5 )
{
// ...
}
This is because lifetime extension does not apply here. The temporary is created inside of std::function's operator() and is destroyed before the comparison.
Therefore, std::function's constructor probably should not rely on Callable alone, but enforce the additional restriction that implicit conversion of an rvalue to a const lvalue in order to bind to a reference is forbidden. Alternatively, Callable could be changed to never allow this conversion, at the expense of disallowing some safe usage (if only because of lifetime extension).
To make things more complicated yet, fWrapped() from the above example is perfectly safe to call, as long as you don't access the target of the dangling reference.
If you use:
return std::ref(in);
In your lambda it will work.
This will make your lambda's return type a std::reference_wrapper<std::vector<int>> which is implicitly convertible to std::vector<int>&.
C++11 (and C++14) introduces additional language constructs and improvements that target generic programming. These include features such as;
R-value references
Reference collapsing
Perfect forwarding
Move semantics, variadic templates and more
I was browsing an earlier draft of the C++14 specification (now with updated text) and the code in an example in ยง20.5.1, Compile-time integer sequences, that I found interesting and peculiar.
template<class F, class Tuple, std::size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template<class F, class Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices = make_index_sequence<std::tuple_size<Tuple>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices());
}
Online here [intseq.general]/2.
Question
Why was the function f in apply_impl being forwarded, i.e. why std::forward<F>(f)(std::get...?
Why not just apply the function as f(std::get...?
In Brief...
The TL;DR, you want to preserve the value category (r-value/l-value nature) of the functor because this can affect the overload resolution, in particular the ref-qualified members.
Function definition reduction
To focus on the issue of the function being forwarded, I've reduced the sample (and made it compile with a C++11 compiler) to;
template<class F, class... Args>
auto apply_impl(F&& func, Args&&... args) -> decltype(std::forward<F>(func)(std::forward<Args>(args)...)) {
return std::forward<F>(func)(std::forward<Args>(args)...);
}
And we create a second form, where we replace the std::forward(func) with just func;
template<class F, class... Args>
auto apply_impl_2(F&& func, Args&&... args) -> decltype(func(std::forward<Args>(args)...)) {
return func(std::forward<Args>(args)...);
}
Sample evaluation
Evaluating some empirical evidence of how this behaves (with conforming compilers) is a neat starting point for evaluating why the code example was written as such. Hence, in addition we will define a general functor;
struct Functor1 {
int operator()(int id) const
{
std::cout << "Functor1 ... " << id << std::endl;
return id;
}
};
Initial sample
Run some sample code;
int main()
{
Functor1 func1;
apply_impl_2(func1, 1);
apply_impl_2(Functor1(), 2);
apply_impl(func1, 3);
apply_impl(Functor1(), 4);
}
And the output is as expected, independent of whether an r-value is used Functor1() or an l-value func when making the call to apply_impl and apply_impl_2 the overloaded call operator is called. It is called for both r-values and l-values. Under C++03, this was all you got, you could not overload member methods based on the "r-value-ness" or "l-value-ness" of the object.
Functor1 ... 1
Functor1 ... 2
Functor1 ... 3
Functor1 ... 4
Ref-qualified samples
We now need to overload that call operator to stretch this a little further...
struct Functor2 {
int operator()(int id) const &
{
std::cout << "Functor2 &... " << id << std::endl;
return id;
}
int operator()(int id) &&
{
std::cout << "Functor2 &&... " << id << std::endl;
return id;
}
};
We run another sample set;
int main()
{
Functor2 func2;
apply_impl_2(func2, 5);
apply_impl_2(Functor2(), 6);
apply_impl(func2, 7);
apply_impl(Functor2(), 8);
}
And the output is;
Functor2 &... 5
Functor2 &... 6
Functor2 &... 7
Functor2 &&... 8
Discussion
In the case of apply_impl_2 (id 5 and 6), the output is not as may have been initially been expected. In both cases, the l-value qualified operator() is called (the r-value is not called at all). It may have been expected that since Functor2(), an r-value, is used to call apply_impl_2 the r-value qualified operator() would have been called. The func, as a named parameter to apply_impl_2, is an r-value reference, but since it is named, it is itself an l-value. Hence the l-value qualified operator()(int) const& is called in both the case of the l-value func2 being the argument and the r-value Functor2() being used as the argument.
In the case of apply_impl (id 7 and 8) the std::forward<F>(func) maintains or preserves the r-value/l-value nature of the argument provided for func. Hence the l-value qualified operator()(int) const& is called with the l-value func2 used as the argument and the r-value qualified operator()(int)&& when the r-value Functor2() is used as the argument. This behaviour is what would have been expected.
Conclusions
The use of std::forward, via perfect forwarding, ensures that we preserve the r-value/l-value nature of the original argument for func. It preserves their value category.
It is required, std::forward can and should be used for more than just forwarding arguments to functions, but also when the use of an argument is required where the r-value/l-value nature must be preserved. Note; there are situations where the r-value/l-value cannot or should not be preserved, in these situations std::forward should not be used (see the converse below).
There are many examples popping up that inadvertently lose the r-value/l-value nature of the arguments via a seemingly innocent use of an r-value reference.
It has always been hard to write well defined and sound generic code. With the introduction of r-value references, and reference collapsing in particular, it has become possible to write better generic code, more concisely, but we need to be ever more aware of what the original nature of the arguments provided are and make sure that they are maintained when we use them in the generic code we write.
Full sample code can be found here
Corollary and converse
A corollary of the question would be; given reference collapsing in a templated function, how is the r-value/l-value nature of the argument maintained? The answer - use std::forward<T>(t).
Converse; does std::forward solve all your "universal reference" problems? No it doesn't, there are cases where it should not be used, such as forwarding the value more than once.
Brief background to perfect forwarding
Perfect forwarding may be unfamiliar to some, so what is perfect forwarding?
In brief, perfect forwarding is there to ensure that the argument provided to a function is forwarded (passed) to another function with the same value category (basically r-value vs. l-value) as originally provided. It is typically used with template functions where reference collapsing may have taken place.
Scott Meyers gives the following pseudo code in his Going Native 2013 presentation to explain the workings of std::forward (at approximately the 20 minute mark);
template <typename T>
T&& forward(T&& param) { // T&& here is formulated to disallow type deduction
if (is_lvalue_reference<T>::value) {
return param; // return type T&& collapses to T& in this case
}
else {
return move(param);
}
}
Perfect forwarding depends on a handful of fundamental language constructs new to C++11 that form the bases for much of what we now see in generic programming:
Reference collapsing
Rvalue references
Move semantics
The use of std::forward is currently intended in the formulaic std::forward<T>, understanding how std::forward works helps understand why this is such, and also aids in identifying non-idiomatic or incorrect use of rvalues, reference collapsing and ilk.
Thomas Becker provides a nice, but dense write up on the perfect forwarding problem and solution.
What are ref-qualifiers?
The ref-qualifiers (lvalue ref-qualifier & and rvalue ref-qualifier &&) are similar to the cv-qualifiers in that they (the ref-qualified members) are used during overload resolution to determine which method to call. They behave as you would expect them to; the & applies to lvalues and && to rvalues. Note: Unlike cv-qualification, *this remains an l-value expression.
Here is a practical example.
struct concat {
std::vector<int> state;
std::vector<int> const& operator()(int x)&{
state.push_back(x);
return state;
}
std::vector<int> operator()(int x)&&{
state.push_back(x);
return std::move(state);
}
std::vector<int> const& operator()()&{ return state; }
std::vector<int> operator()()&&{ return std::move(state); }
};
This function object takes an x, and concatenates it to an internal std::vector. It then returns that std::vector.
If evaluated in an rvalue context it moves to a temporary, otherwise it returns a const& to the internal vector.
Now we call apply:
auto result = apply( concat{}, std::make_tuple(2) );
because we carefully forwarded our function object, only 1 std::vector buffer is allocated. It is simply moved out to result.
Without the careful forwarding, we end up creating an internal std::vector, and we copy it to result, then discard the internal std::vector.
Because the operator()&& knows that the function object should be treated as a rvalue about to be destroyed, it can rip the guts out of the function object while doing its operation. The operator()& cannot do this.
Careful use of perfect forwarding of function objects enables this optimization.
Note, however, that there is very little use of this technique "in the wild" at this point. Rvalue qualified overloading is obscure, and doing so to operator() moreso.
I could easily see future versions of C++ automatically using the rvalue state of a lambda to implicitly move its captured-by-value data in certain contexts, however.