如何从移动捕获lambda表达式创建一个std :: function?

时间:2023-02-03 18:01:38

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.

我正在尝试从移动捕获lambda表达式创建一个std :: function。请注意,我可以创建一个移动捕获lambda表达式而不会出现问题;只有当我尝试将它包装在std :: function中时才会出现错误。

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.

我会解释为什么我要写这样的东西。我编写了一个UI库,类似于jQuery或JavaFX,允许用户通过将std :: functions传递给名称为on_mouse_down(),on_mouse_drag(),push_undo_action()等的方法来处理鼠标/键盘事件。

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理想情况下应该使用移动捕获lambda表达式,否则我需要求助于我在C ++ 11使用时使用的丑陋的“release / acquire-in-lambda”习惯用法标准:

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.

请注意,在上面的代码中调用baz两次将是一个错误。但是,在我的代码中,这个闭包保证只被调用一次。

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

顺便说一下,在我的真实代码中,我没有传递std :: unique_ptr ,而是更有趣的东西。

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

最后,我正在使用Xcode6-Beta4,它使用以下版本的clang:

Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)Target: x86_64-apple-darwin13.3.0Thread model: posix

2 个解决方案

#1


24  

template<class F> function(F f);

template function(F f);

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

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

要求:F应为CopyConstructible。对于参数类型ArgTypes和返回类型R,f应为Callable.A的复制构造函数和析构函数不应抛出异常。

§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:

请注意,operator =是根据此构造函数和swap定义的,因此适用相同的限制:

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

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

所以回答你的问题:是的,有可能从一个移动捕获lambda构造一个std :: function(因为这只指定了lambda捕获的方式),但是不可能从一个移动构造一个std :: function - 只有类型(例如移动捕获lambda,它移动捕获不可复制的东西)。

#2


21  

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.

由于std :: function 必须键入 - 擦除存储的可调用对象的复制构造函数,因此无法从仅移动类型构造它。您的lambda,因为它按值捕获仅移动类型,是一种仅移动类型。所以...你无法解决你的问题。 std :: function无法存储你的lambda。

At least not directly.

至少不是直接的。

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

这是C ++,我们只是解决问题。

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.

shared_function的语义与其他函数略有不同,因为它的副本与原始函数共享相同的状态(包括转换为std :: function时)。

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.

它通过emplace_as 标签支持甚至不可移动的类型。

live example.

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

注意你必须在rvalue上下文中评估()(即在std :: move之后),因为一个无声的破坏性()似乎很粗鲁。

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.

这个实现不使用SBO,因为如果它确实存在,它会要求存储的类型是可移动的,并且对我来说启动会更有用。

#1


24  

template<class F> function(F f);

template function(F f);

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

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

要求:F应为CopyConstructible。对于参数类型ArgTypes和返回类型R,f应为Callable.A的复制构造函数和析构函数不应抛出异常。

§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:

请注意,operator =是根据此构造函数和swap定义的,因此适用相同的限制:

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

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

所以回答你的问题:是的,有可能从一个移动捕获lambda构造一个std :: function(因为这只指定了lambda捕获的方式),但是不可能从一个移动构造一个std :: function - 只有类型(例如移动捕获lambda,它移动捕获不可复制的东西)。

#2


21  

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.

由于std :: function 必须键入 - 擦除存储的可调用对象的复制构造函数,因此无法从仅移动类型构造它。您的lambda,因为它按值捕获仅移动类型,是一种仅移动类型。所以...你无法解决你的问题。 std :: function无法存储你的lambda。

At least not directly.

至少不是直接的。

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

这是C ++,我们只是解决问题。

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.

shared_function的语义与其他函数略有不同,因为它的副本与原始函数共享相同的状态(包括转换为std :: function时)。

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.

它通过emplace_as 标签支持甚至不可移动的类型。

live example.

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

注意你必须在rvalue上下文中评估()(即在std :: move之后),因为一个无声的破坏性()似乎很粗鲁。

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.

这个实现不使用SBO,因为如果它确实存在,它会要求存储的类型是可移动的,并且对我来说启动会更有用。