未来,future 将有一个 .then 运算符,可以让您链接任务。
没有它我们可以写它。
// complete named operator library in about a dozen lines of code:
namespace named_operator {
template<class D>struct make_operator{ constexpr make_operator() {}; };
template<class T, class O> struct half_apply { T&& lhs; };
template<class Lhs, class Op>
half_apply<Lhs, Op> operator*( Lhs&& lhs, make_operator<Op> ) {
return {std::forward<Lhs>(lhs)};
}
template<class Lhs, class Op, class Rhs>
decltype(auto) operator*( half_apply<Lhs, Op>&& lhs, Rhs&& rhs )
{
return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
}
}
// create a named operator then:
namespace then_ns {
static const struct then_t:named_operator::make_operator<then_t> {} then{};
namespace details {
template<size_t...Is, class Tup, class F>
auto invoke_helper( std::index_sequence<Is...>, Tup&& tup, F&& f )
->decltype(std::forward<F>(f)( std::get<Is>(std::forward<Tup>(tup))... ))
{
return std::forward<F>(f)( std::get<Is>(std::forward<Tup>(tup))... );
}
}
// first overload of A *then* B handles tuple and tuple-like return values:
template<class Tup, class F>
auto named_invoke( Tup&& tup, then_t, F&& f )
-> decltype( details::invoke_helper( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) ) )
{
return details::invoke_helper( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
// second overload of A *then* B
// only applies if above does not:
template<class T, class F>
auto named_invoke( T&& t, then_t, F&& f, ... )
-> std::result_of_t< F(T) >
{
return std::forward<F>(f)(std::forward<T>(t));
}
// *then* with a future; unpack the future
// into a call to f within an async:
template<class X, class F>
auto named_invoke( std::future<X> x, then_t, F&& f )
-> std::future< std::decay_t<decltype( std::move(x).get() *then* std::declval<F>() )> >
{
return std::async( std::launch::async,
[x = std::move(x), f = std::forward<F>(f)]() mutable {
return std::move(x).get() *then* std::move(f);
}
);
}
// void future, don't try to pass void to f:
template<class F>
auto named_invoke( std::future<void> x, then_t, F&& f )
-> std::future< std::decay_t<decltype( std::declval<F>()() )> >
{
return std::async( std::launch::async,
[x = std::move(x), f = std::forward<F>(f)]() mutable {
std::move(x).get();
return std::move(f)();
}
);
}
}
using then_ns::then;
看,这并不难。
a *then* f,如果a 是一个元组(或对或数组),将使用a 的内容调用f。
如果a 不是类似元组的,或者f 不接受a 的内容,它会使用a 调用f。
如果a 是一个未来,它会创建一个新的异步未来,使用*then* 消耗a.get()。
Live example.
假设你想在文件保存时增加一个原子 int:
std::vector<std::future<void>> saveFileTasks;
for (int n = 0; n < p.size(); ++n)
{
saveFileTasks.push_back(
std::async(std::launch::async, [filename]{
saveFile(filename);
})
);
}
std::atomic<int> count;
for (auto &e : saveFileTasks) {
e = std::move(e) *then* [&count]{
++count;
});
}
当然,这一切都可以在没有命名运算符*then* 样式语法的情况下完成,但这样做有什么乐趣呢?
如果第一个异步返回一个元组,第二个可以将它作为一个元组或解压缩的“平面”参数。