如何将通用的packaged_tasks存储在容器中? [英] How can I store generic packaged_tasks in a container?

查看:156
本文介绍了如何将通用的packaged_tasks存储在容器中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图以 std :: async 的样式执行一个任务,并将其存储在容器中。我必须跳过圈来实现它,但我认为一定有一个更好的方法。

  std :: vector< ; std :: function< void()>> mTasks; 

template< class F,class ... Args>
std :: future< typename std :: result_of< typename std :: decay< F> :: type(typename std :: decay< Args> :: type ...)> :: type&
push(F& f,Args& ... args)
{
auto func = std :: make_shared< std :: packaged_task< typename std :: result_of< typename std :: decay< F> :: type(typename std :: decay< Args> :: type ...)> :: type()>>(std :: bind(std :: forward& (f),std :: forward< Args(args)...));
auto future = func-> get_future();

//因为某些原因,如果我在这个捕获中摆脱了`=,`,我得到了clang中的编译错误:
mTasks.push_back([=,func = std :: move(func)] {(* func)();});

return future;
}

所以我使用 bind - > packaged_task - > shared_ptr - > lambda - > function 。我该如何做到更好/更好的?如果有一个 std :: function 这可能会采取不可复制但可移动的任务,肯定会更容易。我可以std ::转发args到捕获的lambda,或者我必须使用 bind



步骤1:写一个SFINAE友好的 std :: result_of

/ code>和帮助通过tuple调用的函数:

 命名空间详细信息{
template< size_t。 ..Is,class F,class ... Args>
auto invoke_tuple(std :: index_sequence< Is ...>,F&& f,std :: tuple< Args>&&&args)
{
return std: :forward(F)(std :: get(std :: move(args)));
}
// SFINAE friendly result_of:
template< class Invocation,class = void>
struct invoke_result {};
template< class T,class ... Args>
struct invoke_result< T(Args ...),decltype(void(std :: declval< T>()(std :: declval< Args>()...))) {
using type = decltype(std :: declval< T>()(std :: declval< Args>()...)
};
template< class Invocation,class = void>
struct can_invoke:std :: false_type {};
template< class Invocation>
struct can_invoke< Invocation,decltype(void(std :: declval<
typename invoke_result< Inocation> :: type
>()))>:std :: true_type {};
}

template< class F,class ... Args>
auto invoke_tuple(F&& f,std :: tuple< Args>&& args)
{
return details :: invoke_tuple(std :: index_sequence_for& > {},std :: forward(F),std :: move(args));
}

// SFINAE friendly result_of:
template< class Invocation>
struct invoke_result:details :: invoke_result< Invocation> {};
template< class Invocation>
using invoke_result_t = typename invoke_result< Invocation> :: type;
template< class Invocation>
struct can_invoke:details :: can_invoke< Invocation> {};

现在我们有 invoke_result_t< A(B,C)> code>其是SFINAE友好 result_of_t can_invoke

接下来,写一个 move_only_function 只有版本 std :: function

 命名空间详细信息{
template< class Sig>
struct mof_internal;
template< class R,class ... Args>
struct mof_internal {
virtual〜mof_internal(){};
// 4重载,因为我疯了:
virtual R invoke(Args& ... args)const& = 0;
virtual R invoke(Args& ... args)& = 0;
virtual R invoke(Args& ... args)const&& = 0;
virtual R invoke(Args& ... args)&&& = 0;
};

template< class F,class Sig>
struct mof_pimpl;
template< class R,class ... Args,class F>
struct mof_pimpl< F,R(Args ...)>:mof_internal< R(Args ...)> {
F f;
virtual R invoke(Args& ... args)const& override {return f(std :: forward< Args>(args)...); }
virtual R invoke(Args& ... args)& override {return f(std :: forward< Args>(args)...); }
virtual R invoke(Args& ... args)const&& override {return std :: move(f)(std :: forward< Args>(args)...); }
virtual R invoke(Args& ... args)&&& override {return std :: move(f)(std :: forward< Args>(args)...); }
};
}

template< class R,class ... Args>
struct move_only_function< R(Args)> {
move_only_function(move_only_function const&)= delete;
move_only_function(move_only_function&&)= default;
move_only_function(std :: nullptr_t):move_only_function(){}
move_only_function()= default;
显式运算符bool()const {return pImpl; }
bool operator!()const {return!* this;}} }
R operator()(Args ... args)& {return pImpl()。invoke(std :: forward< Args>(args)...); }
R operator()(Args ... args)const& {return pImpl()。invoke(std :: forward< Args>(args)...); }
R operator()(Args ... args)&& {return std :: move(* this).pImpl()。invoke(std :: forward& ); }
R operator()(Args ... args)const&& {return std :: move(* this).pImpl()。invoke(std :: forward& ); }

