В будущем будущее будет иметь оператор .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
является кортеж (или пары или массив), будет вызывать f
с содержанием a
.
Если a
не кортеж типа, или f
не принимает содержимое a
таким образом, это вызывает f
с a
.
Если a
является будущего, вместо этого он создает новое асинхронное будущее, которое потребляет a.get()
с помощью *then*
.
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 возвращает кортеж, второй может взять его как кортеж или как распакованные «плоские» аргументы.
Не будет 'std :: future :: wait' выполнять эту работу? Какой смысл запускать 'save_file' и' long_operation' в отдельных потоках, если вам нужно ждать, пока файл будет сохранен? – doc
Мне нужен пул потоков. У меня может быть много задач. Например, 1 необходимо сохранить 10 файлов. Когда сохранено 5 файлов, я могу выполнять длительные операции с 5 потоками, а другие 5 потоков будут сохранять другие файлы. – user565447