Anubis
Anubis

Reputation: 7435

Lambda closure lvalues can be passed as rvalue reference parameters

I found that lvalue lambda closures can always be passed as rvalue function parameters.

See the following simple demonstration.

#include <iostream>
#include <functional>

using namespace std;

void foo(std::function<void()>&& t)
{
}

int main()
{
    // Case 1: passing a `lvalue` closure
    auto fn1 = []{};
    foo(fn1);                          // works

    // Case 2: passing a `lvalue` function object
    std::function<void()> fn2 = []{};
    foo(fn2);                          // compile error

    return 0;
}

Case 2 is the standard behavior (I just used a std::function for demonstration purposes, but any other type would behave the same).

How and why does case 1 work ? What is the state of fn1 closure after the function returned ?

Upvotes: 19

Views: 1246

Answers (3)

eerorika
eerorika

Reputation: 238311

What is the state of fn1 closure after the function returned ?

fn1 is stateless, since it captures nothing.

How and why does case 1 work ?

It works because the argument is of different type than the type that is rvalue referenced. Because of having a different type, implicit conversions are considered. Since the lambda is Callable for the arguments of this std::function, it is implicitly convertible to it through the template converting constructor of std::function. The result of the conversion is a prvalue, and thus can be bound with the rvalue reference.

Upvotes: 5

Vittorio Romeo
Vittorio Romeo

Reputation: 93264

How and why does case 1 work ?

Invoking foo requires an instance of std::function<void()> that binds to an rvalue reference. std::function<void()> can be constructed from any callable object that is compatible with the void() signature.

Firstly, a temporary std::function<void()> object is constructed from []{}. The constructor used is #5 here, which copies the closure into the std::function instance:

template< class F >
function( F f );

Initializes the target with std::move(f). If f is a null pointer to function or null pointer to member, *this will be empty after the call.

Then, the temporary function instance is bound to the rvalue reference.


What is the state of fn1 closure after the function returned ?

Same as before, because it was copied into a std::function instance. The original closure is unaffected.

Upvotes: 8

A lambda is not a std::function. The reference doesn't bind directly.

Case 1 works because lambdas are convertible to std::functions. This means that a temporary std::function is materialized by copying fn1. Said temporary is able to be bound to an rvalue reference, and so the argument matches the parameter.

And the copying is also why fn1 is entirely unaffected by anything that happens in foo.

Upvotes: 10

Related Questions