diff --git a/headers/promise.hpp/jobber.hpp b/headers/promise.hpp/jobber.hpp index 615c00a..58b0434 100644 --- a/headers/promise.hpp/jobber.hpp +++ b/headers/promise.hpp/jobber.hpp @@ -26,7 +26,7 @@ namespace jobber_hpp timeout }; - class jobber_cancelled_exception : public std::runtime_error { + class jobber_cancelled_exception final : public std::runtime_error { public: jobber_cancelled_exception() : std::runtime_error("jobber has stopped working") {} @@ -102,7 +102,7 @@ namespace jobber_hpp mutable std::condition_variable cond_var_; }; - class jobber::task : private noncopyable { + class jobber::task : private detail::noncopyable { public: virtual ~task() noexcept = default; virtual void run() noexcept = 0; @@ -110,7 +110,7 @@ namespace jobber_hpp }; template < typename R, typename F, typename... Args > - class jobber::concrete_task : public task { + class jobber::concrete_task final : public task { F f_; std::tuple args_; promise promise_; @@ -123,7 +123,7 @@ namespace jobber_hpp }; template < typename F, typename... Args > - class jobber::concrete_task : public task { + class jobber::concrete_task final : public task { F f_; std::tuple args_; promise promise_; diff --git a/headers/promise.hpp/promise.hpp b/headers/promise.hpp/promise.hpp index 4570a69..778729a 100644 --- a/headers/promise.hpp/promise.hpp +++ b/headers/promise.hpp/promise.hpp @@ -78,107 +78,6 @@ namespace promise_hpp template < typename R, typename T > inline constexpr bool is_promise_r_v = is_promise_r::value; - // - // detail - // - - namespace detail - { - class noncopyable { - public: - noncopyable(const noncopyable&) = delete; - noncopyable& operator=(const noncopyable&) = delete; - protected: - noncopyable() = default; - ~noncopyable() = default; - }; - - template < typename T > - class storage final : private noncopyable { - public: - storage() = default; - - ~storage() - noexcept(std::is_nothrow_destructible_v) { - if ( initialized_ ) { - ptr_()->~T(); - } - } - - void set(T&& value) - noexcept(std::is_nothrow_move_constructible_v) { - assert(!initialized_); - ::new(ptr_()) T(std::move(value)); - initialized_ = true; - } - - void set(const T& value) - noexcept(std::is_nothrow_copy_constructible_v) { - assert(!initialized_); - ::new(ptr_()) T(value); - initialized_ = true; - } - - T get() - noexcept(std::is_nothrow_move_constructible_v) { - assert(initialized_); - return std::move(*ptr_()); - } - - T& value() noexcept { - assert(initialized_); - return *ptr_(); - } - - const T& value() const noexcept { - assert(initialized_); - return *ptr_(); - } - private: - T* ptr_() noexcept { - return reinterpret_cast(&data_); - } - - const T* ptr_() const noexcept { - return reinterpret_cast(&data_); - } - private: - std::aligned_storage_t data_; - bool initialized_ = false; - }; - - template < typename T > - class storage final : private noncopyable { - public: - storage() = default; - ~storage() = default; - - void set(T& value) noexcept { - assert(!initialized_); - value_ = &value; - initialized_ = true; - } - - T& get() noexcept { - assert(initialized_); - return *value_; - } - - T& value() noexcept { - assert(initialized_); - return *value_; - } - - const T& value() const noexcept { - assert(initialized_); - return *value_; - } - private: - T* value_{nullptr}; - bool initialized_ = false; - }; - } - // // promise_wait_status // @@ -189,6 +88,111 @@ namespace promise_hpp }; } +// ----------------------------------------------------------------------------- +// +// detail +// +// ----------------------------------------------------------------------------- + +namespace promise_hpp::detail +{ + template < typename T > + void destroy_in_place(T& ref) noexcept { + ref.~T(); + } + + template < typename T, typename... Args > + void construct_in_place(T& ref, Args&&... args) + noexcept(std::is_nothrow_constructible_v) { + ::new (std::addressof(ref)) T(std::forward(args)...); + } + + class noncopyable { + public: + noncopyable(const noncopyable&) = delete; + noncopyable& operator=(const noncopyable&) = delete; + protected: + noncopyable() = default; + ~noncopyable() = default; + }; + + template < typename T > + class storage final : private noncopyable { + public: + storage() = default; + + ~storage() noexcept { + if ( initialized_ ) { + destroy_in_place(*ptr_()); + } + } + + storage& operator=(T&& value) + noexcept(std::is_nothrow_move_constructible_v) { + assert(!initialized_); + construct_in_place(*ptr_(), std::move(value)); + initialized_ = true; + return *this; + } + + storage& operator=(const T& value) + noexcept(std::is_nothrow_copy_constructible_v) { + assert(!initialized_); + construct_in_place(*ptr_(), value); + initialized_ = true; + return *this; + } + + T& operator*() noexcept { + assert(initialized_); + return *ptr_(); + } + + const T& operator*() const noexcept { + assert(initialized_); + return *ptr_(); + } + private: + T* ptr_() noexcept { + return reinterpret_cast(&data_); + } + + const T* ptr_() const noexcept { + return reinterpret_cast(&data_); + } + private: + std::aligned_storage_t data_; + bool initialized_ = false; + }; + + template < typename T > + class storage final : private noncopyable { + public: + storage() = default; + ~storage() = default; + + storage& operator=(T& value) noexcept { + assert(!initialized_); + value_ = &value; + initialized_ = true; + return *this; + } + + T& operator*() noexcept { + assert(initialized_); + return *value_; + } + + const T& operator*() const noexcept { + assert(initialized_); + return *value_; + } + private: + T* value_{nullptr}; + bool initialized_ = false; + }; +} + // ----------------------------------------------------------------------------- // // promise @@ -299,10 +303,10 @@ namespace promise_hpp then([ n = next, f = std::forward(on_resolve) - ](auto&&... vs) mutable { + ](auto&& v) mutable { auto np = std::invoke( std::forward(f), - std::forward(vs)...); + std::forward(v)); std::move(np).then([n](auto&&... nvs) mutable { n.resolve(std::forward(nvs)...); }).except([n](std::exception_ptr e) mutable { @@ -327,6 +331,18 @@ namespace promise_hpp }); } + template < typename ResolveF > + auto then_any(ResolveF&& on_resolve) { + return then([ + f = std::forward(on_resolve) + ](auto&& v) mutable { + auto r = std::invoke( + std::forward(f), + std::forward(v)); + return make_any_promise(std::move(r)); + }); + } + template < typename ResolveF > auto then_race(ResolveF&& on_resolve) { return then([ @@ -417,7 +433,7 @@ namespace promise_hpp std::rethrow_exception(exception_); } assert(status_ == status::resolved); - return storage_.value(); + return *storage_; } void wait() const noexcept { @@ -449,7 +465,7 @@ namespace promise_hpp if ( status_ != status::pending ) { return false; } - storage_.set(std::forward(value)); + storage_ = std::forward(value); status_ = status::resolved; invoke_resolve_handlers_(); cond_var_.notify_all(); @@ -553,7 +569,7 @@ namespace promise_hpp if ( status_ == status::resolved ) { std::invoke( std::forward(resolve), - storage_.value()); + *storage_); } else if ( status_ == status::rejected ) { std::invoke( std::forward(reject), @@ -567,7 +583,7 @@ namespace promise_hpp void invoke_resolve_handlers_() noexcept { for ( const auto& h : handlers_ ) { - h.resolve_(storage_.value()); + h.resolve_(*storage_); } handlers_.clear(); } @@ -599,8 +615,8 @@ namespace promise_hpp reject_t reject_; }; - std::vector handlers_; detail::storage storage_; + std::vector handlers_; }; }; } @@ -713,10 +729,9 @@ namespace promise_hpp then([ n = next, f = std::forward(on_resolve) - ](auto&&... vs) mutable { + ]() mutable { auto np = std::invoke( - std::forward(f), - std::forward(vs)...); + std::forward(f)); std::move(np).then([n](auto&&... nvs) mutable { n.resolve(std::forward(nvs)...); }).except([n](std::exception_ptr e) mutable { @@ -740,6 +755,17 @@ namespace promise_hpp }); } + template < typename ResolveF > + auto then_any(ResolveF&& on_resolve) { + return then([ + f = std::forward(on_resolve) + ]() mutable { + auto r = std::invoke( + std::forward(f)); + return make_any_promise(std::move(r)); + }); + } + template < typename ResolveF > auto then_race(ResolveF&& on_resolve) { return then([ @@ -1092,34 +1118,6 @@ namespace promise_hpp // make_all_promise // - namespace impl - { - template < typename ResultType > - class all_promise_context_t final : private detail::noncopyable { - public: - all_promise_context_t(std::size_t count) - : results_(count) {} - - template < typename T > - bool apply_result(std::size_t index, T&& value) { - results_[index].set(std::forward(value)); - return ++counter_ == results_.size(); - } - - std::vector get_results() { - std::vector ret; - ret.reserve(results_.size()); - for ( auto&& v : results_ ) { - ret.push_back(v.get()); - } - return ret; - } - private: - std::atomic_size_t counter_{0}; - std::vector> results_; - }; - } - template < typename Iter , typename SubPromise = typename std::iterator_traits::value_type , typename SubPromiseResult = typename SubPromise::value_type @@ -1129,18 +1127,32 @@ namespace promise_hpp if ( begin == end ) { return make_resolved_promise(ResultPromiseValueType()); } + + struct context_t { + std::atomic_size_t success_counter{0u}; + std::vector> results; + context_t(std::size_t count) + : success_counter(count) + , results(count) {} + }; + return make_promise([begin, end](auto&& resolver, auto&& rejector){ std::size_t result_index = 0; - auto context = std::make_shared>(std::distance(begin, end)); + auto context = std::make_shared(std::distance(begin, end)); for ( Iter iter = begin; iter != end; ++iter, ++result_index ) { (*iter).then([ context, resolver, result_index ](auto&& v) mutable { - if ( context->apply_result(result_index, std::forward(v)) ) { - resolver(context->get_results()); + context->results[result_index] = std::forward(v); + if ( !--context->success_counter ) { + std::vector results; + results.reserve(context->results.size()); + for ( auto&& r : context->results ) { + results.push_back(std::move(*r)); + } + resolver(std::move(results)); } }).except(rejector); } @@ -1154,6 +1166,46 @@ namespace promise_hpp std::end(container)); } + // + // make_any_promise + // + + template < typename Iter + , typename SubPromise = typename std::iterator_traits::value_type + , typename SubPromiseResult = typename SubPromise::value_type > + promise + make_any_promise(Iter begin, Iter end) { + if ( begin == end ) { + throw std::logic_error("at least one input promise must be provided for make_any_promise"); + } + + struct context_t { + std::atomic_size_t failure_counter{0u}; + context_t(std::size_t count) + : failure_counter(count) {} + }; + + return make_promise([begin, end](auto&& resolver, auto&& rejector){ + auto context = std::make_shared(std::distance(begin, end)); + for ( Iter iter = begin; iter != end; ++iter ) { + (*iter).then([resolver](auto&& v) mutable { + resolver(std::forward(v)); + }).except([context, rejector](std::exception_ptr e) mutable { + if ( !--context->failure_counter ) { + rejector(e); + } + }); + } + }); + } + + template < typename Container > + auto make_any_promise(Container&& container) { + return make_any_promise( + std::begin(container), + std::end(container)); + } + // // make_race_promise // @@ -1166,6 +1218,7 @@ namespace promise_hpp if ( begin == end ) { throw std::logic_error("at least one input promise must be provided for make_race_promise"); } + return make_promise([begin, end](auto&& resolver, auto&& rejector){ for ( Iter iter = begin; iter != end; ++iter ) { (*iter) @@ -1205,11 +1258,11 @@ namespace promise_hpp using tuple_promise_result_t = typename tuple_promise_result::type; template < typename... ResultTypes > - class tuple_promise_context_t { + class tuple_promise_context_t final : private detail::noncopyable { public: template < std::size_t N, typename T > bool apply_result(T&& value) { - std::get(results_).set(std::forward(value)); + std::get(results_) = std::forward(value); return ++counter_ == sizeof...(ResultTypes); } @@ -1220,7 +1273,7 @@ namespace promise_hpp private: template < std::size_t... Is > std::tuple get_results_impl(std::index_sequence) { - return {std::get(results_).get()...}; + return {std::move(*std::get(results_))...}; } private: std::atomic_size_t counter_{0}; diff --git a/headers/promise.hpp/scheduler.hpp b/headers/promise.hpp/scheduler.hpp index 7ace422..d694ac6 100644 --- a/headers/promise.hpp/scheduler.hpp +++ b/headers/promise.hpp/scheduler.hpp @@ -26,7 +26,7 @@ namespace scheduler_hpp cancelled }; - class scheduler_cancelled_exception : public std::runtime_error { + class scheduler_cancelled_exception final : public std::runtime_error { public: scheduler_cancelled_exception() : std::runtime_error("scheduler has stopped working") {} @@ -82,7 +82,7 @@ namespace scheduler_hpp mutable std::condition_variable cond_var_; }; - class scheduler::task : private noncopyable { + class scheduler::task : private detail::noncopyable { public: virtual ~task() noexcept = default; virtual void run() noexcept = 0; @@ -90,7 +90,7 @@ namespace scheduler_hpp }; template < typename R, typename F, typename... Args > - class scheduler::concrete_task : public task { + class scheduler::concrete_task final : public task { F f_; std::tuple args_; promise promise_; @@ -103,7 +103,7 @@ namespace scheduler_hpp }; template < typename F, typename... Args > - class scheduler::concrete_task : public task { + class scheduler::concrete_task final : public task { F f_; std::tuple args_; promise promise_; diff --git a/untests/promise_tests.cpp b/untests/promise_tests.cpp index 59df20a..abd2fe7 100644 --- a/untests/promise_tests.cpp +++ b/untests/promise_tests.cpp @@ -205,7 +205,7 @@ TEST_CASE("promise") { SECTION("resolved_ref") { { int* check_42_int = nullptr; - auto p = pr::promise(); + auto p = pr::promise>(); int i = 42; p.resolve(i); p.then([&check_42_int](int& value) mutable { @@ -216,7 +216,7 @@ TEST_CASE("promise") { } { const int* check_42_int = nullptr; - auto p = pr::promise(); + auto p = pr::promise>(); const int i = 42; p.resolve(i); p.then([&check_42_int](const int& value) mutable { @@ -692,9 +692,23 @@ TEST_CASE("promise") { } { bool all_is_ok = false; - auto p = pr::make_all_promise(std::vector>{ - pr::make_resolved_promise(32), - pr::make_resolved_promise(10) + auto p = pr::make_resolved_promise().then_all([](){ + return std::vector>{ + pr::make_resolved_promise(32), + pr::make_resolved_promise(10)}; + }).then([&all_is_ok](const std::vector& c){ + all_is_ok = (2 == c.size()) + && c[0] == 32 + && c[1] == 10; + }); + REQUIRE(all_is_ok); + } + { + bool all_is_ok = false; + auto p = pr::make_resolved_promise(1).then_all([](int){ + return std::vector>{ + pr::make_resolved_promise(32), + pr::make_resolved_promise(10)}; }).then([&all_is_ok](const std::vector& c){ all_is_ok = (2 == c.size()) && c[0] == 32 @@ -749,6 +763,60 @@ TEST_CASE("promise") { }); } } + SECTION("make_any_promise") { + REQUIRE_THROWS_AS( + pr::make_any_promise(std::vector>{}), + std::logic_error); + { + auto p = pr::make_resolved_promise().then_any([](){ + return std::vector>{ + pr::make_resolved_promise(32), + pr::make_resolved_promise(10)}; + }).then([](int i){ + return i; + }); + REQUIRE(p.get() == 32); + } + { + auto p = pr::make_resolved_promise(1).then_any([](int){ + return std::vector>{ + pr::make_resolved_promise(32), + pr::make_resolved_promise(10)}; + }).then([](int i){ + return i; + }); + REQUIRE(p.get() == 32); + } + { + auto p = pr::make_any_promise(std::vector>{ + pr::make_resolved_promise(32), + pr::make_rejected_promise(std::logic_error("hello fail")) + }).then([](int i){ + return i; + }); + REQUIRE(p.get() == 32); + } + { + auto p = pr::make_any_promise(std::vector>{ + pr::make_rejected_promise(std::logic_error("hello fail")), + pr::make_resolved_promise(32) + }).then([](int i){ + return i; + }); + REQUIRE(p.get() == 32); + } + { + bool all_is_ok = false; + auto p = pr::make_any_promise(std::vector>{ + pr::make_rejected_promise(std::logic_error("hello fail")), + pr::make_rejected_promise(std::logic_error("hello fail")) + }).except([&all_is_ok](std::exception_ptr e){ + all_is_ok = true; + return 0; + }); + REQUIRE(all_is_ok); + } + } SECTION("make_race_promise") { { auto p1 = pr::promise(); @@ -892,8 +960,8 @@ TEST_CASE("promise") { }); } { - auto p1 = pr::promise(); - auto p2 = pr::promise(); + auto p1 = pr::promise>(); + auto p2 = pr::promise>(); auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2)); int i = 10;