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

see*_*aak 52 c++ lambda std c++14

我正在尝试创建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);
Run Code Online (Sandbox Code Playgroud)

我会解释为什么我要写这样的东西.我写了一个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;
};
Run Code Online (Sandbox Code Playgroud)

请注意,调用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.0
Thread model: posix
Run Code Online (Sandbox Code Playgroud)

Rob*_*ahy 31

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,移动捕获不可复制的东西.

  • @seertaak另请参阅*为什么在地球上的解释*`std :: function`要求仿函数可以复制构造,并通过适配器[此处]解决方法(http://stackoverflow.com/questions/11768273/why-斜面-C11-移动-A-不可复制-函子到一个-stdfunction). (3认同)
  • @seertaak:假设你有一个lambda,它通过移动捕获一个`std :: vector`.`std :: vector`被移动到lambda中,但`std :: vector`是`CopyConstructible`,因此lambda是可复制的,即使它是通过move捕获的.因此,这个lambda,在其他条件相同的情况下,可用于构造`std :: function`.即你如何捕获某些东西并不是真正相关的,那些相关的属性是这些对象在被捕获后给予lambda的属性. (2认同)
  • @seertaak你捕获`std :: unique_ptr`,而不是`int`.尝试用可复制构造的`std :: shared_ptr`替换它. (2认同)

Yak*_*ont 27

由于std::function<?>有TYPE-删除存储可调用对象的拷贝构造函数,你不能从一个只移动式构造它.您的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) };
}
Run Code Online (Sandbox Code Playgroud)

既然上面已经完成,我们可以解决您的问题.

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
Run Code Online (Sandbox Code Playgroud)

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;
};
Run Code Online (Sandbox Code Playgroud)

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

实例.

注意你必须()在右值上下文中评估(即在a之后std::move),因为沉默的破坏性()似乎很粗鲁.

这个实现不使用SBO,因为如果它这样做,它将要求存储的类型是可移动的,并且对我来说(对我来说)启动会更多.


Tay*_*lor 6

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

   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;
   };
Run Code Online (Sandbox Code Playgroud)

现场示例在这里

  • [本演讲](https://youtu.be/3jCOwajNch0?t=3033) 中提到了类似的技巧。 (3认同)
  • 谢谢@Taylor,这证明是一个非常棘手的难题需要解决 (2认同)