template< class F,class = std :: enable_if_t< can_invoke< decay_t< F>(Args ...)>
move_only_function(F&& f):
m_pImpl(std :: make_unique< details :: mof_pimpl< std :: decay_t F,R(Args ...)> :: forward(F)))
{}
private:
using internal = details :: mof_internal< R(Args ...)>
std :: unique_ptr< internal> m_pImpl;

// rvalue helpers:
internal& pImpl()& {return * m_pImpl.get(); }
internal const& pImpl()const& {return * m_pImpl.get(); }
internal&&& pImpl()&& {return std :: move(* m_pImpl.get()); }
internal const&&& pImpl()const&& {return std :: move(* m_pImpl.get()); } //大多是无用的
};

未测试,只是喷出代码。 can_invoke 给构造函数基本的SFINAE - 你可以添加返回类型转换正确和void返回类型意味着我们忽略返回如果你喜欢。

现在我们重做你的代码。首先,你的任务是仅移动函数,而不是函数:

  std :: vector< move_only_function< X& mTasks; 

接下来,我们存储 R 类型计算一次,并再次使用:

  template< class F,class ... Args,class R = std :: result_of_t< std :: decay< F> _&&(std :: decay_t< Args>&& ...)> 
std :: future< R>
push(F& f,Args& ... args)
{
auto tuple_args = std :: make_tuple(std :: forward< Args> 。]];

// lambda将只被调用一次:
std :: packaged_task< R()>任务([f = std :: forward< F>(f),args = std :: move(tuple_args)]
return invoke_tuple(std :: move(f),std :: move
});

auto future = func.get_future();

//因为某种原因,如果我在这个捕获中摆脱了`=,`,我得到了clang中的编译错误:
mTasks.emplace_back(std :: move(task)) ;

return future;
}

我们将参数放入一个元组,将该元组传递给一个lambda,在lambda中调用元组在只做一次的方式。因为我们只调用一次函数,所以我们优化了这个函数的lambda。



A packed_task< R()> std :: function< R()> move_only_function< R()> ,所以我们可以把它移动到我们的向量。我们得到的 std :: future 应该工作正常,即使我们在 move 之前得到它。



这应该会减少你的开销。当然,有很多样板。



我没有编译任何上述代码,我只是吐出来,所以编译的可能性很低。



随机,我决定给 move_only_function 4个不同的() overloads(rvalue / lvalue和const / not)。我可以增加不稳定,但这似乎鲁莽。



另外,我的 move_only_function 缺少获取底层存储的东西操作, code> std :: function has。随意键入擦除,如果你喜欢。它把(R(*)(Args ...))0 看作是一个真正的函数指针(I return true 当转换为 bool ,不像null:类型擦除转换 - bool 可能是值得的



我重写了 std :: function ,因为 std 缺少一个 std :: move_only_function ,通常的概念是一个有用的( packed_task )。您的解决方案通过使用 std :: shared_ptr 包装它来调用可调用方法。



你不喜欢上面的样板,考虑写 make_copyable(F&& amp;),它接受一个函数对象 F ,并使用 shared_ptr 技术将其包装以使其可复制,甚至可以添加SFINAE以避免在已经可复制的情况下执行它(并将其调用 ensure_copyable )。



然后你的原始代码会更干净,因为你只需要使 code>可复制,然后存储。

  template< class F> 
auto make_function_copyable(F&& f){
auto sp = std :: make_shared< std :: decay_t F>(std :: forward< F&
return [sp](auto& ... args){return(* sp)(std :: forward< decltype(args)>(args)...) }
}
template< class F,class ... Args,class R = std :: result_of_t std :: future< R>
push(F& f,Args& ... args)
{
auto tuple_args = std :: make_tuple(std :: forward< Args> 。]];

// lambda将只被调用一次:
std :: packaged_task< R()>任务([f = std :: forward< F>(f),args = std :: move(tuple_args)]
return invoke_tuple(std :: move(f),std :: move
});

auto future = func.get_future();

//因为某种原因,如果我在这个捕获中摆脱了`=,`,我得到了clang中的编译错误:
mTasks.emplace_back(make_function_copyable(std :: move )));

return future;
}

