Re: [boost] Non-allocating constexpr functional continuations-based future-promise
On 10/23/2014 11:00 PM, Niall Douglas wrote:
Given Hartmut's feedback from my previous attempt at a non-allocating future-promise implementation, I now have a second, much more generic demonstration prototype which is based on heterogenous sequences of constexpr functional calls. I think this design may have huge potential.
This is a very nice evolution of your previous promise-future framework.
template<class T> struct then_continuation { std::vector<std::function<void(T)>> _conts; template<class U> auto then(U &&c) { typedef decltype(c(std::declval<T>())) result_type; promise<result_type> p; future<result_type> ret(p.get_future()); // Unfortunately libstdc++'s std::function currently always tries to copy lambda types //_conts.emplace_back([p=std::move(p), c=std::forward<U>(c)](T v){p.set_value(c(v));}); _conts.push_back([p = std::make_shared<promise<result_type>>(std::move(p)), c=std::forward<U>(c)](T v){p->set_value(c(v));}); return ret; } BOOST_CONSTEXPR T operator()(T v) const { for(auto &i : _conts) i(v); return v; } };
All this continuation does is provide a .then(callable) which stores those callables into a std::vector and its operator() executes the previously stored callables.
Previously you defined callables as composed functions, but here you execute them sequentially. That is, then(f1).then(f2) gets executed as f1(v),f2(v) instead of f2(f1(v)). Or am I missing something?
On 25 Oct 2014 at 13:00, Bjorn Reese wrote:
Given Hartmut's feedback from my previous attempt at a non-allocating future-promise implementation, I now have a second, much more generic demonstration prototype which is based on heterogenous sequences of constexpr functional calls. I think this design may have huge potential.
This is a very nice evolution of your previous promise-future framework.
Thanks.
template<class T> struct then_continuation { std::vector<std::function<void(T)>> _conts; template<class U> auto then(U &&c) { typedef decltype(c(std::declval<T>())) result_type; promise<result_type> p; future<result_type> ret(p.get_future()); // Unfortunately libstdc++'s std::function currently always tries to copy lambda types //_conts.emplace_back([p=std::move(p), c=std::forward<U>(c)](T v){p.set_value(c(v));}); _conts.push_back([p = std::make_shared<promise<result_type>>(std::move(p)), c=std::forward<U>(c)](T v){p->set_value(c(v));}); return ret; } BOOST_CONSTEXPR T operator()(T v) const { for(auto &i : _conts) i(v); return v; } };
All this continuation does is provide a .then(callable) which stores those callables into a std::vector and its operator() executes the previously stored callables.
Previously you defined callables as composed functions, but here you execute them sequentially. That is, then(f1).then(f2) gets executed as f1(v),f2(v) instead of f2(f1(v)). Or am I missing something?
No, you have a keen eye, that's all. This relates to a very interesting part of the Concurrency TS in how their continuations are destructive for future but non-destructive for shared_future. This code shows what I mean: future<int> f1(p.get_future()); future<int> f2(f1.then([](future<int>){...}); f1.get(); // throws an exception! Because futures cannot be copied, and because the callable called by then must take the incoming future by value, this implies the destruction of state of the future f1 by its then(). Which is all well and good. But if we were writing generic code, and we had typedefed our own future alias type, and we decided it suddenly needs to be a shared_future instead: shared_future<int> f1(p.get_future()); shared_future<int> f2(f1.then([](shared_future<int>){...}); f1.get(); // works as expected I have a problem with these semantics. My problem is this: there is a BIG difference between a future whose lifetime is managed by multiple holders and a future with atomic consumption (i.e. self destructing) value semantics. The former is really "go wrap it with shared_ptr", and I think if you want multiple owners to manage lifetime of something then instead of reinventing shared_ptr, just use shared_ptr instead. The latter is rather like an atomic-compare-and-exchange operation, the very first person who reaches the pie gets all of it, no exceptions. Where my issue is is that I see no reason why the two semantics needs to be forced together like that - in particular, an atomic consuming shared future is obviously useful (though in fairness can be easily emulated with a shared_ptr to a future), while a value transporting but move constructing only future has the same usefulness as unique_ptr or optional future semantics (though in fairness could be easily emulated with a unique_ptr or optional to a shared_future). Besides, thanks to my constexpr reduction I can be much more flexible with which types of continuation .then() can invoke. I can provide value consuming ones, rvalue and lvalue ref consuming ones, and whole future consuming ones. Each is stored in its own std::vector container with the appropriate std::function type - which, if never used, gets constexpr out of existence, so you only pay for what you use. Therefore, I would hope that in my reimplementation of future promise, this would still fail as per Concurrency TS: future<int> f1(p.get_future()); future<int> f2(f1.then([](future<int>){...}); f1.get(); // throws an exception! But this works: future<int> f1(p.get_future()); future<int> f2(f1.then([](future<int> &){...}); f1.get(); // works So does this: future<int> f1(p.get_future()); future<int> f2(f1.then([](future<int> &&){...}); f1.get(); // depends if the continuation move constructed And this: future<int> f1(p.get_future()); future<int> f2(f1.then([](int){...}); f1.get(); // works And of course this: future<int> f1(p.get_future()); future<int> f2(f1.then([](expected<int> &){...}); f1.get(); // works This is because the std::future replica uses a std::experimental::expected<int, exception_ptr> as the transport type, so if you wish access to that you can get it. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
participants (2)
-
Bjorn Reese
-
Niall Douglas