diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9d2b3b3..143177b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -15,3 +15,4 @@ add_subdirectory(invoke) add_subdirectory(nested) add_subdirectory(simple) add_subdirectory(pipeline) +add_subdirectory(reduce) diff --git a/examples/for_each/for_each_1.cpp b/examples/for_each/for_each_1.cpp deleted file mode 100644 index 76815f9..0000000 --- a/examples/for_each/for_each_1.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace execution = std::experimental::execution; -using std::experimental::static_thread_pool; - -namespace impl -{ - -static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; - -class system_thread_pool_bulk_executor -{ - public: - using shape_type = execution::executor_shape_t; - using index_type = execution::executor_index_t; - - template - using future = execution::executor_future_t; - - auto& query(execution::context_t) const noexcept { return system_thread_pool; } - - friend bool operator==(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return true; - } - - friend bool operator!=(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return false; - } - - system_thread_pool_bulk_executor require(execution::bulk_guarantee_t::parallel_t) const { return *this; } - - template - auto bulk_twoway_execute(Function f, size_t n, ResultFactory rf, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_twoway_execute(std::move(f), n, std::move(rf), std::move(sf)); - } - - template - auto bulk_execute(Function f, size_t n, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_execute(std::move(f), n, std::move(sf)); - } -}; - -template -class basic_execution_policy -{ - public: - //static_assert(is_weaker_than< - // BulkForwardProgressRequirement, - // executor_bulk_forward_progress_guarantee_t - // >::value, - // "basic_execution_policy: BulkForwardProgressRequirement cannot be satisfied by Executor's guarantee." - //); - - using executor_type = Executor; - using bulk_forward_progress_requirement = BulkForwardProgressRequirement; - - basic_execution_policy() = default; - - basic_execution_policy(const basic_execution_policy&) = default; - - basic_execution_policy(executor_type&& exec) - : executor_(std::move(exec)) - {} - - basic_execution_policy(const executor_type& exec) - : executor_(exec) - {} - - template::type> - // >::value - //>::type - > - basic_execution_policy on(OtherExecutor&& exec) const - { - return basic_execution_policy( - execution::require(std::forward(exec), BulkForwardProgressRequirement{})); - } - - executor_type executor() const - { - return executor_; - } - - private: - executor_type executor_; -}; - -constexpr struct ignored {} ignore; - -} // end impl - -class parallel_policy : public impl::basic_execution_policy -{ - using super_t = impl::basic_execution_policy; - - public: - using super_t::super_t; -}; - -constexpr parallel_policy par{}; - -template -void for_each(ExecutionPolicy&& policy, RandomAccessIterator first, RandomAccessIterator last, Function f) -{ - auto n = last - first; - - auto twoway_bulk_exec = execution::require(policy.executor(), execution::bulk, execution::twoway); - - twoway_bulk_exec.bulk_twoway_execute([=](size_t idx, impl::ignored&) - { - f(first[idx]); - }, - n, - []{}, - []{ return impl::ignore; } - ).get(); -} - -int main() -{ - std::vector vec(10); - - for_each(par, vec.begin(), vec.end(), [](int& x) - { - x = 42; - }); - - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); - - std::cout << "OK" << std::endl; -} diff --git a/examples/for_each/for_each_2.cpp b/examples/for_each/for_each_2.cpp deleted file mode 100644 index 0f1d7ce..0000000 --- a/examples/for_each/for_each_2.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace execution = std::experimental::execution; -using std::experimental::static_thread_pool; - -namespace impl -{ - -static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; - -class system_thread_pool_bulk_executor -{ - public: - using shape_type = execution::executor_shape_t; - using index_type = execution::executor_index_t; - - template - using future = execution::executor_future_t; - - auto& query(execution::context_t) const noexcept { return system_thread_pool; } - - friend bool operator==(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return true; - } - - friend bool operator!=(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return false; - } - - system_thread_pool_bulk_executor require(execution::bulk_guarantee_t::parallel_t) const { return *this; } - - template - auto bulk_twoway_execute(Function f, size_t n, ResultFactory rf, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_twoway_execute(std::move(f), n, std::move(rf), std::move(sf)); - } - - template - auto bulk_execute(Function f, size_t n, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_execute(std::move(f), n, std::move(sf)); - } -}; - -template -class basic_execution_policy -{ - public: - //static_assert(is_weaker_than< - // BulkForwardProgressRequirement, - // executor_bulk_forward_progress_guarantee_t - // >::value, - // "basic_execution_policy: BulkForwardProgressRequirement cannot be satisfied by Executor's guarantee." - //); - - using executor_type = Executor; - using bulk_forward_progress_requirement = BulkForwardProgressRequirement; - - basic_execution_policy() = default; - - basic_execution_policy(const basic_execution_policy&) = default; - - basic_execution_policy(executor_type&& exec) - : executor_(std::move(exec)) - {} - - basic_execution_policy(const executor_type& exec) - : executor_(exec) - {} - - template::type> - // >::value - //>::type - > - basic_execution_policy on(OtherExecutor&& exec) const - { - return basic_execution_policy( - execution::require(std::forward(exec), BulkForwardProgressRequirement{})); - } - - executor_type executor() const - { - return executor_; - } - - private: - executor_type executor_; -}; - -constexpr struct ignored {} ignore; - -} // end impl - -class parallel_policy : public impl::basic_execution_policy -{ - using super_t = impl::basic_execution_policy; - - public: - using super_t::super_t; -}; - -constexpr parallel_policy par{}; - -template -void for_each(ExecutionPolicy&& policy, RandomAccessIterator first, RandomAccessIterator last, Function f) -{ - auto n = last - first; - - auto twoway_bulk_exec = execution::require(policy.executor(), execution::bulk, execution::twoway); - - twoway_bulk_exec.bulk_twoway_execute([=](size_t idx, impl::ignored&) - { - f(first[idx]); - }, - n, - []{}, - []{ return impl::ignore; } - ).get(); -} - -int main() -{ - static_thread_pool pool{1}; - - std::vector vec(10); - - for_each(par.on(pool.executor()), vec.begin(), vec.end(), [](int& x) - { - x = 42; - }); - - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); - - std::cout << "OK" << std::endl; -} diff --git a/examples/for_each/for_each_3.cpp b/examples/for_each/for_each_3.cpp deleted file mode 100644 index 627bc91..0000000 --- a/examples/for_each/for_each_3.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace execution = std::experimental::execution; -using std::experimental::static_thread_pool; - -namespace impl -{ - -static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; - -class system_thread_pool_bulk_executor -{ - public: - using shape_type = execution::executor_shape_t; - using index_type = execution::executor_index_t; - - template - using future = execution::executor_future_t; - - auto& query(execution::context_t) const noexcept { return system_thread_pool; } - - friend bool operator==(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return true; - } - - friend bool operator!=(const system_thread_pool_bulk_executor&, const system_thread_pool_bulk_executor&) noexcept - { - return false; - } - - system_thread_pool_bulk_executor require(execution::bulk_guarantee_t::parallel_t) const { return *this; } - bool query(execution::bulk_guarantee_t::parallel_t) const { return true; } - - template - auto bulk_twoway_execute(Function f, size_t n, ResultFactory rf, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_twoway_execute(std::move(f), n, std::move(rf), std::move(sf)); - } - - template - auto bulk_execute(Function f, size_t n, SharedFactory sf) const - { - return system_thread_pool.executor().bulk_execute(std::move(f), n, std::move(sf)); - } -}; - -template -class basic_execution_policy -{ - public: - //static_assert(is_weaker_than< - // BulkForwardProgressRequirement, - // executor_bulk_forward_progress_guarantee_t - // >::value, - // "basic_execution_policy: BulkForwardProgressRequirement cannot be satisfied by Executor's guarantee." - //); - - using executor_type = Executor; - using bulk_forward_progress_requirement = BulkForwardProgressRequirement; - - basic_execution_policy() = default; - - basic_execution_policy(const basic_execution_policy&) = default; - - basic_execution_policy(executor_type&& exec) - : executor_(std::move(exec)) - {} - - basic_execution_policy(const executor_type& exec) - : executor_(exec) - {} - - template::type> - // >::value - //>::type - > - basic_execution_policy on(OtherExecutor&& exec) const - { - return basic_execution_policy( - execution::require(std::forward(exec), BulkForwardProgressRequirement{})); - } - - executor_type executor() const - { - return executor_; - } - - private: - executor_type executor_; -}; - -constexpr struct ignored {} ignore; - -} // end impl - -class parallel_policy : public impl::basic_execution_policy -{ - using super_t = impl::basic_execution_policy; - - public: - using super_t::super_t; -}; - -constexpr parallel_policy par{}; - -template -void for_each(ExecutionPolicy&& policy, RandomAccessIterator first, RandomAccessIterator last, Function f) -{ - auto n = last - first; - - auto twoway_bulk_exec = execution::require(policy.executor(), execution::blocking_adaptation.allowed, execution::bulk, execution::twoway); - - twoway_bulk_exec.bulk_twoway_execute([=](size_t idx, impl::ignored&) - { - f(first[idx]); - }, - n, - []{}, - []{ return impl::ignore; } - ).get(); -} - -class inline_executor -{ -public: - friend bool operator==(const inline_executor&, const inline_executor&) noexcept - { - return true; - } - - friend bool operator!=(const inline_executor&, const inline_executor&) noexcept - { - return false; - } - - inline_executor require(execution::bulk_guarantee_t::parallel_t) const - { - return *this; - } - - bool query(execution::bulk_guarantee_t::parallel_t) const - { - return true; - } - - template - void execute(Function f) const noexcept - { - f(); - } -}; - -int main() -{ - std::vector vec(10); - - for_each(par.on(inline_executor()), vec.begin(), vec.end(), [](int& x) - { - x = 42; - }); - - assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); - - std::cout << "OK" << std::endl; -} diff --git a/examples/for_each/for_each_n.hpp b/examples/for_each/for_each_n.hpp new file mode 100644 index 0000000..da2bdeb --- /dev/null +++ b/examples/for_each/for_each_n.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include "query_or.hpp" +#include "noexcept_function.hpp" +#include "noexcept_iterator.hpp" +#include +#include +#include + + +namespace impl +{ + + +struct ignored {}; +constexpr ignored ignore{}; + + +template +void for_each_n(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccessIterator first, Size n, Function f) +{ + // terminate if f throws for any reason + auto noexcept_f = impl::make_noexcept_function(f); + + // terminate if an iterator operation throws for any reason + auto noexcept_first = impl::make_noexcept_iterator(first); + + try + { + namespace execution = std::experimental::execution; + + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always); + + // create agents + // note that this throws only upon failure to create EAs + ex.bulk_execute( + [=](size_t idx, impl::ignored&) + { + noexcept_f(noexcept_first[idx]); + }, + n, + []{ return impl::ignore; } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++noexcept_first) + { + noexcept_f(*noexcept_first); + } + } +} + + +template +std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) +{ + // how large should each subrange be? + std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; + + // store an iterator pointing to the beginning of each subrange + // the final element of this vector points is first + n + std::vector result; + result.reserve(num_subranges + 1); + + Size subrange_idx = 0; + for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) + { + // append the beginning of the subrange + result.push_back(first); + + // are we at the last subrange? + std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; + + std::advance(first, distance); + } + + // finally, add the end of the range + result.push_back(first); + + return result; +} + + +template +void for_each_n(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator first, Size n, Function f) +{ + // terminate if f throws for any reason + auto noexcept_f = impl::make_noexcept_function(f); + + // terminate if an iterator operation throws for any reason + auto noexcept_first = impl::make_noexcept_iterator(first); + + try + { + namespace execution = std::experimental::execution; + + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always); + + // choose a number of subranges + size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, Size(1))); + + // create agents + // note that this throws only upon failure to create EAs + ex.bulk_execute( + [=](size_t subrange_idx, const std::vector>& subranges_begin) + { + noexcept_iterator end = subranges_begin[subrange_idx+1]; + + for(noexcept_iterator iter = subranges_begin[subrange_idx]; iter != end; ++iter) + { + noexcept_f(*iter); + } + }, + num_subranges, + [=] + { + return impl::partition_into_subranges(noexcept_first, n, num_subranges); + } + ); + } + catch(...) + { + // sequential fallback + for(Size i = 0; i < n; ++i, ++noexcept_first) + { + noexcept_f(*noexcept_first); + } + } +} + + +} // end impl + + +template +void for_each_n(ExecutionPolicy&& policy, Iterator first, Size n, Function f) +{ + impl::for_each_n(typename std::iterator_traits::iterator_category(), std::forward(policy), first, n, f); +} + diff --git a/examples/for_each/for_each_n_1.cpp b/examples/for_each/for_each_n_1.cpp new file mode 100644 index 0000000..c65a4e8 --- /dev/null +++ b/examples/for_each/for_each_n_1.cpp @@ -0,0 +1,106 @@ +#include "for_each_n.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + + for_each_n(par, vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + + { + // test with par, list + + std::list lst(10); + + for_each_n(par, lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + + // for_each_n(par, vec.begin(), vec.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + + // for_each_n(par, lst.begin(), lst.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + + // for_each_n(par, make_throwing_iterator(vec.begin()), vec.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + + // for_each_n(par, make_throwing_iterator(lst.begin()), lst.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + diff --git a/examples/for_each/for_each_n_2.cpp b/examples/for_each/for_each_n_2.cpp new file mode 100644 index 0000000..b319f83 --- /dev/null +++ b/examples/for_each/for_each_n_2.cpp @@ -0,0 +1,120 @@ +#include "for_each_n.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include +#include + + +int main() +{ + { + // test with static_thread_pool, vector + + std::vector vec(10); + + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(pool.executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + + { + // test with static_thread_pool, list + + std::list lst(10); + + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(pool.executor()), lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with static_thread_pool, vector, throwing function + + // std::vector vec(10); + + // std::experimental::static_thread_pool pool(4); + + // for_each_n(par.on(pool.executor()), vec.begin(), vec.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with static_thread_pool, list, throwing function + + // std::list lst(10); + + // std::experimental::static_thread_pool pool(4); + + // for_each_n(par.on(pool.executor()), lst.begin(), lst.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with static_thread_pool, vector, throwing iterator + + // std::vector vec(10); + + // std::experimental::static_thread_pool pool(4); + + // for_each_n(par.on(pool.executor()), make_throwing_iterator(vec.begin()), vec.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with static_thread_pool, list, throwing iterator + + // std::list lst(10); + + // std::experimental::static_thread_pool pool(4); + + // for_each_n(par.on(pool.executor()), make_throwing_iterator(lst.begin()), lst.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + diff --git a/examples/for_each/for_each_n_3.cpp b/examples/for_each/for_each_n_3.cpp new file mode 100644 index 0000000..ce49138 --- /dev/null +++ b/examples/for_each/for_each_n_3.cpp @@ -0,0 +1,151 @@ +#include +#include "for_each_n.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; + + +class inline_executor +{ + public: + friend bool operator==(const inline_executor&, const inline_executor&) noexcept + { + return true; + } + + friend bool operator!=(const inline_executor&, const inline_executor&) noexcept + { + return false; + } + + inline_executor require(execution::oneway_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void execute(Function f) const noexcept + { + f(); + } +}; + + +int main() +{ + { + // test with inline_executor, vector + + std::vector vec(10); + + for_each_n(par.on(inline_executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + + { + // test with inline_executor, list + + std::list lst(10); + + for_each_n(par.on(inline_executor()), lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with inline_executor, vector, throwing function + + // std::vector vec(10); + + // for_each_n(par.on(inline_executor()), vec.begin(), vec.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with inline_executor, list, throwing function + + // std::list lst(10); + + // for_each_n(par.on(inline_executor()), lst.begin(), lst.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with inline_executor, vector, throwing iterator + + // std::vector vec(10); + + // for_each_n(par.on(inline_executor()), make_throwing_iterator(vec.begin()), vec.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with inline_executor, list, throwing iterator + + // std::list lst(10); + + // for_each_n(par.on(inline_executor()), make_throwing_iterator(lst.begin()), lst.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + diff --git a/examples/for_each/for_each_n_4.cpp b/examples/for_each/for_each_n_4.cpp new file mode 100644 index 0000000..924b3aa --- /dev/null +++ b/examples/for_each/for_each_n_4.cpp @@ -0,0 +1,165 @@ +#include +#include "for_each_n.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; + + +class bulk_inline_executor +{ + public: + friend bool operator==(const bulk_inline_executor&, const bulk_inline_executor&) noexcept + { + return true; + } + + friend bool operator!=(const bulk_inline_executor&, const bulk_inline_executor&) noexcept + { + return false; + } + + bulk_inline_executor require(execution::oneway_t) const + { + return *this; + } + + bulk_inline_executor require(execution::bulk_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void bulk_execute(Function f, size_t n, Factory shared_factory) const noexcept + { + auto shared = shared_factory(); + + for(size_t i = 0; i < n; ++i) + { + f(i, shared); + } + } +}; + + +int main() +{ + { + // test with bulk_inline_executor, vector + + std::vector vec(10); + + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(bulk_inline_executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + + { + // test with bulk_inline_executor, list + + std::list lst(10); + + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(bulk_inline_executor()), lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with bulk_inline_executor, vector, throwing function + + // std::vector vec(10); + + // for_each_n(par.on(bulk_inline_executor()), vec.begin(), vec.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with bulk_inline_executor, list, throwing function + + // std::list lst(10); + + // for_each_n(par.on(bulk_inline_executor()), lst.begin(), lst.size(), [](int& x) + // { + // x = 42; + + // throw 13; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with bulk_inline_executor, vector, throwing iterator + + // std::vector vec(10); + + // for_each_n(par.on(bulk_inline_executor()), make_throwing_iterator(vec.begin()), vec.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with bulk_inline_executor, list, throwing iterator + + // std::list lst(10); + + // for_each_n(par.on(bulk_inline_executor()), make_throwing_iterator(lst.begin()), lst.size(), [](int& x) + // { + // x = 42; + // }); + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + diff --git a/examples/for_each/for_each_n_5.cpp b/examples/for_each/for_each_n_5.cpp new file mode 100644 index 0000000..6cd1915 --- /dev/null +++ b/examples/for_each/for_each_n_5.cpp @@ -0,0 +1,97 @@ +#include +#include "for_each_n.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; + + +// creating EAs with this executor always fails via exception +class throwing_executor +{ + public: + friend bool operator==(const throwing_executor&, const throwing_executor&) noexcept + { + return true; + } + + friend bool operator!=(const throwing_executor&, const throwing_executor&) noexcept + { + return false; + } + + throwing_executor require(execution::oneway_t) const + { + return *this; + } + + throwing_executor require(execution::bulk_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void bulk_execute(Function f, size_t n, Factory shared_factory) const + { + throw 13; + } +}; + + +int main() +{ + { + // test with throwing_executor, vector + + std::vector vec(10); + + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(throwing_executor()), vec.begin(), vec.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + + { + // test with throwing_executor, list + + std::list lst(10); + + std::experimental::static_thread_pool pool(4); + + for_each_n(par.on(throwing_executor()), lst.begin(), lst.size(), [](int& x) + { + x = 42; + }); + + assert(std::count(lst.begin(), lst.end(), 42) == static_cast(lst.size())); + } + + std::cout << "OK" << std::endl; + + return 0; +} + diff --git a/examples/for_each/for_each_n_6.cpp b/examples/for_each/for_each_n_6.cpp new file mode 100644 index 0000000..4c19748 --- /dev/null +++ b/examples/for_each/for_each_n_6.cpp @@ -0,0 +1,170 @@ +#include +#include +#include +#include +#include +#include +#include "par.hpp" +#include "for_each_n.hpp" + +namespace execution = std::experimental::execution; + +template +class bulk_oneway_executor_adaptor +{ + public: + bulk_oneway_executor_adaptor(const bulk_oneway_executor_adaptor&) = default; + + explicit bulk_oneway_executor_adaptor(const OneWayExecutor& executor) noexcept + : executor_(executor) + {} + + template + void bulk_execute(Function f, size_t n, Factory shared_factory) const + { + executor_.execute([=] + { + auto shared = shared_factory(); + for(size_t i = 0; i != n; ++i) + { + f(i, shared); + } + }); + } + + bool operator==(const bulk_oneway_executor_adaptor& other) const noexcept + { + return executor_ == other.executor_; + } + + bool operator!=(const bulk_oneway_executor_adaptor& other) const noexcept + { + return executor_ != other.executor_; + } + + bulk_oneway_executor_adaptor require(execution::oneway_t) const + { + return *this; + } + + template + >> + auto require(const Property& prop) const + { + auto adapted_executor = execution::require(executor_, prop); + return bulk_oneway_executor_adaptor{adapted_executor}; + } + + // XXX what is the best way to provide both .query() and ::query()? + //template + // >> + //constexpr auto query(const Property& prop) const + //{ + // return execution::query(executor_, prop); + //} + + //template + // >, + // class = decltype(Property::template static_query_v) + // > + //constexpr static auto query(const Property& prop) + //{ + // return Property::template static_query_v; + //} + + // XXX workaround issues with forwarding queries above + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) noexcept + { + return execution::mapping.thread; + } + + // XXX weaken this guarantee to satisfy .on() + //constexpr static execution::bulk_guarantee_t::sequenced_t query(execution::bulk_guarantee_t) noexcept + //{ + // return execution::bulk_guarantee.sequenced; + //} + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) noexcept + { + return execution::bulk_guarantee.parallel; + } + + private: + OneWayExecutor executor_; +}; + + +class inline_executor +{ + public: + friend bool operator==(const inline_executor&, const inline_executor&) noexcept + { + return true; + } + + friend bool operator!=(const inline_executor&, const inline_executor&) noexcept + { + return false; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void execute(Function f) const noexcept + { + f(); + } +}; + + +int main() +{ + { + // test with adapted inline_executor, vector + + std::vector vec(10); + + bulk_oneway_executor_adaptor ex(inline_executor{}); + + for_each_n(par.on(ex), vec.begin(), vec.size(), [](int& x) + { + x += 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + + { + // test with adapted static_thread_pool.executor(), vector + + std::vector vec(10); + + std::experimental::static_thread_pool pool(4); + bulk_oneway_executor_adaptor ex(pool.executor()); + + for_each_n(par.on(ex), vec.begin(), vec.size(), [](int& x) + { + x += 42; + }); + + assert(std::count(vec.begin(), vec.end(), 42) == static_cast(vec.size())); + } + + std::cout << "OK" << std::endl; + + return 0; +} + diff --git a/examples/for_each/noexcept_function.hpp b/examples/for_each/noexcept_function.hpp new file mode 100644 index 0000000..5833ded --- /dev/null +++ b/examples/for_each/noexcept_function.hpp @@ -0,0 +1,51 @@ +#pragma + +#include +#include + +namespace impl +{ + + +template +class noexcept_function +{ + public: + template + >> + noexcept_function(OtherFunction&& function) noexcept + : function_(std::forward(function)) + {} + + noexcept_function(const noexcept_function& other) noexcept + : function_(other.function_) + {} + + noexcept_function(noexcept_function&& other) noexcept + : function_(std::move(other.function_)) + {} + + ~noexcept_function() noexcept + {} + + template + auto operator()(Args&&... args) const noexcept + { + return function_(std::forward(args)...); + } + + private: + mutable Function function_; +}; + +template +noexcept_function> make_noexcept_function(Function&& f) +{ + return {std::forward(f)}; +} + + +} // end impl + diff --git a/examples/for_each/noexcept_iterator.hpp b/examples/for_each/noexcept_iterator.hpp new file mode 100644 index 0000000..2785fc5 --- /dev/null +++ b/examples/for_each/noexcept_iterator.hpp @@ -0,0 +1,169 @@ +#pragma once + +#include +#include +#include + + +namespace impl +{ + + +template +class noexcept_iterator +{ + public: + template + >> + explicit noexcept_iterator(OtherIterator&& iterator) noexcept + : iterator_(std::forward(iterator)) + {} + + // CopyConstructible requirements + noexcept_iterator(const noexcept_iterator& other) noexcept + : iterator_(other.iterator_) + {} + + // CopyAssignable requirements + noexcept_iterator& operator=(const noexcept_iterator& other) noexcept + { + iterator_ = other.iterator_; + return *this; + } + + // Destructible requirements + ~noexcept_iterator() noexcept + {} + + // Swappable requirements + void swap(const noexcept_iterator& other) noexcept + { + using std::swap; + swap(iterator_, other.iterator_); + } + + // LegacyIterator requirements + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + using iterator_category = typename std::iterator_traits::iterator_category; + + noexcept_iterator& operator++() noexcept + { + ++iterator_; + return *this; + } + + // LegacyInputIterator requirements + bool operator!=(const noexcept_iterator& other) const noexcept + { + return iterator_ != other.iterator_; + } + + reference operator*() const noexcept + { + return *iterator_; + } + + pointer operator->() const noexcept + { + // XXX this doesn't seem right because it wouldn't work if Iterator is a raw pointer + return iterator_.operator->(); + } + + noexcept_iterator operator++(int) const noexcept + { + noexcept_iterator result(*this); + operator++(); + return result; + } + + // LegacyBidirectionalIterator requirements + noexcept_iterator& operator--() noexcept + { + --iterator_; + return *this; + } + + noexcept_iterator operator--(int) const noexcept + { + noexcept_iterator result(*this); + operator--(); + return result; + } + + // LegacyRandomAccessIterator requirements + noexcept_iterator& operator+=(const difference_type& n) noexcept + { + iterator_ += n; + return *this; + } + + friend noexcept_iterator operator+(const noexcept_iterator& i, const difference_type& n) noexcept + { + return {i.iterator_ + n}; + } + + friend noexcept_iterator operator+(const difference_type& n, const noexcept_iterator& i) noexcept + { + return {n + i.iterator_}; + } + + noexcept_iterator& operator-=(const difference_type& n) noexcept + { + iterator_ -=n; + return *this; + } + + noexcept_iterator operator-(const difference_type& n) const noexcept + { + return {iterator_ - n}; + } + + difference_type operator-(const noexcept_iterator& rhs) const noexcept + { + return iterator_ - rhs.iterator_; + } + + reference operator[](const difference_type& n) const noexcept + { + return iterator_[n]; + } + + bool operator<(const noexcept_iterator& rhs) const noexcept + { + return iterator_ < rhs.iterator_; + } + + bool operator>(const noexcept_iterator& rhs) const noexcept + { + return iterator_ > rhs.iterator_; + } + + bool operator>=(const noexcept_iterator& rhs) const noexcept + { + return iterator_ >= rhs.iterator_; + } + + bool operator<=(const noexcept_iterator& rhs) const noexcept + { + return iterator_ <= rhs.iterator_; + } + + private: + mutable Iterator iterator_; +}; + + +template +noexcept_iterator> make_noexcept_iterator(Iterator&& iter) +{ + return noexcept_iterator>{std::forward(iter)}; +} + + +} // end impl + diff --git a/examples/for_each/par.hpp b/examples/for_each/par.hpp new file mode 100644 index 0000000..b9313f9 --- /dev/null +++ b/examples/for_each/par.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include + + +namespace impl +{ + + +template class basic_execution_policy; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex); + + +template +class basic_execution_policy +{ + public: + static constexpr ExecutionRequirement execution_requirement{}; + + Executor executor() const + { + return executor_; + } + + private: + template + friend basic_execution_policy make_basic_execution_policy(const E& ex); + + basic_execution_policy(const Executor& executor) + : executor_(executor) + {} + + Executor executor_; +}; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex) +{ + return basic_execution_policy{ex}; +} + + +template +constexpr bool satisfies_cpp20_on_requirements_v = + std::experimental::execution::can_require_v< + Executor + , ExecutionRequirement + , std::experimental::execution::bulk_t + , std::experimental::execution::oneway_t + , std::experimental::execution::blocking_t::always_t + , std::experimental::execution::mapping_t::thread_t +>; + + +} // end impl + + +class parallel_policy +{ + public: + static constexpr std::experimental::execution::bulk_guarantee_t::parallel_t execution_requirement{}; + + template + >> + impl::basic_execution_policy on(const Executor& ex) const + { + return impl::make_basic_execution_policy(ex); + } +}; + + +constexpr parallel_policy par{}; + + +namespace impl +{ + + +std::experimental::static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; + + +} // end impl + + +template +void for_each_n(const parallel_policy&, Iterator first, Size n, Function f) +{ + return for_each_n(par.on(impl::system_thread_pool.executor()), first, n, f); +} + diff --git a/examples/for_each/query_or.hpp b/examples/for_each/query_or.hpp new file mode 100644 index 0000000..2887450 --- /dev/null +++ b/examples/for_each/query_or.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +template + >> +auto query_or(const T& query_me, const Property& prop, Default&&) +{ + return std::experimental::execution::query(query_me, prop); +} + + +template + >> +Default&& query_or(const T&, const Property&, Default&& result) +{ + return std::forward(result); +} diff --git a/examples/for_each/throwing_iterator.hpp b/examples/for_each/throwing_iterator.hpp new file mode 100644 index 0000000..60f4593 --- /dev/null +++ b/examples/for_each/throwing_iterator.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include + + +template +class throwing_iterator +{ + public: + template + >> + explicit throwing_iterator(OtherIterator&& iterator) + : iterator_(std::forward(iterator)) + {} + + // CopyConstructible requirements + throwing_iterator(const throwing_iterator& other) + : iterator_(other.iterator_) + {} + + // LegacyIterator requirements + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + using iterator_category = typename std::iterator_traits::iterator_category; + + throwing_iterator& operator++() + { + ++iterator_; + return *this; + } + + // LegacyInputIterator requirements + bool operator!=(const throwing_iterator& other) const + { + return iterator_ != other.iterator_; + } + + reference operator*() const + { + throw 13; + return *iterator_; + } + + pointer operator->() const + { + // XXX this doesn't seem right because it wouldn't work if Iterator is a raw pointer + return iterator_.operator->(); + } + + throwing_iterator operator++(int) const + { + throwing_iterator result(*this); + operator++(); + return result; + } + + // LegacyBidirectionalIterator requirements + throwing_iterator& operator--() + { + --iterator_; + return *this; + } + + throwing_iterator operator--(int) const + { + throwing_iterator result(*this); + operator--(); + return result; + } + + // LegacyRandomAccessIterator requirements + throwing_iterator& operator+=(const difference_type& n) + { + iterator_ += n; + return *this; + } + + friend throwing_iterator operator+(const throwing_iterator& i, const difference_type& n) + { + return {i.iterator_ + n}; + } + + friend throwing_iterator operator+(const difference_type& n, const throwing_iterator& i) + { + return {n + i.iterator_}; + } + + throwing_iterator& operator-=(const difference_type& n) + { + iterator_ -=n; + return *this; + } + + throwing_iterator operator-(const difference_type& n) const + { + return {iterator_ - n}; + } + + difference_type operator-(const throwing_iterator& rhs) const + { + return iterator_ - rhs.iterator_; + } + + reference operator[](const difference_type& n) const + { + throw 13; + return iterator_[n]; + } + + bool operator<(const throwing_iterator& rhs) const + { + return iterator_ < rhs.iterator_; + } + + bool operator>(const throwing_iterator& rhs) const + { + return iterator_ > rhs.iterator_; + } + + bool operator>=(const throwing_iterator& rhs) const + { + return iterator_ >= rhs.iterator_; + } + + bool operator<=(const throwing_iterator& rhs) const + { + return iterator_ <= rhs.iterator_; + } + + private: + mutable Iterator iterator_; +}; + +template +throwing_iterator make_throwing_iterator(const Iterator& iter) +{ + return throwing_iterator(iter); +} + diff --git a/examples/reduce/CMakeLists.txt b/examples/reduce/CMakeLists.txt new file mode 100644 index 0000000..9f4f6fd --- /dev/null +++ b/examples/reduce/CMakeLists.txt @@ -0,0 +1 @@ +executors_add_example(reduce_1) diff --git a/examples/reduce/noexcept_function.hpp b/examples/reduce/noexcept_function.hpp new file mode 100644 index 0000000..5833ded --- /dev/null +++ b/examples/reduce/noexcept_function.hpp @@ -0,0 +1,51 @@ +#pragma + +#include +#include + +namespace impl +{ + + +template +class noexcept_function +{ + public: + template + >> + noexcept_function(OtherFunction&& function) noexcept + : function_(std::forward(function)) + {} + + noexcept_function(const noexcept_function& other) noexcept + : function_(other.function_) + {} + + noexcept_function(noexcept_function&& other) noexcept + : function_(std::move(other.function_)) + {} + + ~noexcept_function() noexcept + {} + + template + auto operator()(Args&&... args) const noexcept + { + return function_(std::forward(args)...); + } + + private: + mutable Function function_; +}; + +template +noexcept_function> make_noexcept_function(Function&& f) +{ + return {std::forward(f)}; +} + + +} // end impl + diff --git a/examples/reduce/noexcept_iterator.hpp b/examples/reduce/noexcept_iterator.hpp new file mode 100644 index 0000000..53ecb40 --- /dev/null +++ b/examples/reduce/noexcept_iterator.hpp @@ -0,0 +1,169 @@ +#pragma once + +#include +#include +#include + + +namespace impl +{ + + +template +class noexcept_iterator +{ + public: + template + >> + explicit noexcept_iterator(OtherIterator&& iterator) noexcept + : iterator_(std::forward(iterator)) + {} + + // CopyConstructible requirements + noexcept_iterator(const noexcept_iterator& other) noexcept + : iterator_(other.iterator_) + {} + + // CopyAssignable requirements + noexcept_iterator& operator=(const noexcept_iterator& other) noexcept + { + iterator_ = other.iterator_; + return *this; + } + + // Destructible requirements + ~noexcept_iterator() noexcept + {} + + // Swappable requirements + void swap(const noexcept_iterator& other) noexcept + { + using std::swap; + swap(iterator_, other.iterator_); + } + + // LegacyIterator requirements + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + using iterator_category = typename std::iterator_traits::iterator_category; + + noexcept_iterator& operator++() noexcept + { + ++iterator_; + return *this; + } + + // LegacyInputIterator requirements + bool operator!=(const noexcept_iterator& other) const noexcept + { + return iterator_ != other.iterator_; + } + + reference operator*() const noexcept + { + return *iterator_; + } + + pointer operator->() const noexcept + { + // XXX this doesn't seem right because it wouldn't work if Iterator is a raw pointer + return iterator_.operator->(); + } + + noexcept_iterator operator++(int) noexcept + { + noexcept_iterator result(*this); + operator++(); + return result; + } + + // LegacyBidirectionalIterator requirements + noexcept_iterator& operator--() noexcept + { + --iterator_; + return *this; + } + + noexcept_iterator operator--(int) noexcept + { + noexcept_iterator result(*this); + operator--(); + return result; + } + + // LegacyRandomAccessIterator requirements + noexcept_iterator& operator+=(const difference_type& n) noexcept + { + iterator_ += n; + return *this; + } + + friend noexcept_iterator operator+(const noexcept_iterator& i, const difference_type& n) noexcept + { + return noexcept_iterator{i.iterator_ + n}; + } + + friend noexcept_iterator operator+(const difference_type& n, const noexcept_iterator& i) noexcept + { + return noexcept_iterator{n + i.iterator_}; + } + + noexcept_iterator& operator-=(const difference_type& n) noexcept + { + iterator_ -=n; + return *this; + } + + noexcept_iterator operator-(const difference_type& n) const noexcept + { + return {iterator_ - n}; + } + + difference_type operator-(const noexcept_iterator& rhs) const noexcept + { + return iterator_ - rhs.iterator_; + } + + reference operator[](const difference_type& n) const noexcept + { + return iterator_[n]; + } + + bool operator<(const noexcept_iterator& rhs) const noexcept + { + return iterator_ < rhs.iterator_; + } + + bool operator>(const noexcept_iterator& rhs) const noexcept + { + return iterator_ > rhs.iterator_; + } + + bool operator>=(const noexcept_iterator& rhs) const noexcept + { + return iterator_ >= rhs.iterator_; + } + + bool operator<=(const noexcept_iterator& rhs) const noexcept + { + return iterator_ <= rhs.iterator_; + } + + private: + mutable Iterator iterator_; +}; + + +template +noexcept_iterator> make_noexcept_iterator(Iterator&& iter) +{ + return noexcept_iterator>{std::forward(iter)}; +} + + +} // end impl + diff --git a/examples/reduce/par.hpp b/examples/reduce/par.hpp new file mode 100644 index 0000000..658eb83 --- /dev/null +++ b/examples/reduce/par.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include +#include + + +namespace impl +{ + + +template class basic_execution_policy; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex); + + +template +class basic_execution_policy +{ + public: + static constexpr ExecutionRequirement execution_requirement{}; + + Executor executor() const + { + return executor_; + } + + private: + template + friend basic_execution_policy make_basic_execution_policy(const E& ex); + + basic_execution_policy(const Executor& executor) + : executor_(executor) + {} + + Executor executor_; +}; + + +template +basic_execution_policy make_basic_execution_policy(const E& ex) +{ + return basic_execution_policy{ex}; +} + + +template +constexpr bool satisfies_cpp20_on_requirements_v = + std::experimental::execution::can_require_v< + Executor + , ExecutionRequirement + , std::experimental::execution::bulk_t + , std::experimental::execution::oneway_t + , std::experimental::execution::blocking_t::always_t + , std::experimental::execution::mapping_t::thread_t +>; + + +} // end impl + + +class parallel_policy +{ + public: + static constexpr std::experimental::execution::bulk_guarantee_t::parallel_t execution_requirement{}; + + template + >> + impl::basic_execution_policy on(const Executor& ex) const + { + return impl::make_basic_execution_policy(ex); + } +}; + + +constexpr parallel_policy par{}; + + +namespace impl +{ + + +std::experimental::static_thread_pool system_thread_pool{std::max(1u,std::thread::hardware_concurrency())}; + + +} // end impl + diff --git a/examples/reduce/query_or.hpp b/examples/reduce/query_or.hpp new file mode 100644 index 0000000..2887450 --- /dev/null +++ b/examples/reduce/query_or.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +template + >> +auto query_or(const T& query_me, const Property& prop, Default&&) +{ + return std::experimental::execution::query(query_me, prop); +} + + +template + >> +Default&& query_or(const T&, const Property&, Default&& result) +{ + return std::forward(result); +} diff --git a/examples/reduce/reduce.hpp b/examples/reduce/reduce.hpp new file mode 100644 index 0000000..a968766 --- /dev/null +++ b/examples/reduce/reduce.hpp @@ -0,0 +1,224 @@ +#pragma once + +#include +#include "par.hpp" +#include "query_or.hpp" +#include "noexcept_function.hpp" +#include "noexcept_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include + + +namespace impl +{ + + +template +struct reduce_state +{ + reduce_state(std::size_t num_partial_sums) + : num_outstanding(num_partial_sums), + sums(num_partial_sums) + {} + + // XXX implement a move ctor to allow std::make_shared>(shared_factory()) to work + reduce_state(reduce_state&& other) + : num_outstanding(other.sums.size()), + sums(std::move(other.sums)) + {} + + std::atomic num_outstanding; + + // XXX we should just make this a vector of optionals to avoid requiring DefaultConstructible T + std::vector sums; +}; + + +template +T reduce(std::random_access_iterator_tag, ExecutionPolicy&& policy, RandomAccessIterator first, RandomAccessIterator last, T init, BinaryOperation binary_op) +{ + std::optional result; + + try + { + namespace execution = std::experimental::execution; + + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always); + + // measure the size of the range + size_t n = last - first; + + // choose a number of subranges + size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, size_t(1))); + + // how large should each subrange be? + size_t subrange_size = (n + num_subranges - 1) / num_subranges; + + // deduce the type of each partial sum + using partial_sum_type = std::invoke_result_t::reference, typename std::iterator_traits::reference>; + + // create agents + ex.bulk_execute( + [=,&result,&init](size_t subrange_idx, reduce_state& shared) + { + // find the bounds of the subrange + RandomAccessIterator subrange_begin = first + subrange_idx * subrange_size; + RandomAccessIterator subrange_end = std::min(last, subrange_begin + subrange_size); + + // reduce the subrange to produce a partial sum + shared.sums[subrange_idx] = std::accumulate(subrange_begin + 1, subrange_end, *subrange_begin, binary_op); + + // the final agent reduces partial sums + if(--shared.num_outstanding == 0) + { + result = std::accumulate(shared.sums.begin(), shared.sums.end(), init, binary_op); + } + }, + num_subranges, + [=] + { + return reduce_state(num_subranges); + } + ); + } + catch(...) + { + // sequential fallback + result = std::accumulate(first, last, init, binary_op); + } + + return result.value(); +} + + +template +std::vector partition_into_subranges(ForwardIterator first, Size n, std::size_t num_subranges) +{ + // how large should each subrange be? + std::size_t subrange_size = (n + num_subranges - 1) / num_subranges; + + // store an iterator pointing to the beginning of each subrange + // the final element of this vector points is first + n + std::vector result; + result.reserve(num_subranges + 1); + + Size subrange_idx = 0; + for(Size i = 0; i < n; i += subrange_size, ++subrange_idx) + { + // append the beginning of the subrange + result.push_back(first); + + // are we at the last subrange? + std::size_t distance = (subrange_idx == num_subranges - 1) ? (n - i) : subrange_size; + + std::advance(first, distance); + } + + // finally, add the end of the range + result.push_back(first); + + return result; +} + + +template +struct partitions_and_reduce_state +{ + partitions_and_reduce_state(std::vector&& partitions) + : partitions_begin(std::move(partitions)), + state(partitions_begin.size() - 1) + {} + + partitions_and_reduce_state(partitions_and_reduce_state&& other) + : partitions_begin(std::move(other.partitions_begin)), + state(std::move(other.state)) + {} + + std::vector partitions_begin; + reduce_state state; +}; + + +template +T reduce(std::forward_iterator_tag, ExecutionPolicy&& policy, ForwardIterator first, ForwardIterator last, T init, BinaryOperation binary_op) +{ + std::optional result; + + try + { + namespace execution = std::experimental::execution; + + // enforce requirements + auto ex = execution::require(policy.executor(), policy.execution_requirement, execution::bulk, execution::oneway, execution::blocking.always); + + // measure the size of the range + size_t n = std::distance(first, last); + + // choose a number of subranges + size_t num_subranges = std::min(n, query_or(ex, execution::occupancy, size_t(1))); + + // deduce the type of each partial sum + using partial_sum_type = std::invoke_result_t< + BinaryOperation, + typename std::iterator_traits::reference, + typename std::iterator_traits::reference + >; + + // create agents + ex.bulk_execute( + [=,&result,&init](size_t subrange_idx, partitions_and_reduce_state& shared) + { + // find the bounds of the subrange + ForwardIterator subrange_begin = shared.partitions_begin[subrange_idx]; + ForwardIterator subrange_init = subrange_begin++; + ForwardIterator subrange_end = shared.partitions_begin[subrange_idx + 1]; + + // reduce the subrange to produce a partial sum + shared.state.sums[subrange_idx] = std::accumulate(subrange_begin, subrange_end, *subrange_init, binary_op); + + // the final agent reduces partial sums + if(--shared.state.num_outstanding == 0) + { + result = std::accumulate(shared.state.sums.begin(), shared.state.sums.end(), init, binary_op); + } + }, + num_subranges, + [=] + { + // construct shared state for reduction + return partitions_and_reduce_state(partition_into_subranges(first, n, num_subranges)); + } + ); + } + catch(...) + { + // sequential fallback + result = std::accumulate(first, last, init, binary_op); + } + + return result.value(); +} + + +} // end impl + + +template +T reduce(ExecutionPolicy&& policy, ForwardIterator first, ForwardIterator last, T init, BinaryOperation binary_op) +{ + return impl::reduce(typename std::iterator_traits::iterator_category(), std::forward(policy), impl::make_noexcept_iterator(first), impl::make_noexcept_iterator(last), init, impl::make_noexcept_function(binary_op)); +} + + +template +T reduce(const parallel_policy&, ForwardIterator first, ForwardIterator last, T init, BinaryOperation binary_op) +{ + return ::reduce(par.on(impl::system_thread_pool.executor()), first, last, init, binary_op); +} + diff --git a/examples/reduce/reduce_1.cpp b/examples/reduce/reduce_1.cpp new file mode 100644 index 0000000..f1d2eaa --- /dev/null +++ b/examples/reduce/reduce_1.cpp @@ -0,0 +1,103 @@ +#include "reduce.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + std::iota(vec.begin(), vec.end(), 1); + + int result = reduce(par, vec.begin(), vec.end(), 13, std::plus<>()); + + assert(std::accumulate(vec.begin(), vec.end(), 13) == result); + } + + { + // test with par, list + + std::list lst(10); + std::iota(lst.begin(), lst.end(), 1); + + int result = reduce(par, lst.begin(), lst.end(), 13, std::plus<>()); + + assert(std::accumulate(lst.begin(), lst.end(), 13) == result); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // reduce(par, vec.begin(), vec.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // reduce(par, lst.begin(), lst.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // reduce(par, make_throwing_iterator(vec.begin()), make_throwing_iterator(vec.end()), 13, std::plus<>()); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // reduce(par, make_throwing_iterator(lst.begin()), make_throwing_iterator(lst.end()), 13, std::plus<>()); + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + + diff --git a/examples/reduce/reduce_2.cpp b/examples/reduce/reduce_2.cpp new file mode 100644 index 0000000..08f2b52 --- /dev/null +++ b/examples/reduce/reduce_2.cpp @@ -0,0 +1,116 @@ +#include "reduce.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + std::iota(vec.begin(), vec.end(), 1); + + std::experimental::static_thread_pool pool(4); + + int result = reduce(par.on(pool.executor()), vec.begin(), vec.end(), 13, std::plus<>()); + + assert(std::accumulate(vec.begin(), vec.end(), 13) == result); + } + + { + // test with par, list + + std::list lst(10); + std::iota(lst.begin(), lst.end(), 1); + + std::experimental::static_thread_pool pool(4); + + int result = reduce(par.on(pool.executor()), lst.begin(), lst.end(), 13, std::plus<>()); + + assert(std::accumulate(lst.begin(), lst.end(), 13) == result); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // std::experimental::static_thread_pool pool(4); + + // reduce(par.on(pool.executor()), vec.begin(), vec.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // std::experimental::static_thread_pool pool(4); + + // reduce(par.on(pool.executor()), lst.begin(), lst.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // std::experimental::static_thread_pool pool(4); + + // reduce(par.on(pool.executor()), make_throwing_iterator(vec.begin()), make_throwing_iterator(vec.end()), 13, std::plus<>()); + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // std::experimental::static_thread_pool pool(4); + + // reduce(par.on(pool.executor()), make_throwing_iterator(lst.begin()), make_throwing_iterator(lst.end()), 13, std::plus<>()); + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + + diff --git a/examples/reduce/reduce_3.cpp b/examples/reduce/reduce_3.cpp new file mode 100644 index 0000000..a65e579 --- /dev/null +++ b/examples/reduce/reduce_3.cpp @@ -0,0 +1,164 @@ +#include "reduce.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; + + +class inline_executor +{ + public: + friend bool operator==(const inline_executor&, const inline_executor&) noexcept + { + return true; + } + + friend bool operator!=(const inline_executor&, const inline_executor&) noexcept + { + return false; + } + + inline_executor require(execution::oneway_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void execute(Function f) const noexcept + { + f(); + } +}; + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + std::iota(vec.begin(), vec.end(), 1); + + int result = reduce(par.on(inline_executor()), vec.begin(), vec.end(), 13, std::plus<>()); + + assert(std::accumulate(vec.begin(), vec.end(), 13) == result); + } + + { + // test with par, list + + std::list lst(10); + std::iota(lst.begin(), lst.end(), 1); + + int result = reduce(par.on(inline_executor()), lst.begin(), lst.end(), 13, std::plus<>()); + + assert(std::accumulate(lst.begin(), lst.end(), 13) == result); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(inline_executor()), vec.begin(), vec.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(inline_executor()), lst.begin(), lst.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(inline_executor()), make_throwing_iterator(vec.begin()), make_throwing_iterator(vec.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(inline_executor()), make_throwing_iterator(lst.begin()), make_throwing_iterator(lst.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + + diff --git a/examples/reduce/reduce_4.cpp b/examples/reduce/reduce_4.cpp new file mode 100644 index 0000000..93c4f44 --- /dev/null +++ b/examples/reduce/reduce_4.cpp @@ -0,0 +1,174 @@ +#include "reduce.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; + + +class bulk_inline_executor +{ + public: + friend bool operator==(const bulk_inline_executor&, const bulk_inline_executor&) noexcept + { + return true; + } + + friend bool operator!=(const bulk_inline_executor&, const bulk_inline_executor&) noexcept + { + return false; + } + + bulk_inline_executor require(execution::oneway_t) const + { + return *this; + } + + bulk_inline_executor require(execution::bulk_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void bulk_execute(Function f, size_t n, Factory shared_factory) const noexcept + { + auto shared = shared_factory(); + + for(size_t i = 0; i < n; ++i) + { + f(i, shared); + } + } +}; + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + std::iota(vec.begin(), vec.end(), 1); + + int result = reduce(par.on(bulk_inline_executor()), vec.begin(), vec.end(), 13, std::plus<>()); + + assert(std::accumulate(vec.begin(), vec.end(), 13) == result); + } + + { + // test with par, list + + std::list lst(10); + std::iota(lst.begin(), lst.end(), 1); + + int result = reduce(par.on(bulk_inline_executor()), lst.begin(), lst.end(), 13, std::plus<>()); + + assert(std::accumulate(lst.begin(), lst.end(), 13) == result); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(bulk_inline_executor()), vec.begin(), vec.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(bulk_inline_executor()), lst.begin(), lst.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(bulk_inline_executor()), make_throwing_iterator(vec.begin()), make_throwing_iterator(vec.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(bulk_inline_executor()), make_throwing_iterator(lst.begin()), make_throwing_iterator(lst.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + + diff --git a/examples/reduce/reduce_5.cpp b/examples/reduce/reduce_5.cpp new file mode 100644 index 0000000..d412b40 --- /dev/null +++ b/examples/reduce/reduce_5.cpp @@ -0,0 +1,170 @@ +#include "reduce.hpp" +#include "par.hpp" +#include "throwing_iterator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace execution = std::experimental::execution; + + +// creating EAs with this executor always fails via exception +class throwing_executor +{ + public: + friend bool operator==(const throwing_executor&, const throwing_executor&) noexcept + { + return true; + } + + friend bool operator!=(const throwing_executor&, const throwing_executor&) noexcept + { + return false; + } + + throwing_executor require(execution::oneway_t) const + { + return *this; + } + + throwing_executor require(execution::bulk_t) const + { + return *this; + } + + constexpr static execution::bulk_guarantee_t::parallel_t query(execution::bulk_guarantee_t) + { + return execution::bulk_guarantee.parallel; + } + + constexpr static execution::blocking_t::always_t query(execution::blocking_t) + { + return execution::blocking.always; + } + + constexpr static execution::mapping_t::thread_t query(execution::mapping_t) + { + return execution::mapping.thread; + } + + template + void bulk_execute(Function f, size_t n, Factory shared_factory) const + { + throw -1; + } +}; + +int main() +{ + { + // test with par, vector + + std::vector vec(10); + std::iota(vec.begin(), vec.end(), 1); + + int result = reduce(par.on(throwing_executor()), vec.begin(), vec.end(), 13, std::plus<>()); + + assert(std::accumulate(vec.begin(), vec.end(), 13) == result); + } + + { + // test with par, list + + std::list lst(10); + std::iota(lst.begin(), lst.end(), 1); + + int result = reduce(par.on(throwing_executor()), lst.begin(), lst.end(), 13, std::plus<>()); + + assert(std::accumulate(lst.begin(), lst.end(), 13) == result); + } + + // XXX the following tests correctly call std::terminate upon + // exception in an element access function + // they are disabled to allow the test program to complete normally + + //{ + // // test with par, vector, throwing function + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(throwing_executor()), vec.begin(), vec.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing function + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(throwing_executor()), lst.begin(), lst.end(), 13, [](int x, int y) + // { + // throw 13; + + // return x + y; + // }); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, vector, throwing iterator + + // std::vector vec(10); + // std::iota(vec.begin(), vec.end(), 1); + + // try + // { + // reduce(par.on(throwing_executor()), make_throwing_iterator(vec.begin()), make_throwing_iterator(vec.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + //{ + // // test with par, list, throwing iterator + + // std::list lst(10); + // std::iota(lst.begin(), lst.end(), 1); + + // try + // { + // reduce(par.on(throwing_executor()), make_throwing_iterator(lst.begin()), make_throwing_iterator(lst.end()), 13, std::plus<>()); + // } + // catch(...) {} + + // // this line should never be reached + // assert(false); + //} + + std::cout << "OK" << std::endl; + + return 0; +} + + diff --git a/examples/reduce/throwing_iterator.hpp b/examples/reduce/throwing_iterator.hpp new file mode 100644 index 0000000..b991a47 --- /dev/null +++ b/examples/reduce/throwing_iterator.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include + + +template +class throwing_iterator +{ + public: + template + >> + explicit throwing_iterator(OtherIterator&& iterator) + : iterator_(std::forward(iterator)) + {} + + // CopyConstructible requirements + throwing_iterator(const throwing_iterator& other) + : iterator_(other.iterator_) + {} + + // LegacyIterator requirements + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + using iterator_category = typename std::iterator_traits::iterator_category; + + throwing_iterator& operator++() + { + ++iterator_; + return *this; + } + + // LegacyInputIterator requirements + bool operator!=(const throwing_iterator& other) const + { + return iterator_ != other.iterator_; + } + + reference operator*() const + { + throw 13; + return *iterator_; + } + + pointer operator->() const + { + // XXX this doesn't seem right because it wouldn't work if Iterator is a raw pointer + return iterator_.operator->(); + } + + throwing_iterator operator++(int) + { + throwing_iterator result(*this); + operator++(); + return result; + } + + // LegacyBidirectionalIterator requirements + throwing_iterator& operator--() + { + --iterator_; + return *this; + } + + throwing_iterator operator--(int) + { + throwing_iterator result(*this); + operator--(); + return result; + } + + // LegacyRandomAccessIterator requirements + throwing_iterator& operator+=(const difference_type& n) + { + iterator_ += n; + return *this; + } + + friend throwing_iterator operator+(const throwing_iterator& i, const difference_type& n) + { + return throwing_iterator{i.iterator_ + n}; + } + + friend throwing_iterator operator+(const difference_type& n, const throwing_iterator& i) + { + return {n + i.iterator_}; + } + + throwing_iterator& operator-=(const difference_type& n) + { + iterator_ -=n; + return *this; + } + + throwing_iterator operator-(const difference_type& n) const + { + return {iterator_ - n}; + } + + difference_type operator-(const throwing_iterator& rhs) const + { + return iterator_ - rhs.iterator_; + } + + reference operator[](const difference_type& n) const + { + throw 13; + return iterator_[n]; + } + + bool operator<(const throwing_iterator& rhs) const + { + return iterator_ < rhs.iterator_; + } + + bool operator>(const throwing_iterator& rhs) const + { + return iterator_ > rhs.iterator_; + } + + bool operator>=(const throwing_iterator& rhs) const + { + return iterator_ >= rhs.iterator_; + } + + bool operator<=(const throwing_iterator& rhs) const + { + return iterator_ <= rhs.iterator_; + } + + private: + mutable Iterator iterator_; +}; + +template +throwing_iterator make_throwing_iterator(const Iterator& iter) +{ + return throwing_iterator(iter); +} + diff --git a/include/experimental/bits/occupancy.h b/include/experimental/bits/occupancy.h new file mode 100644 index 0000000..e5e58e0 --- /dev/null +++ b/include/experimental/bits/occupancy.h @@ -0,0 +1,35 @@ +#ifndef STD_EXPERIMENTAL_BITS_OCCUPANCY_H +#define STD_EXPERIMENTAL_BITS_OCCUPANCY_H + +#include + +namespace std { +namespace experimental { +inline namespace executors_v1 { +namespace execution { +namespace occupancy_impl { + +template +struct property_base +{ + static constexpr bool is_requirable = false; + static constexpr bool is_preferable = false; + + using polymorphic_query_result_type = std::size_t; + + template(0)))> + static constexpr Type static_query_v = Executor::query(Derived()); +}; + +} // end namespace occupancy_impl + +struct occupancy_t : occupancy_impl::property_base {}; + +constexpr occupancy_t occupancy; + +} // namespace execution +} // inline namespace executors_v1 +} // namespace experimental +} // namespace std + +#endif // STD_EXPERIMENTAL_BITS_OCCUPANCY_H diff --git a/include/experimental/bits/static_thread_pool.h b/include/experimental/bits/static_thread_pool.h index 1ddfcea..120ad84 100644 --- a/include/experimental/bits/static_thread_pool.h +++ b/include/experimental/bits/static_thread_pool.h @@ -77,6 +77,9 @@ class static_thread_pool ProtoAllocator query(const execution::allocator_t&) const noexcept { return allocator_; } ProtoAllocator query(const execution::allocator_t&) const noexcept { return allocator_; } + // Occupancy. + std::size_t query(const execution::occupancy_t&) const noexcept { return pool_->threads_.size(); } + bool running_in_this_thread() const noexcept { return pool_->running_in_this_thread(); } friend bool operator==(const executor_impl& a, const executor_impl& b) noexcept diff --git a/include/experimental/execution b/include/experimental/execution index 2bcb177..9b82c05 100644 --- a/include/experimental/execution +++ b/include/experimental/execution @@ -72,6 +72,9 @@ struct mapping_t; // Memory allocations. template struct allocator_t; +// Occupancy. +struct occupancy_t; + // Type traits to determine conformance to executor type requirements. template struct is_oneway_executor; template struct is_twoway_executor; @@ -118,6 +121,7 @@ template struct prefer_only; #include #include #include +#include #include #include #include