70

I'm trying to create an std::function from a move-capturing lambda expression. Note that I can create a move-capturing lambda expression without problems; it's only when I try to wrap it in an std::function that I get an error.

For example:

auto pi = std::make_unique<int>(0);

// no problems here!
auto foo = [q = std::move(pi)] {
    *q = 5;
    std::cout << *q << std::endl;
};

// All of the attempts below yield:
// "Call to implicitly-deleted copy constructor of '<lambda...."

std::function<void()> bar = foo;
std::function<void()> bar{foo};
std::function<void()> bar{std::move(foo)};
std::function<void()> bar = std::move(foo);
std::function<void()> bar{std::forward<std::function<void()>>(foo)};
std::function<void()> bar = std::forward<std::function<void()>>(foo);

I'll explain why I want to write something like this. I've written a UI library which, similar to jQuery or JavaFX, allows the user to handle mouse/keyboard events by passing std::functions to methods with names like on_mouse_down(), on_mouse_drag(), push_undo_action(), etc.

Obviously, the std::function I want to pass in should ideally use a move-capturing lambda expression, otherwise I need to resort to the ugly "release/acquire-in-lambda" idiom I was using when C++11 was the standard:

std::function<void()> baz = [q = pi.release()] {
    std::unique_ptr<int> p{q};
    *p = 5;
    std::cout << *q << std::endl;
};

Note that calling baz twice would be an error in the above code. However, in my code, this closure is guaranteed to be called exactly once.

BTW, in my real code, I'm not passing an std::unique_ptr<int>, but something more interesting.

Finally, I'm using Xcode6-Beta4 which uses the following version of clang:

Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.3.0
Thread model: posix
Walter
  • 44,150
  • 20
  • 113
  • 196
