讓通過關注點分離攻擊這一點。
首先,一個可調用的輸出pipeing到另一個問題:
template<class Index>
auto get_nth(Index) {
return [](auto&&...args)noexcept(true)->decltype(auto) {
return std::get<Index::value>(
std::forward_as_tuple(decltype(args)(args)...)
);
};
}
template<class F>
struct pipeable_t;
template<class F>
pipeable_t<std::decay_t<F>> make_pipe(F&&);
struct is_pipeable {
template<
class Lhs, class Rhs,
std::enable_if_t<
std::is_base_of<is_pipeable, std::decay_t<Lhs>>{}
|| std::is_base_of<is_pipeable, std::decay_t<Rhs>>{}
, int> = 0
>
friend
auto operator|(Lhs&& lhs, Rhs&& rhs) {
auto pipe_result=
[lhs = std::forward<Lhs>(lhs), rhs=std::forward<Rhs>(rhs)]
(auto&&...args)mutable ->decltype(auto)
{
auto value_is_void = std::is_same< decltype(lhs(decltype(args)(args)...)), void >{};
auto pipe_chooser = get_nth(value_is_void);
auto pipe_execution = pipe_chooser(
[&](auto&& lhs, auto&& rhs)->decltype(auto){ return rhs(lhs(decltype(args)(args)...)); },
[&](auto&& lhs, auto&& rhs)->decltype(auto){ lhs(decltype(args)(args)...); return rhs(); }
);
return pipe_execution(lhs, rhs);
};
return make_pipe(std::move(pipe_result));
}
};
template<class F>
struct pipeable_t:is_pipeable {
F f;
pipeable_t(F fin):f(std::forward<F>(fin)) {}
template<class...Args>
auto operator()(Args&&...args) const {
return f(std::forward<Args>(args)...);
}
template<class...Args>
auto operator()(Args&&...args) {
return f(std::forward<Args>(args)...);
}
};
template<class F>
pipeable_t<std::decay_t<F>> make_pipe(F&& f) { return {std::forward<F>(f)}; }
template<class T>
auto future_to_factory(std::future<T>&& f) {
return [f=std::move(f)]() mutable { return f.get(); };
}
template<class T>
auto make_pipe(std::future<T>&& f) {
return make_pipe(future_to_factory(std::move(f)));
}
我們可以make_pipe(some lambda)
,它現在可以輸送到更多的東西。
管道本身是可執行和可管道的。我們可以使任何東西都可執行一個管道,我們可以將一個std::future
放入管道中。
一旦我們有了這個,我們在這方面重寫你Task
:
struct dispatch_via_async {
template<class F>
auto operator()(F&& f)const {
return std::async(std::launch::async, std::forward<F>(f));
}
};
template<class T, class Dispatch=dispatch_via_async>
class Task;
template<class F, class D=dispatch_via_async>
using task_type = Task<std::decay_t<std::result_of_t<F&&()>>, D>;
template<class F, class D=dispatch_via_async>
task_type<F, D> CreateTask(F&& func, D&& d={});
template<class T, class Dispatch>
class Task :
public is_pipeable, // why not?
private Dispatch
{
std::future<T> mTask;
Dispatch& my_dispatch() { return *this; }
public:
T operator()() { return mTask.get(); }
Task(){};
template<class F>
auto then(F&& func)&&
{
return CreateTask(
make_pipe(std::move(mTask)) | std::forward<F>(func),
std::move(my_dispatch())
);
}
template<class F, class D=Dispatch>
explicit Task(F&& func, D&& dispatch={}):
Dispatch(std::forward<D>(dispatch))
{
mTask = my_dispatch()(
[func = std::forward<F>(func)]() mutable
-> decltype(func())
{
return func();
}
);
}
};
template<class F, class D>
task_type<F,D> CreateTask(F&& func, D&& d)
{
return task_type<F,D>(std::forward<F>(func), std::forward<D>(d));
}
在這裏,我們仔細允許調度員(我們如何讓未來的)如果需要傳遞到Task
。 .then
'延續將使用調度員創建鏈式期貨。
Live example。
作爲獎勵,您現在擁有一個簡單的流式操作鏈操作庫。
請注意,我用async
爲基礎的實現替換了基於thread
的實現,因爲系統在程序完成之前未能正確等待線程終止,從而導致未定義的行爲。您仍然可以將Dispatcher
替換爲使用線程的那個。
在c++2a,Regular Void提案試圖擺脫這個問題。我不知道它的工作有多好。
以上不MSVC的當前版本構建,因爲MSVC不能是一個正確的c++14編譯器,它的異步使用包裝任務,其包裝任務保存其任務一個std功能,這導致async錯誤地要求被可複製調用的任務爲std::function
type-erases複製。
這打破了上面的代碼,因爲我們在我們的異步任務中存儲了一個std::future
,它不能被複制。
我們可以以適度的成本解決這個問題。最小最偏僻的方式做到這一點是改變future_to_factory
到:
template<class T>
auto future_to_factory(std::future<T>&& f) {
return [f=std::make_shared<std::future<T>>(std::move(f))]() mutable { return f->get(); };
}
和代碼compiles on visual studio。
通過添加噸和噸的標籤調度代碼。在C++ 17中,由於「如果constexpr」,它變得更容易一些。但這是一個[已知問題](http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0146r1.html),沒有很好的解決方案。 – nwp
@nwp噸和噸? – Yakk
@Yakk是的。在實踐中,你將有多個功能和三重冗餘。你很難在你看到的所有樣板中看到代碼。嘗試不支持'void'返回類型以便能夠在之後維護代碼是有意義的。 – nwp