为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C++
慕田峪9158850 2019-10-11 11:11:13
我正在尝试std::function从捕获移动的lambda表达式创建一个。请注意,我可以毫无问题地创建一个捕获移动的lambda表达式;只有当我尝试将其包装为时std::function,我才会收到错误消息。例如: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);我将解释为什么我要写这样的东西。我写了一个UI库,类似于jQuery的或JavaFX的,允许用户通过传递给处理鼠标/键盘事件std::functions到方法有相似的名字on_mouse_down(),on_mouse_drag(),push_undo_action(),等。显然,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;};请注意,baz两次调用将是上述代码中的错误。但是,在我的代码中,此闭包被保证只被调用一次。顺便说一句,在我的真实代码中,我没有传递std::unique_ptr<int>,而是更有趣的东西。最后,我正在使用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
查看完整描述

3 回答

?
拉丁的传说

TA贡献1789条经验 获得超8个赞

template<class F> function(F f);


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


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


§20.9.11.2.1[func.wrap.func.con]


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


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


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


§20.9.11.2.1[func.wrap.func.con]


因此,要回答你的问题:是的,这是可以构建一个std::function从一招捕获拉姆达(因为这仅指定拉姆达如何捕捉),但它是不是可以构建一个std::function从唯才是举型(如MOVE-捕获lambda,该lambda移动捕获无法复制构造的内容)。


查看完整回答
反对 回复 2019-10-11
?
慕妹3242003

TA贡献1824条经验 获得超6个赞

由于std::function<?>必须对存储的可调用对象的副本构造函数进行类型擦除,因此不能从仅移动类型构造它。您的lambda因为它按值捕获仅移动类型,所以它是仅移动类型。所以...您无法解决您的问题。  std::function无法存储您的lambda。


至少不是直接。


这是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) };

}

至此,我们可以解决您的问题。


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

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


我们还可以编写仅移动一次触发函数:


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;

};

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


请注意,您必须()在右值上下文中进行评估(即,在后面std::move),因为沉默的破坏性行为()似乎是不礼貌的。


此实现不使用SBO,因为如果这样做,它将要求存储的类型是可移动的,并且(对我来说)引导将需要更多工作。


查看完整回答
反对 回复 2019-10-11
?
心有法竹

TA贡献1866条经验 获得超5个赞

这是一个更简单的解决方案:


   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;

   };


查看完整回答
反对 回复 2019-10-11
  • 3 回答
  • 0 关注
  • 783 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信