75

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
6
  • 6
    You can't. std::function requires the function object be CopyConstructible. Commented Aug 21, 2014 at 8:23
  • 4
    This is very similar to stackoverflow.com/questions/25330716/… 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. Commented Aug 21, 2014 at 8: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!) Commented Aug 21, 2014 at 8:31
  • Related question: Move-only version of std::function Commented Oct 14, 2018 at 18:54
  • The range library now included in the standard fixes this problem (only here for backward compatiblity) with a semiregular wrapper: eel.is/c++draft/range.semi.wrap Commented Dec 13, 2018 at 12:03

4 Answers 4

46

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

Sign up to request clarification or add additional context in comments.

9 Comments

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: 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.
@seertaak you capture std::unique_ptr, not an int. Try to replace it with std::shared_ptr which is copy-constructable.
@seertaak See also an explanation of why-on-Earth std::function requires functor to be copy-constructable and a workaround via adapter here.
@RobertAllanHenniganLeahy it's probably worth mentioning that in C++23, std::move_only_function now solves this exact problem.
|
40

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.

2 Comments

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?
@user8 [x=std::optional<T>(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.
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

3 Comments

Similar trick is mentioned in this talk.
Thanks @Taylor this was proving a very tricky puzzle to solve
@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.
0

As of C++23, the solution is using std::move_only_function

For example,

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

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

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

int main()
{
  std::move_only_function<void()> bar = std::move(foo);
  bar();
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.