18

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 ?

Anubis
  • 6,995
  • 14
  • 56
  • 87
  • 6
    I guess that is because `fn1` is implicitly converted to a `std::function` in `foo(fn1)`. That temporary function then is a rvalue. – eike Nov 12 '19 at 12:25
  • @RichardCritten I really wasn't sure, so I didn't post an answer. I think now there is no need for another one. – eike Nov 12 '19 at 12:29
  • 1
    @eike np I often feel the same way, and yup many answers. – Richard Critten Nov 12 '19 at 12:29
  • I didn't read details yet, but doesn't this question contradict this claim [Why doesn't C++11 implicitly convert lambdas to std::function objects?](https://stackoverflow.com/questions/21586756/why-doesnt-c11-implicitly-convert-lambdas-to-stdfunction-objects) – Anubis Nov 12 '19 at 12:32
  • 2
    @Sumudu The person who asked the question misled you because they didn't know what they were trying to ask. What they meant to ask was: "Why cannot template arguments of `std::function` be deduced from a lambda". Your program does not attempt to deduce the template arguments of `std::function`, so there is no problem with the implicit conversion. – eerorika Nov 12 '19 at 12:36
  • 1
    The title of the question you linked is a little misleading. `std::function` has a non-explicit constructor that accepts lambda closures, so there is implicit conversion. But in the circumstances of the linked question, the template instantiation of `std::function` cannot be inferred from the lambda type. (For example `std::function` can be constructed from `[](){return 5;}` even though it has a non-void return type. – eike Nov 12 '19 at 12:37

3 Answers3

9

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
8

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.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
5

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.

eerorika
  • 232,697
  • 12
  • 197
  • 326