如何从动作捕获lambda表达式创建std::函数?

时间:2021-01-13 18:01:46

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::函数。注意,我可以创建一个动作捕获lambda表达式,没有问题;只有当我尝试将它封装到std::函数中时,我才会得到一个错误。

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:::函数传递给名称为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::函数应该理想地使用一个动作捕获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.0
Thread model: posix

2 个解决方案

#1


21  

template<class F> function(F f);

模板 <类f> 函数F(F);

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

模板 函数(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为可复制的。对于参数类型ArgTypes和返回类型r, f应该是可调用的。

§20.9.11.2.1 [func.wrap.func.con]

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

注意,操作符=是由这个构造函数和swap定义的,所以同样的限制适用:

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

模板 function& operator=(f&f);

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

效果:函数(std::转发< F >(F)).swap(*);

§20.9.11.2.1 [func.wrap.func.con]

§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:::函数(因为这只指定了lambda捕获的方式),但是不可能从一个只动作的类型(例如,一个动作捕获lambda捕获一些不可复制的东西)构造一个std:::函数。

#2


16  

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::函数< ?>必须对存储的invocable对象的复制构造函数进行类型清除,您不能只从一个移动的类型构造它。你的lambda,因为它按值捕获一个只移动的类型,是一个只移动的类型。所以…你不能解决你的问题。函数不能存储你的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,因为如果使用SBO,它将要求存储的类型是可移动的,而且(对我来说)启动起来需要更多的工作。

#1


21  

template<class F> function(F f);

模板 <类f> 函数F(F);

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

模板 函数(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为可复制的。对于参数类型ArgTypes和返回类型r, f应该是可调用的。

§20.9.11.2.1 [func.wrap.func.con]

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

注意,操作符=是由这个构造函数和swap定义的,所以同样的限制适用:

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

模板 function& operator=(f&f);

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

效果:函数(std::转发< F >(F)).swap(*);

§20.9.11.2.1 [func.wrap.func.con]

§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:::函数(因为这只指定了lambda捕获的方式),但是不可能从一个只动作的类型(例如,一个动作捕获lambda捕获一些不可复制的东西)构造一个std:::函数。

#2


16  

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::函数< ?>必须对存储的invocable对象的复制构造函数进行类型清除,您不能只从一个移动的类型构造它。你的lambda,因为它按值捕获一个只移动的类型,是一个只移动的类型。所以…你不能解决你的问题。函数不能存储你的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,因为如果使用SBO,它将要求存储的类型是可移动的,而且(对我来说)启动起来需要更多的工作。