0

Why does this code snippet work?

#include <functional>
#include <iostream>
#include <memory>

int main()
{
    std::unique_ptr<int> ptr(new int(666));

    std::bind([](std::unique_ptr<int> &ptr){std::cout << *ptr << std::endl;}, std::move(ptr))();
}

You see the parameter of the lambda should be a lvalue-reference, whereas std::bind pass a rvalue(i.e. std::move(ptr)) to it.

Also, why does this code snippet not compile?

std::function<void()> = std::bind([](std::unique_ptr<int> &ptr){std::cout << *ptr << std::endl;}, std::move(ptr));

Just because std::function need copy all the objects, whereas std::move(ptr) is not copyable?

UPDATED:

The aforementioned code which could not compile is seen at https://localcoder.org/how-to-capture-a-unique-ptr-into-a-lambda-expression (i.e. see the 'solution 3' in the post). So the said solution is totally wrong. Am I right?

John
  • 2,963
  • 11
  • 33
  • “*Just because std::function need copy all the objects, whereas std::move(ptr) is not copyable?*” Yep, in such case you might want to use the C++23 [`std::move_only_function`](https://godbolt.org/z/rcT5d5ae7). – 康桓瑋 Apr 08 '22 at 02:21
  • `C++23` is impossible for me. What I can use is `C++17`. – John Apr 08 '22 at 02:22
  • @John related: [Move-only version of std::function](https://stackoverflow.com/questions/25330716/), [How to create an std::function from a move-capturing lambda expression?](https://stackoverflow.com/questions/25421346/) – Remy Lebeau Apr 08 '22 at 02:33
  • @John it's not a lot of code to make a move-only function. I'd suggest putting it in a header and leaving a `// C++23: std::move_only_function` comment. – Ben Apr 08 '22 at 02:34
  • @RemyLebeau The code about the move-only version of `std::function` is too hard for me to understand. :( – John Apr 08 '22 at 02:41

2 Answers2

2

std::bind() does not call the lambda, it returns a proxy that will call the lambda when the proxy is invoked later. So, just because you pass in an rvalue reference into std::bind() does not mean the proxy will pass an rvalue reference into the lambda.

And in fact, if you think about it, the proxy can't do so anyway. It has to move your rvalue-referenced unique_ptr object into something in the proxy to save it for later use after std::bind() has exited. And that something is itself not an rvalue, and so that something can then be passed into the lambda by lvalue reference.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you, sincerely. I have a related question, please see the updated question. – John Apr 08 '22 at 01:59
  • @John I can't answer your other question. It ha something to do with stateless (non-capturing) lambdas not being copy-constructible until recently. Your version of gcc likely hasn't implemented support for that. – Remy Lebeau Apr 08 '22 at 02:30
1

std::bind pass a rvalue ... to it

Yes, an rvalue was passed to std::bind. It was duly stashed away for safe-keeping, until such time that the bound callable object gets called.

At that time the saved object gets passed to the lambda by referenced.

In other words, the following is a gross simplification, this is what this particular std::bind roughly ends up making something like this:

struct bound_function {

   template<typename Arg> bound_function(Arg &&arg)
      : saved_parameter{std::forward<Arg>(arg)}
   {
   }

   void operator()()
   {
       invoke_bound_function(saved_parameter);
   }

private:

   std::unique_ptr<int> saved_parameter;

   void invoke_bound_function(std::unique_ptr<int> &ptr){std::cout << *ptr << std::endl;}
};

The act of binding a parameter to a callable object, and then invoking a callable object with a bound parameter, are two discrete, independent events. In the first one, the parameter is moved into the bound object, as an rvalue reference.

Invoking the bound function is a separate step, and the bound parameter gets passed to the bound function, by reference.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • How to understand " It was duly stashed away for safe-keeping, until such time that the bound callable object gets called" in the right way? – John Apr 08 '22 at 02:13
  • At first glance, there is a member function whose name is the same with the class, which makes me confused. :( – John Apr 08 '22 at 02:19
  • It can be understood literally. The parameter that was passed in by rvalue doesn't just get suspended, like a zombie, in the ether, until it's passed along to the callable object. It has to be saved somewhere. Then, when it's time to call the lambda it gets passed. And a "function whose name is the same with the class" is called a "constructor". One of the first points of discussion in every C++ textbook's first chapter on classes is an explanation of what a constructor a destructor is, how they work, how to declare them, and how they're used. – Sam Varshavchik Apr 08 '22 at 02:49