gpt4 book ai didi

c++ - 如何将通用 packaged_tasks 存储在容器中?

转载 作者:塔克拉玛干 更新时间:2023-11-02 23:30:53 25 4
gpt4 key购买 nike

我正在尝试采用 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>(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;
}

所以我正在使用 bind -> packaged_task -> shared_ptr -> lambda -> 功能。我怎样才能做得更好/更优化?如果有一个 std::function 可以执行不可复制但可移动的任务,那肯定会更容易。我可以将 std::forward args 放入 lambda 的捕获中,还是必须使用 bind

最佳答案

杀无赦。

第 1 步:写一个 SFINAE 友好 std::result_of和一个帮助通过元组调用的函数:

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>{};

我们现在有invoke_result_t<A(B,C)>这是一个 SFINAE 友好 result_of_t<A(B,C)>can_invoke<A(B,C)>这只是检查。

接下来,写一个move_only_function , 一个只能移动的版本 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
};

未测试,只是吐出代码。 can_invoke为构造函数提供基本的 SFINAE——如果您愿意,您可以添加“返回类型正确转换”和“无效返回类型意味着我们忽略返回”。

现在我们重新编写您的代码。首先,您的任务是仅移动函数,而不是函数:

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>(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;
}

我们将参数填充到一个元组中,将该元组传递到一个 lambda 中,然后在 lambda 中以一种“只执行一次”的方式调用该元组。由于我们只会调用该函数一次,因此我们针对这种情况优化了 lambda。

A packaged_task<R()>move_only_function<R()> 兼容不像 std::function<R()> , 所以我们可以把它移到我们的 vector 中。 std::future我们从中得到它应该工作正常,即使我们在 move 之前得到它.

这应该会稍微减少您的开销。当然,有很多样板文件。

我没有编译上面的任何代码,我只是把它吐出来,所以它全部编译的几率很低。但错误应该主要是 tpyos。

随机,我决定给move_only_function 4 种不同 ()重载(右值/左值和常量/非)。我本可以添加 volatile,但这似乎很鲁莽。无可否认,这增加了样板文件。

还有我的move_only_function缺少 std::function 的“获取底层存储的东西”操作已。如果愿意,请随意键入删除。它对待(R(*)(Args...))0就好像它是一个真正的函数指针(我在转换为 true 时返回 bool,不像 null:转换为 bool 的类型删除对于更具工业质量的实现可能是值得的。

我重写了 std::function因为std缺少 std::move_only_function ,并且这个概念通常是有用的(如 packaged_task 所证明的)。您的解决方案通过用 std::shared_ptr 包装它来使您的可调用对象可移动.

如果你不喜欢上面的样板,考虑写make_copyable(F&&) ,它接受一个函数对象 F并用你的 shared_ptr 把它包起来使其可复制的技术。如果它已经可以复制,您甚至可以添加 SFINAE 以避免这样做(并将其称为 ensure_copyable )。

那么您的原始代码会更清晰,因为您只需制作 packaged_task可复制,然后存储。

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;
}

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

关于c++ - 如何将通用 packaged_tasks 存储在容器中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28179817/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com