Search code examples
c++c++14

How to capture a unique_ptr in a std::function


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?


Solution

  • 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.