这仍然需要 invoke_tuple 上面的样板,主要是因为我不喜欢 bind


I'm trying to take a 'task' in the style of std::async and store it in a container. I'm having to jump through hoops to achieve it, but I think there must be a better way.

std::vector<std::function<void()>> mTasks;

template<class F, class... Args>
std::future<typename std::result_of<typename std::decay<F>::type(typename std::decay<Args>::type...)>::type>
push(F&& f, Args&&... args)
{
    auto func = std::make_shared<std::packaged_task<typename std::result_of<typename std::decay<F>::type(typename std::decay<Args>::type...)>::type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
    auto future = func->get_future();

    // for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
    mTasks.push_back([=, func = std::move(func)]{ (*func)(); });

    return future;
}

So I'm using bind -> packaged_task -> shared_ptr -> lambda -> function. How can I do this better/more optimally? It would certainly be easier if there was a std::function which could take a non-copyable but moveable task. Can I std::forward args into the capture of a lambda, or do I have to use bind?

解决方案

There is no kill like overkill.

Step 1: write a SFINAE friendly std::result_of and a function to help calling via tuple:

namespace details {
  template<size_t...Is, class F, class... Args>
  auto invoke_tuple( std::index_sequence<Is...>, F&& f, std::tuple<Args>&& args)
  {
    return std::forward<F>(f)( std::get<Is>(std::move(args)) );
  }
  // SFINAE friendly result_of:
  template<class Invocation, class=void>
  struct invoke_result {};
  template<class T, class...Args>
  struct invoke_result<T(Args...), decltype( void(std::declval<T>()(std::declval<Args>()...)) ) > {
    using type = decltype( std::declval<T>()(std::declval<Args>()...) );
  };
  template<class Invocation, class=void>
  struct can_invoke:std::false_type{};
  template<class Invocation>
  struct can_invoke<Invocation, decltype(void(std::declval<
    typename invoke_result<Inocation>::type
  >()))>:std::true_type{};
}

template<class F, class... Args>
auto invoke_tuple( F&& f, std::tuple<Args>&& args)
{
  return details::invoke_tuple( std::index_sequence_for<Args...>{}, std::forward<F>(f), std::move(args) );
}

// SFINAE friendly result_of:
template<class Invocation>
struct invoke_result:details::invoke_result<Invocation>{};
template<class Invocation>
using invoke_result_t = typename invoke_result<Invocation>::type;
template<class Invocation>
struct can_invoke:details::can_invoke<Invocation>{};

We now have invoke_result_t<A(B,C)> which is a SFINAE friendly result_of_t<A(B,C)> and can_invoke<A(B,C)> which just does the check.

Next, write a move_only_function, a move-only version of std::function:

namespace details {
  template<class Sig>
  struct mof_internal;
  template<class R, class...Args>
  struct mof_internal {
    virtual ~mof_internal() {};
    // 4 overloads, because I'm insane:
    virtual R invoke( Args&&... args ) const& = 0;
    virtual R invoke( Args&&... args ) & = 0;
    virtual R invoke( Args&&... args ) const&& = 0;
    virtual R invoke( Args&&... args ) && = 0;
  };

  template<class F, class Sig>
  struct mof_pimpl;
  template<class R, class...Args, class F>
  struct mof_pimpl<F, R(Args...)>:mof_internal<R(Args...)> {
    F f;
    virtual R invoke( Args&&... args ) const&  override { return f( std::forward<Args>(args)... ); }
    virtual R invoke( Args&&... args )      &  override { return f( std::forward<Args>(args)... ); }
    virtual R invoke( Args&&... args ) const&& override { return std::move(f)( std::forward<Args>(args)... ); }
    virtual R invoke( Args&&... args )      && override { return std::move(f)( std::forward<Args>(args)... ); }
  };
}

template<class R, class...Args>
struct move_only_function<R(Args)> {
  move_only_function(move_only_function const&)=delete;
  move_only_function(move_only_function &&)=default;
  move_only_function(std::nullptr_t):move_only_function() {}
  move_only_function() = default;
  explicit operator bool() const { return pImpl; }
  bool operator!() const { return !*this; }
  R operator()(Args...args)     & { return                  pImpl().invoke(std::forward<Args>(args)...); }
  R operator()(Args...args)const& { return                  pImpl().invoke(std::forward<Args>(args)...); }
  R operator()(Args...args)     &&{ return std::move(*this).pImpl().invoke(std::forward<Args>(args)...); }
  R operator()(Args...args)const&&{ return std::move(*this).pImpl().invoke(std::forward<Args>(args)...); }

