3

I need to move a unique_ptr to a std::function closure. I'm using generalized lambda captures in C++14.

auto ptr = make_unique<Foo>();

// Works.
auto lambda = [p = move(ptr)] { };

// This does not compile.
std::function<void()> func = [p = move(ptr)] { };

It's trying to copy, rather than move, the lambda capture into the std::function. The relevant error is:

 copy constructor of '' is implicitly deleted because field '' has a deleted copy
      constructor
  std::function<void()> func = [p = move(ptr)] { };

The example here would make this seem to work.

Note that the answer here just repeats the example on isocpp.org.

I can move to a shared_ptr as follows:

shared_ptr<Foo> s = move(ptr);

but that creates another issue, because I need to call a function that expects a unique_ptr from within my lambda, and I can't convert a shared_ptr back to a unique_ptr.

Is it possible to capture a unique_ptr in a std::function?

Taylor
  • 5,871
  • 2
  • 30
  • 64
  • 1
    *"It seems that std::function needs a move constructor for functors"* And your lambda has a move constructor. If that was enough, you could `std::move` your lambda into the `std::function`. The problem is that `std::function` requires the functor to be copyable, because `std::function` itself can be copied. – HolyBlackCat Jul 08 '19 at 17:56
  • @HolyBlackCat right ok. Will remove that. – Taylor Jul 08 '19 at 17:58
  • 1
    A `std::function` which calls a function which requires a `unique_ptr` can mostly be called only once :/ . So inner function should probably not expect a `unique_ptr`. – Jarod42 Jul 08 '19 at 18:04
  • 1
    very related: https://stackoverflow.com/questions/25421346/how-to-create-an-stdfunction-from-a-move-capturing-lambda-expression – NathanOliver Jul 08 '19 at 18:05
  • 1
    Even if you never copy `std::function`, it requires that the callable it holds can be copyable. `std::function` actually does move the lambda into its storage, but it doesn't compile, because the lambda isn't copyable. – Justin Jul 08 '19 at 18:09

1 Answers1

4

std::function objects can all be copied

std::function is a type-erasure object that supports copying the object stored.

When you store a std::unique_ptr in a lambda, that lambda does not support being copied.

So std::function quite rightly complains. It is a type that can be copied, and when passed in something is works out how to copy it. "I cannot copy it" isn't a valid answer; all std::functions can be copied.

Industrial strength solution:

There are two common approaches to solve this problem. First, you store the std::function's state in a std::shared_ptr of some kind. Second, you write or find a non-copying std::function and use that instead.

More "modern" std::function replacement libraries support a number of useful things:

  1. function views, that do not own what they wrap.
  2. move-only function objects, that don't support copying.
  3. multiple-overload function objects, that support more than 1 signature at once.
  4. fixed-sized buffers, that fail to compile if there isn't enough automatic storage instead of heap allocating.
  5. trivially copyable function objects

I've personally had a need for every one of the above for various special purposes.

Then you'd use moveonly_function<void()> when you don't need to copy the callable, and your code compiles.

However, this is probably too heavy for your needs right now.

A quick solution is:

template<class F>
auto make_shared_function( F&& f ) {
  return
   [pf = std::make_shared<std::decay_t<F>>(std::forward<F>(f))]
   (auto&&...args)->decltype(auto)
   {
     return (*pf)( decltype(args)(args)... );
   };
}

now whenever you run into this problem:

// This does not compile.
std::function<void()> func = make_shared_function([p = move(ptr)] { });

and the state of the callable object is now stored in a shared ptr.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524