mirror of
https://github.com/BlackMATov/promise.hpp.git
synced 2025-12-13 03:46:29 +07:00
@@ -6,6 +6,10 @@ find_package(Threads REQUIRED)
|
||||
file(GLOB test_sources "*.cpp" "*.hpp")
|
||||
add_executable(${PROJECT_NAME} ${test_sources})
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /bigobj)
|
||||
endif(MSVC)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
Threads::Threads)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
## Installation
|
||||
|
||||
[promise.hpp][promise] is a header only library. All you need to do is copy the header files (invoke.hpp and promise.hpp) into your project and include this file:
|
||||
[promise.hpp][promise] is a header only library. All you need to do is copy the header files (`invoke.hpp` and `promise.hpp`) into your project and include this file:
|
||||
|
||||
```cpp
|
||||
#include "promise.hpp"
|
||||
|
||||
@@ -329,6 +329,7 @@ namespace jobber_hpp
|
||||
if ( task ) {
|
||||
lock.unlock();
|
||||
task->run();
|
||||
lock.lock();
|
||||
--active_task_count_;
|
||||
cond_var_.notify_all();
|
||||
}
|
||||
|
||||
234
promise.hpp
234
promise.hpp
@@ -10,6 +10,7 @@
|
||||
#include <cassert>
|
||||
|
||||
#include <new>
|
||||
#include <tuple>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
@@ -92,19 +93,30 @@ namespace promise_hpp
|
||||
public:
|
||||
storage() = default;
|
||||
|
||||
~storage() noexcept(std::is_nothrow_destructible<T>::value) {
|
||||
~storage()
|
||||
noexcept(std::is_nothrow_destructible<T>::value)
|
||||
{
|
||||
if ( initialized_ ) {
|
||||
ptr_()->~T();
|
||||
}
|
||||
}
|
||||
|
||||
template < typename U >
|
||||
void set(U&& value) noexcept(std::is_nothrow_constructible<T,U&&>::value) {
|
||||
void set(U&& value)
|
||||
noexcept(std::is_nothrow_constructible<T,U&&>::value)
|
||||
{
|
||||
assert(!initialized_);
|
||||
::new(ptr_()) T(std::forward<U>(value));
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
T get()
|
||||
noexcept(std::is_nothrow_move_constructible<T>::value)
|
||||
{
|
||||
assert(initialized_);
|
||||
return std::move(*ptr_());
|
||||
}
|
||||
|
||||
const T& value() const noexcept {
|
||||
assert(initialized_);
|
||||
return *ptr_();
|
||||
@@ -245,6 +257,18 @@ namespace promise_hpp
|
||||
});
|
||||
}
|
||||
|
||||
template < typename ResolveF >
|
||||
auto then_tuple(ResolveF&& on_resolve) {
|
||||
return then([
|
||||
f = std::forward<ResolveF>(on_resolve)
|
||||
](auto&& v) mutable {
|
||||
auto r = invoke_hpp::invoke(
|
||||
std::forward<decltype(f)>(f),
|
||||
std::forward<decltype(v)>(v));
|
||||
return make_tuple_promise(std::move(r));
|
||||
});
|
||||
}
|
||||
|
||||
template < typename ResolveF
|
||||
, typename RejectF
|
||||
, typename ResolveFR = invoke_hpp::invoke_result_t<ResolveF,T> >
|
||||
@@ -637,6 +661,17 @@ namespace promise_hpp
|
||||
});
|
||||
}
|
||||
|
||||
template < typename ResolveF >
|
||||
auto then_tuple(ResolveF&& on_resolve) {
|
||||
return then([
|
||||
f = std::forward<ResolveF>(on_resolve)
|
||||
]() mutable {
|
||||
auto r = invoke_hpp::invoke(
|
||||
std::forward<decltype(f)>(f));
|
||||
return make_tuple_promise(std::move(r));
|
||||
});
|
||||
}
|
||||
|
||||
template < typename ResolveF
|
||||
, typename RejectF
|
||||
, typename ResolveFR = invoke_hpp::invoke_result_t<ResolveF> >
|
||||
@@ -992,40 +1027,55 @@ namespace promise_hpp
|
||||
// make_all_promise
|
||||
//
|
||||
|
||||
template < typename Iter >
|
||||
auto make_all_promise(Iter begin, Iter end) {
|
||||
using child_promise_t = typename Iter::value_type;
|
||||
using child_promise_value_t = typename child_promise_t::value_type;
|
||||
using promise_out_container_t = std::vector<child_promise_value_t>;
|
||||
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) {}
|
||||
|
||||
struct context_t {
|
||||
promise_out_container_t results;
|
||||
std::atomic_size_t counter = ATOMIC_VAR_INIT(0);
|
||||
|
||||
context_t(std::size_t count)
|
||||
: results(count) {}
|
||||
|
||||
bool apply_result(std::size_t index, const child_promise_value_t& value) {
|
||||
results[index] = value;
|
||||
return ++counter == results.size();
|
||||
template < typename T >
|
||||
bool apply_result(std::size_t index, T&& value) {
|
||||
results_[index].set(std::forward<T>(value));
|
||||
return ++counter_ == results_.size();
|
||||
}
|
||||
|
||||
std::vector<ResultType> get_results() {
|
||||
std::vector<ResultType> ret;
|
||||
ret.reserve(results_.size());
|
||||
for ( auto&& v : results_ ) {
|
||||
ret.emplace_back(v.get());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
private:
|
||||
std::atomic_size_t counter_{0};
|
||||
std::vector<detail::storage<ResultType>> results_;
|
||||
};
|
||||
}
|
||||
|
||||
template < typename Iter
|
||||
, typename SubPromise = typename Iter::value_type
|
||||
, typename SubPromiseResult = typename SubPromise::value_type
|
||||
, typename ResultPromiseValueType = std::vector<SubPromiseResult> >
|
||||
promise<ResultPromiseValueType>
|
||||
make_all_promise(Iter begin, Iter end) {
|
||||
if ( begin == end ) {
|
||||
return make_resolved_promise(promise_out_container_t());
|
||||
return make_resolved_promise(ResultPromiseValueType());
|
||||
}
|
||||
|
||||
return make_promise<promise_out_container_t>([begin, end](auto&& resolver, auto&& rejector){
|
||||
return make_promise<ResultPromiseValueType>([begin, end](auto&& resolver, auto&& rejector){
|
||||
std::size_t result_index = 0;
|
||||
auto context = std::make_shared<context_t>(std::distance(begin, end));
|
||||
for ( auto iter = begin; iter != end; ++iter, ++result_index ) {
|
||||
auto context = std::make_shared<impl::all_promise_context_t<
|
||||
SubPromiseResult>>(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<decltype(v)>(v)) ) {
|
||||
resolver(std::move(context->results));
|
||||
resolver(context->get_results());
|
||||
}
|
||||
}).except(rejector);
|
||||
}
|
||||
@@ -1043,18 +1093,18 @@ namespace promise_hpp
|
||||
// make_any_promise
|
||||
//
|
||||
|
||||
template < typename Iter >
|
||||
template < typename Iter
|
||||
, typename SubPromise = typename Iter::value_type
|
||||
, typename SubPromiseResult = typename SubPromise::value_type >
|
||||
auto make_any_promise(Iter begin, Iter end) {
|
||||
using child_promise_t = typename Iter::value_type;
|
||||
using child_promise_value_t = typename child_promise_t::value_type;
|
||||
|
||||
if ( begin == end ) {
|
||||
throw std::logic_error("at least one input promise must be provided for make_any_promise");
|
||||
}
|
||||
|
||||
return make_promise<child_promise_value_t>([begin, end](auto&& resolver, auto&& rejector){
|
||||
for ( auto iter = begin; iter != end; ++iter ) {
|
||||
(*iter).then(resolver).except(rejector);
|
||||
return make_promise<SubPromiseResult>([begin, end](auto&& resolver, auto&& rejector){
|
||||
for ( Iter iter = begin; iter != end; ++iter ) {
|
||||
(*iter)
|
||||
.then(resolver)
|
||||
.except(rejector);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1065,6 +1115,128 @@ namespace promise_hpp
|
||||
std::begin(container),
|
||||
std::end(container));
|
||||
}
|
||||
|
||||
//
|
||||
// make_tuple_promise
|
||||
//
|
||||
|
||||
namespace impl
|
||||
{
|
||||
template < typename Tuple >
|
||||
struct tuple_promise_result_impl {};
|
||||
|
||||
template < typename... Args >
|
||||
struct tuple_promise_result_impl<std::tuple<promise<Args>...>> {
|
||||
using type = std::tuple<Args...>;
|
||||
};
|
||||
|
||||
template < typename Tuple >
|
||||
struct tuple_promise_result {
|
||||
using type = typename tuple_promise_result_impl<std::remove_cv_t<Tuple>>::type;
|
||||
};
|
||||
|
||||
template < typename Tuple >
|
||||
using tuple_promise_result_t = typename tuple_promise_result<Tuple>::type;
|
||||
|
||||
template < typename... ResultTypes >
|
||||
class tuple_promise_context_t {
|
||||
public:
|
||||
template < std::size_t N, typename T >
|
||||
bool apply_result(T&& value) {
|
||||
std::get<N>(results_).set(std::forward<T>(value));
|
||||
return ++counter_ == sizeof...(ResultTypes);
|
||||
}
|
||||
|
||||
std::tuple<ResultTypes...> get_results() {
|
||||
return get_results_impl(
|
||||
std::make_index_sequence<sizeof...(ResultTypes)>());
|
||||
}
|
||||
private:
|
||||
template < std::size_t... Is >
|
||||
std::tuple<ResultTypes...> get_results_impl(std::index_sequence<Is...>) {
|
||||
return std::make_tuple(std::get<Is>(results_).get()...);
|
||||
}
|
||||
private:
|
||||
std::atomic_size_t counter_{0};
|
||||
std::tuple<detail::storage<ResultTypes>...> results_;
|
||||
};
|
||||
|
||||
template < typename... ResultTypes >
|
||||
using tuple_promise_context_ptr = std::shared_ptr<
|
||||
tuple_promise_context_t<ResultTypes...>>;
|
||||
|
||||
template < std::size_t I
|
||||
, typename Tuple
|
||||
, typename Resolver
|
||||
, typename Rejector
|
||||
, typename... ResultTypes >
|
||||
promise<void> make_tuple_sub_promise_impl(
|
||||
Tuple&& tuple,
|
||||
Resolver&& resolver,
|
||||
Rejector&& rejector,
|
||||
const tuple_promise_context_ptr<ResultTypes...>& context)
|
||||
{
|
||||
return std::get<I>(tuple).then([
|
||||
context,
|
||||
resolver
|
||||
](auto&& v) mutable {
|
||||
if (context->template apply_result<I>(std::forward<decltype(v)>(v))) {
|
||||
resolver(context->get_results());
|
||||
}
|
||||
}).except(rejector);
|
||||
}
|
||||
|
||||
template < typename Tuple
|
||||
, std::size_t... Is
|
||||
, typename ResultTuple = tuple_promise_result_t<std::decay_t<Tuple>> >
|
||||
std::enable_if_t<
|
||||
sizeof...(Is) == 0,
|
||||
promise<ResultTuple>>
|
||||
make_tuple_promise_impl(Tuple&&, std::index_sequence<Is...>) {
|
||||
return make_resolved_promise(ResultTuple());
|
||||
}
|
||||
|
||||
template < typename Tuple
|
||||
, std::size_t... Is
|
||||
, typename ResultTuple = tuple_promise_result_t<std::decay_t<Tuple>> >
|
||||
std::enable_if_t<
|
||||
sizeof...(Is) != 0,
|
||||
promise<ResultTuple>>
|
||||
make_tuple_promise_impl(Tuple&& tuple, std::index_sequence<Is...>) {
|
||||
auto result = promise<ResultTuple>();
|
||||
|
||||
auto resolver = [result](auto&& v) mutable {
|
||||
return result.resolve(std::forward<decltype(v)>(v));
|
||||
};
|
||||
|
||||
auto rejector = [result](auto&& e) mutable {
|
||||
return result.reject(std::forward<decltype(e)>(e));
|
||||
};
|
||||
|
||||
try {
|
||||
auto context = std::make_shared<tuple_promise_context_t<
|
||||
std::tuple_element_t<Is, ResultTuple>...>>();
|
||||
std::make_tuple(make_tuple_sub_promise_impl<Is>(
|
||||
tuple,
|
||||
resolver,
|
||||
rejector,
|
||||
context)...);
|
||||
} catch (...) {
|
||||
result.reject(std::current_exception());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template < typename Tuple
|
||||
, typename ResultTuple = impl::tuple_promise_result_t<std::decay_t<Tuple>> >
|
||||
promise<ResultTuple>
|
||||
make_tuple_promise(Tuple&& tuple) {
|
||||
return impl::make_tuple_promise_impl(
|
||||
std::forward<Tuple>(tuple),
|
||||
std::make_index_sequence<std::tuple_size<ResultTuple>::value>());
|
||||
}
|
||||
}
|
||||
|
||||
namespace std
|
||||
|
||||
@@ -29,16 +29,6 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
bool check_hello_fail2_exception(std::exception_ptr e) {
|
||||
try {
|
||||
std::rethrow_exception(e);
|
||||
} catch (std::logic_error& ee) {
|
||||
return 0 == std::strcmp(ee.what(), "hello fail2");
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class auto_thread final {
|
||||
public:
|
||||
template < typename F, typename... Args >
|
||||
@@ -709,6 +699,20 @@ TEST_CASE("promise") {
|
||||
|
||||
REQUIRE(call_then_only_once == 1);
|
||||
}
|
||||
{
|
||||
class o_t {
|
||||
public:
|
||||
o_t() = delete;
|
||||
o_t(int) {}
|
||||
};
|
||||
|
||||
pr::promise<>()
|
||||
.then_all([](){
|
||||
return std::vector<pr::promise<o_t>>{
|
||||
pr::make_resolved_promise<o_t>(40),
|
||||
pr::make_resolved_promise<o_t>(2)};
|
||||
});
|
||||
}
|
||||
}
|
||||
SECTION("make_any_promise") {
|
||||
{
|
||||
@@ -751,6 +755,87 @@ TEST_CASE("promise") {
|
||||
REQUIRE(check_42_int == 42);
|
||||
REQUIRE(call_then_only_once == 1);
|
||||
}
|
||||
{
|
||||
class o_t {
|
||||
public:
|
||||
o_t() = delete;
|
||||
o_t(int) {}
|
||||
};
|
||||
|
||||
pr::promise<>()
|
||||
.then_any([](){
|
||||
return std::vector<pr::promise<o_t>>{
|
||||
pr::make_resolved_promise<o_t>(40),
|
||||
pr::make_resolved_promise<o_t>(2)};
|
||||
});
|
||||
}
|
||||
}
|
||||
SECTION("make_tuple_promise") {
|
||||
{
|
||||
static_assert(
|
||||
std::is_same<
|
||||
pr::impl::tuple_promise_result_t<std::tuple<>>,
|
||||
std::tuple<>>::value,
|
||||
"unit test fail");
|
||||
static_assert(
|
||||
std::is_same<
|
||||
pr::impl::tuple_promise_result_t<std::tuple<pr::promise<int>>>,
|
||||
std::tuple<int>>::value,
|
||||
"unit test fail");
|
||||
static_assert(
|
||||
std::is_same<
|
||||
pr::impl::tuple_promise_result_t<std::tuple<pr::promise<int>, pr::promise<float>>>,
|
||||
std::tuple<int, float>>::value,
|
||||
"unit test fail");
|
||||
}
|
||||
{
|
||||
auto p = pr::make_tuple_promise(std::make_tuple());
|
||||
REQUIRE(p.get() == std::make_tuple());
|
||||
}
|
||||
{
|
||||
auto p1 = pr::promise<int>();
|
||||
auto p2 = pr::make_tuple_promise(std::make_tuple(p1));
|
||||
p1.resolve(42);
|
||||
REQUIRE(p2.get_or_default(std::make_tuple(0)) == std::make_tuple(42));
|
||||
}
|
||||
{
|
||||
auto p1 = pr::promise<int>();
|
||||
auto t0 = std::make_tuple(p1);
|
||||
auto p2 = pr::make_tuple_promise(t0);
|
||||
p1.resolve(42);
|
||||
REQUIRE(p2.get_or_default(std::make_tuple(0)) == std::make_tuple(42));
|
||||
}
|
||||
{
|
||||
auto p1 = pr::promise<int>();
|
||||
auto p2 = pr::promise<float>();
|
||||
auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2));
|
||||
p1.resolve(42);
|
||||
p2.resolve(4.2f);
|
||||
REQUIRE(p3.get_or_default(std::make_tuple(0, 0.f)) == std::make_tuple(42, 4.2f));
|
||||
}
|
||||
{
|
||||
auto p1 = pr::promise<int>();
|
||||
auto p2 = pr::promise<float>();
|
||||
auto p3 = pr::promise<int>();
|
||||
auto p4 = pr::make_tuple_promise(std::make_tuple(p1, p2, p3));
|
||||
p1.resolve(42);
|
||||
p2.resolve(4.2f);
|
||||
p3.resolve(84);
|
||||
REQUIRE(p4.get_or_default(std::make_tuple(0, 0.f, 0)) == std::make_tuple(42, 4.2f, 84));
|
||||
}
|
||||
{
|
||||
class o_t {
|
||||
public:
|
||||
o_t() = delete;
|
||||
};
|
||||
|
||||
pr::promise<>()
|
||||
.then_tuple([](){
|
||||
auto p1 = pr::promise<o_t>();
|
||||
auto p2 = pr::promise<o_t>();
|
||||
return std::make_tuple(std::move(p1), std::move(p2));
|
||||
});
|
||||
}
|
||||
}
|
||||
SECTION("make_all_promise_fail") {
|
||||
{
|
||||
@@ -810,6 +895,44 @@ TEST_CASE("promise") {
|
||||
REQUIRE(call_fail_with_logic_error);
|
||||
}
|
||||
}
|
||||
SECTION("make_tuple_promise_fail") {
|
||||
{
|
||||
auto p1 = pr::promise<int>();
|
||||
auto p2 = pr::make_tuple_promise(std::make_tuple(p1));
|
||||
p1.reject(std::logic_error("hello failt"));
|
||||
REQUIRE_THROWS_AS(p2.get(), std::logic_error);
|
||||
}
|
||||
{
|
||||
auto p1 = pr::promise<int>();
|
||||
auto p2 = pr::promise<float>();
|
||||
auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2));
|
||||
p1.resolve(42);
|
||||
p2.reject(std::logic_error("hello failt"));
|
||||
REQUIRE_THROWS_AS(p3.get(), std::logic_error);
|
||||
}
|
||||
{
|
||||
auto p1 = pr::promise<int>();
|
||||
auto p2 = pr::promise<float>();
|
||||
auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2));
|
||||
p1.reject(std::logic_error("hello failt"));
|
||||
p2.resolve(4.2f);
|
||||
REQUIRE_THROWS_AS(p3.get(), std::logic_error);
|
||||
}
|
||||
{
|
||||
auto p1 = pr::promise<int>();
|
||||
auto p2 = pr::promise<float>();
|
||||
auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2));
|
||||
p1.reject(std::logic_error("hello failt"));
|
||||
REQUIRE_THROWS_AS(p3.get(), std::logic_error);
|
||||
}
|
||||
{
|
||||
auto p1 = pr::promise<int>();
|
||||
auto p2 = pr::promise<float>();
|
||||
auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2));
|
||||
p2.reject(std::logic_error("hello failt"));
|
||||
REQUIRE_THROWS_AS(p3.get(), std::logic_error);
|
||||
}
|
||||
}
|
||||
SECTION("then_all") {
|
||||
{
|
||||
int check_42_int = 0;
|
||||
@@ -887,6 +1010,44 @@ TEST_CASE("promise") {
|
||||
REQUIRE(call_then_only_once == 1);
|
||||
}
|
||||
}
|
||||
SECTION("then_tuple") {
|
||||
{
|
||||
float check_42_float = 0.f;
|
||||
pr::make_resolved_promise()
|
||||
.then_tuple([](){
|
||||
return std::make_tuple(
|
||||
pr::make_resolved_promise(32),
|
||||
pr::make_resolved_promise(10.f));
|
||||
}).then([&check_42_float](const std::tuple<int, float>& t){
|
||||
check_42_float = std::get<0>(t) + std::get<1>(t);
|
||||
});
|
||||
REQUIRE(check_42_float == Approx(42.f).margin(0.01f));
|
||||
}
|
||||
{
|
||||
float check_42_float = 0.f;
|
||||
pr::make_resolved_promise(42)
|
||||
.then_tuple([](int){
|
||||
return std::make_tuple(
|
||||
pr::make_resolved_promise(32),
|
||||
pr::make_resolved_promise(10.f));
|
||||
}).then([&check_42_float](const std::tuple<int, float>& t){
|
||||
check_42_float = std::get<0>(t) + std::get<1>(t);
|
||||
});
|
||||
REQUIRE(check_42_float == Approx(42.f).margin(0.01f));
|
||||
}
|
||||
{
|
||||
bool call_fail_with_logic_error = false;
|
||||
pr::make_resolved_promise(42)
|
||||
.then_tuple([](int){
|
||||
return std::make_tuple(
|
||||
pr::make_resolved_promise(32),
|
||||
pr::make_rejected_promise<float>(std::logic_error("hello fail")));
|
||||
}).except([&call_fail_with_logic_error](std::exception_ptr e){
|
||||
call_fail_with_logic_error = check_hello_fail_exception(e);
|
||||
});
|
||||
REQUIRE(call_fail_with_logic_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("get_and_wait") {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@echo off
|
||||
set SCRIPT_DIR=%~dp0%
|
||||
%SCRIPT_DIR%\build_debug.bat || goto :error
|
||||
%SCRIPT_DIR%\build_release.bat || goto :error
|
||||
call %SCRIPT_DIR%\build_debug.bat || goto :error
|
||||
call %SCRIPT_DIR%\build_release.bat || goto :error
|
||||
|
||||
goto :EOF
|
||||
|
||||
|
||||
Reference in New Issue
Block a user