  template<class F,class=std::enable_if_t<can_invoke<decay_t<F>(Args...)>>
  move_only_function(F&& f):
    m_pImpl( std::make_unique<details::mof_pimpl<std::decay_t<F>, R(Args...)>>( std::forward<F>(f) ) )
  {}
private:
  using internal = details::mof_internal<R(Args...)>;
  std::unique_ptr<internal> m_pImpl;

  // rvalue helpers:
  internal      &  pImpl()      &  { return *m_pImpl.get(); }
  internal const&  pImpl() const&  { return *m_pImpl.get(); }
  internal      && pImpl()      && { return std::move(*m_pImpl.get()); }
  internal const&& pImpl() const&& { return std::move(*m_pImpl.get()); } // mostly useless
 };

not tested, just spewed the code. The can_invoke gives the constructor basic SFINAE -- you can add "return type converts properly" and "void return type means we ignore the return" if you like.

Now we rework your code. First, your task are move-only functions, not functions:

std::vector<move_only_function<X>> mTasks;

Next, we store the R type calculation once, and use it again:

template<class F, class... Args, class R=std::result_of_t<std::decay<F>_&&(std::decay_t<Args>&&...)>>
std::future<R>
push(F&& f, Args&&... args)
{
  auto tuple_args=std::make_tuple(std::forward<Args>(args)...)];

  // lambda will only be called once:
  std::packaged_task<R()> task([f=std::forward<F>(f),args=std::move(tuple_args)]
    return invoke_tuple( std::move(f), std::move(args) );
  });

   auto future = func.get_future();

  // for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
  mTasks.emplace_back( std::move(task) );

  return future;
}

we stuff the arguments into a tuple, pass that tuple into a lambda, and invoke the tuple in a "only do this once" kind of way within the lambda. As we will only invoke the function once, we optimize the lambda for that case.

A packaged_task<R()> is compatible with a move_only_function<R()> unlike a std::function<R()>, so we can just move it into our vector. The std::future we get from it should work fine even though we got it before the move.

This should reduce your overhead by a bit. Of course, there is lots of boilerplate.

I have not compiled any of the above code, I just spewed it out, so the odds it all compiles are low. But the errors should mostly be tpyos.

Randomly, I decided to give move_only_function 4 different () overloads (rvalue/lvalue and const/not). I could have added volatile, but that seems reckless. Which increase boilerplate, admittedly.

Also my move_only_function lacks the "get at the underlying stored stuff" operation that std::function has. Feel free to type erase that if you like. And it treats (R(*)(Args...))0 as if it was a real function pointer (I return true when cast to bool, not like null: type erasure of convert-to-bool might be worthwhile for a more industrial quality implementation.

I rewrote std::function because std lacks a std::move_only_function, and the concept in general is a useful one (as evidenced by packaged_task). Your solution makes your callable movable by wrapping it with a std::shared_ptr.

If you don't like the above boilerplate, consider writing make_copyable(F&&), which takes an function object F and wraps it up using your shared_ptr technique to make it copyable. You can even add SFINAE to avoid doing it if it is already copyable (and call it ensure_copyable).

Then your original code would be cleaner, as you'd just make the packaged_task copyable, then store that.

template<class F>
auto make_function_copyable( F&& f ) {
  auto sp = std::make_shared<std::decay_t<F>>(std::forward<F>(f));
  return [sp](auto&&...args){return (*sp)(std::forward<decltype(args)>(args)...); }
}
template<class F, class... Args, class R=std::result_of_t<std::decay<F>_&&(std::decay_t<Args>&&...)>>
std::future<R>
push(F&& f, Args&&... args)
{
  auto tuple_args=std::make_tuple(std::forward<Args>(args)...)];

  // lambda will only be called once:
  std::packaged_task<R()> task([f=std::forward<F>(f),args=std::move(tuple_args)]
    return invoke_tuple( std::move(f), std::move(args) );
  });

  auto future = func.get_future();

  // for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
  mTasks.emplace_back( make_function_copyable( std::move(task) ) );

  return future;
}

this still requires the invoke_tuple boilerplate above, mainly because I dislike bind.

这篇关于如何将通用的packaged_tasks存储在容器中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