将来的には、作業を連鎖させる.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*
スタイルの構文なしで行うことができますが、それの楽しみは何ですか?
最初のasyncがタプルを返した場合、2番目のタプルはタプルとして、または解凍された "フラット"引数として取り込むことができます。
'std :: future :: wait'は仕事をしませんか?とにかくファイルが保存されるまで待たなければならない場合、 'save_file'と' long_operation'を別々のスレッドで実行する点は何ですか? – doc
私はある種のスレッドプールが必要です。私は多くの仕事をすることができます。たとえば、1つは10個のファイルを保存する必要があります。 5つのファイルが保存されると、5つのスレッドで長い操作を実行でき、他の5つのスレッドは他のファイルを保存します。 – user565447