seertaak
  • 1,087
  • 1
  • 9
  • 17
  • 6
    You can't. `std::function` requires the function object be `CopyConstructible`. – T.C. Aug 21 '14 at 08:23
  • 4
    This is very similar to http://stackoverflow.com/questions/25330716/move-only-version-of-stdfunction Also, why not just using templates for the functons instead of `std::function` type erasure? Using `std::function` as an universal function type is not a good idea. – Manu343726 Aug 21 '14 at 08:27
  • Well the idea was to avoid compilation times getting too long, combined with the fact that the performance penalty for using `std::function` is acceptable in the context of UI callbacks. (Perhaps premature optimization!) – seertaak Aug 21 '14 at 08:31
  • Related question: [Move-only version of std::function](https://stackoverflow.com/q/25330716/427158) – maxschlepzig Oct 14 '18 at 18:54
  • The range library now included in the standard fixes this problem (only here for backward compatiblity) with a semiregular wrapper: http://eel.is/c++draft/range.semi.wrap – Oliv Dec 13 '18 at 12:03
  • The "release/acquire-in-lambda" idiom that you demonstrate doesn't work. If the lambda is not called then you have a memory leak. – mikep Jun 04 '19 at 19:19

3 Answers3

44

template<class F> function(F f);

template <class F, class A> function(allocator_arg_t, const A& a, F f);

Requires: F shall be CopyConstructible. f shall be Callable for argument types ArgTypes and return type R. The copy constructor and destructor of A shall not throw exceptions.

§20.9.11.2.1 [func.wrap.func.con]

Note that operator = is defined in terms of this constructor and swap, so the same restrictions apply:

template<class F> function& operator=(F&& f);

Effects: function(std::forward<F>(f)).swap(*this);

§20.9.11.2.1 [func.wrap.func.con]

So to answer your question: Yes, it is possible to construct a std::function from a move-capturing lambda (since this only specifies how the lambda captures), but it is not possible to construct a std::function from a move-only type (e.g. a move-capturing lambda which move-captures something that is not copy constructible).

  • I don't understand the difference between the first and second clauses in your last sentence ("it is possible to construct..." and "but it is not possible..."). Could you perhaps provide an example? – seertaak Aug 21 '14 at 08:28
  • 3
    @seertaak: Let's say you have a lambda which captures a `std::vector` by move. The `std::vector` is moved into the lambda, but `std::vector` is `CopyConstructible`, and therefore the lambda is copyable, even though it captured by move. Therefore this lambda, all else being equal, may be used to construct a `std::function`. I.e. how you capture something isn't really relevant, what's relevant is the properties those objects give the lambda once captured. – Robert Allan Hennigan Leahy Aug 21 '14 at 08:31
  • Wait: ints are copy-constructible, just like std::vector. Why shouldn't it work on ints then? – seertaak Aug 21 '14 at 08:33
  • 2
    @seertaak you capture `std::unique_ptr`, not an `int`. Try to replace it with `std::shared_ptr` which is copy-constructable. – Ivan Aksamentov - Drop Aug 21 '14 at 08:34
  • True. Now it all makes sense. – seertaak Aug 21 '14 at 08:35
  • 3
    @seertaak See also an explanation of *why-on-Earth* `std::function` requires functor to be copy-constructable and a workaround via adapter [here](http://stackoverflow.com/questions/11768273/why-cant-c11-move-a-noncopyable-functor-to-a-stdfunction). – Ivan Aksamentov - Drop Aug 21 '14 at 08:45
  • Since the std::function requires copyable, how can I save the move only lambda into a membership? – alpha Nov 06 '15 at 02:38
  • Does the above mean that what the question refer to as the _ugly "release/acquire-in-lambda" idiom_ still is the best (or a sensible) solution to the problem ? – Zitrax Jan 20 '17 at 12:34
  • @RobertAllanHenniganLeahy it's probably worth mentioning that in C++23, `std::move_only_function` **now solves this exact problem**. – seertaak May 29 '22 at 09:57
39

As std::function<?> has to type-erase the copy constructor of the stored invocable object, you cannot construct it from a move-only type. Your lambda, because it captures a move-only type by value, is a move-only type. So... you cannot solve your problem. std::function cannot store your lambda.

At least not directly.

This is C++, we simply route around the problem.

template<class F>
struct shared_function {
  std::shared_ptr<F> f;
  shared_function() = delete; // = default works, but I don't use it
  shared_function(F&& f_):f(std::make_shared<F>(std::move(f_))){}
  shared_function(shared_function const&)=default;
  shared_function(shared_function&&)=default;
  shared_function& operator=(shared_function const&)=default;
  shared_function& operator=(shared_function&&)=default;
  template<class...As>
  auto operator()(As&&...as) const {
    return (*f)(std::forward<As>(as)...);
  }
};
template<class F>
shared_function< std::decay_t<F> > make_shared_function( F&& f ) {
  return { std::forward<F>(f) };
}

now that the above is done, we can solve your problem.

auto pi = std::make_unique<int>(0);

auto foo = [q = std::move(pi)] {
  *q = 5;
  std::cout << *q << std::endl;
};

std::function< void() > test = make_shared_function( std::move(foo) );
test(); // prints 5

The semantics of a shared_function is slightly different than other functions, as a copy of it shares the same state (including when turned into a std::function) as the original.

We can also write a move-only fire-once function:

template<class Sig>
struct fire_once;

template<class T>
struct emplace_as {};

template<class R, class...Args>
struct fire_once<R(Args...)> {
  // can be default ctored and moved:
  fire_once() = default;
  fire_once(fire_once&&)=default;
  fire_once& operator=(fire_once&&)=default;

  // implicitly create from a type that can be compatibly invoked
  // and isn't a fire_once itself
  template<class F,
    std::enable_if_t<!std::is_same<std::decay_t<F>, fire_once>{}, int> =0,
    std::enable_if_t<
      std::is_convertible<std::result_of_t<std::decay_t<F>&(Args...)>, R>{}
      || std::is_same<R, void>{},
      int
    > =0
  >
  fire_once( F&& f ):
    fire_once( emplace_as<std::decay_t<F>>{}, std::forward<F>(f) )
  {}
  // emplacement construct using the emplace_as tag type:
  template<class F, class...FArgs>
  fire_once( emplace_as<F>, FArgs&&...fargs ) {
    rebind<F>(std::forward<FArgs>(fargs)...);
  }
  // invoke in the case where R is not void:
  template<class R2=R,
    std::enable_if_t<!std::is_same<R2, void>{}, int> = 0
  >
  R2 operator()(Args...args)&&{
    try {
      R2 ret = invoke( ptr.get(), std::forward<Args>(args)... );
      clear();
      return ret;
    } catch(...) {
      clear();
      throw;
    }
  }
  // invoke in the case where R is void:
  template<class R2=R,
    std::enable_if_t<std::is_same<R2, void>{}, int> = 0
  >
  R2 operator()(Args...args)&&{
    try {
      invoke( ptr.get(), std::forward<Args>(args)... );
      clear();
    } catch(...) {
      clear();
      throw;
    }
  }

  // empty the fire_once:
  void clear() {
    invoke = nullptr;
    ptr.reset();
  }

  // test if it is non-empty:
  explicit operator bool()const{return (bool)ptr;}

  // change what the fire_once contains:
  template<class F, class...FArgs>
  void rebind( FArgs&&... fargs ) {
    clear();
    auto pf = std::make_unique<F>(std::forward<FArgs>(fargs)...);
    invoke = +[](void* pf, Args...args)->R {
      return (*(F*)pf)(std::forward<Args>(args)...);
    };
    ptr = {
      pf.release(),
      [](void* pf){
        delete (F*)(pf);
      }
    };
  }
private:
  // storage.  A unique pointer with deleter
  // and an invoker function pointer:
  std::unique_ptr<void, void(*)(void*)> ptr{nullptr, +[](void*){}};
  void(*invoke)(void*, Args...) = nullptr;
};

which supports even non-movable types via the emplace_as<T> tag.

live example.

Note you have to evaluate () in an rvalue context (ie, after a std::move), as a silent destructive () seemed rude.

This implementation does not use SBO, for if it did it would demand that the type stored be movable, and it would be more work (for me) to boot.

isanae
  • 3,253
  • 1
  • 22
  • 47
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Why would it be a problem to call a fire_once function twice? As long as it holds the callable, it should be fine to call the object, right? – user877329 Apr 29 '20 at 19:33
  • @user8 `[x=std::optional(x)]()mutable{ auto r = std::move(*x); x=std::nullopt; return r; }` is a toy example; a function that when called becomes invalid to call again. Proxy move constructors more generally, and any case where copying costs more than consuming. – Yakk - Adam Nevraumont May 01 '20 at 01:48
7

Here's a simpler solution:

   auto pi = std::make_unique<int>(0);

   auto ppi = std::make_shared<std::unique_ptr<int>>(std::move(pi));

   std::function<void()> bar = [ppi] {
        **ppi = 5;
        std::cout << **ppi << std::endl;
   };

Live example here

Taylor
  • 5,871
  • 2
  • 30
  • 64
  • 3
    Similar trick is mentioned in [this talk](https://youtu.be/3jCOwajNch0?t=3033). – Evg Nov 12 '19 at 20:58
  • 2
    Thanks @Taylor this was proving a very tricky puzzle to solve – Adamski Jul 02 '20 at 15:44
  • 1
    @Evg Thanks for sharing that talk. From that talk, now I know `folly::Function` could be used in this scenario. That saved a lot of time. – Christophe Sep 20 '20 at 17:33