From 0d3a88c4a156b800c306d2d88c156dd6800588ad Mon Sep 17 00:00:00 2001 From: Denis Blank Date: Tue, 30 Jan 2018 21:50:13 +0100 Subject: [PATCH] Take my GSoC code for nested pack traversal over * See https://naios.github.io/gsoc2017 for details --- .../continuable/detail/container-category.hpp | 54 ++ .../detail/pack-traversal-async.hpp | 550 +++++++++++ include/continuable/detail/pack-traversal.hpp | 856 ++++++++++++++++++ test/playground/CMakeLists.txt | 3 + 4 files changed, 1463 insertions(+) create mode 100644 include/continuable/detail/container-category.hpp create mode 100644 include/continuable/detail/pack-traversal-async.hpp create mode 100644 include/continuable/detail/pack-traversal.hpp diff --git a/include/continuable/detail/container-category.hpp b/include/continuable/detail/container-category.hpp new file mode 100644 index 0000000..6f7a17a --- /dev/null +++ b/include/continuable/detail/container-category.hpp @@ -0,0 +1,54 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v2.0.0 + + Copyright(c) 2015 - 2018 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_DETAIL_CONTAINER_CATEGORY_HPP_INCLUDED__ +#define CONTINUABLE_DETAIL_CONTAINER_CATEGORY_HPP_INCLUDED__ + +#include + +namespace cti { +namespace detail { +namespace traversal { +/// A tag for dispatching based on the tuple like +/// or container properties of a type. +template +struct container_category_tag {}; + +/// Deduces to the container_category_tag of the given type T. +template +using container_category_of_t = + container_category_tag::value, + false // TODO traits::is_tuple_like::value + >; +} // namespace traversal +} // namespace detail +} // namespace cti + +#endif // CONTINUABLE_DETAIL_CONTAINER_CATEGORY_HPP_INCLUDED__ diff --git a/include/continuable/detail/pack-traversal-async.hpp b/include/continuable/detail/pack-traversal-async.hpp new file mode 100644 index 0000000..6ae6b10 --- /dev/null +++ b/include/continuable/detail/pack-traversal-async.hpp @@ -0,0 +1,550 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v2.0.0 + + Copyright(c) 2015 - 2018 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_DETAIL_PACK_TRAVERSAL_ASYNC_HPP_INCLUDED__ +#define CONTINUABLE_DETAIL_PACK_TRAVERSAL_ASYNC_HPP_INCLUDED__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace cti { +namespace detail { +namespace traversal { +/// A tag which is passed to the `operator()` of the visitor +/// if an element is visited synchronously. +struct async_traverse_visit_tag {}; + +/// A tag which is passed to the `operator()` of the visitor +/// if an element is visited after the traversal was detached. +struct async_traverse_detach_tag {}; + +/// A tag which is passed to the `operator()` of the visitor +/// if the asynchronous pack traversal was finished. +struct async_traverse_complete_tag {}; + +/// A tag to identify that a mapper shall be constructed in-place +/// from the first argument passed. +template +struct async_traverse_in_place_tag {}; + +/// Relocates the given pack with the given offset +template +struct relocate_index_pack; +template +struct relocate_index_pack> + : std::common_type> {}; + +/// Creates a sequence from begin to end explicitly +template +using explicit_range_sequence_of_t = typename relocate_index_pack< + Begin, typename make_index_pack::type>::type; + +/// Continues the traversal when the object is called +template +class resume_traversal_callable { + Frame frame_; + State state_; + +public: + explicit resume_traversal_callable(Frame frame, State state) + : frame_(std::move(frame)), state_(std::move(state)) { + } + + /// The callable operator for resuming + /// the asynchronous pack traversal + void operator()(); +}; + +/// Creates a resume_traversal_callable from the given frame and the +/// given iterator tuple. +template +auto make_resume_traversal_callable(Frame&& frame, State&& state) + -> resume_traversal_callable::type, + typename std::decay::type> { + return resume_traversal_callable::type, + typename std::decay::type>( + std::forward(frame), std::forward(state)); +} + +/// Stores the visitor and the arguments to traverse +template +class async_traversal_frame : public Visitor { + tuple args_; + std::atomic finished_; + + Visitor& visitor() noexcept { + return *static_cast(this); + } + + Visitor const& visitor() const noexcept { + return *static_cast(this); + } + +public: + explicit async_traversal_frame(Visitor visitor, Args... args) + : Visitor(std::move(visitor)), + args_(util::make_tuple(std::move(args)...)), finished_(false) { + } + + /// We require a virtual base + ~async_traversal_frame() override { + HPX_ASSERT(finished_); + } + + template + explicit async_traversal_frame(async_traverse_in_place_tag, + MapperArg&& mapper_arg, Args... args) + : Visitor(std::forward(mapper_arg)), + args_(util::make_tuple(std::move(args)...)), finished_(false) { + } + + /// Returns the arguments of the frame + tuple& head() noexcept { + return args_; + } + + /// Calls the visitor with the given element + template + auto traverse(T&& value) -> decltype(util::invoke(std::declval(), + async_traverse_visit_tag{}, + std::forward(value))) { + return util::invoke(visitor(), async_traverse_visit_tag{}, + std::forward(value)); + } + + /// Calls the visitor with the given element and a continuation + /// which is capable of continuing the asynchronous traversal + /// when it's called later. + template + void async_continue(T&& value, Hierarchy&& hierarchy) { + // Create a self reference + boost::intrusive_ptr self(this); + + // Create a callable object which resumes the current + // traversal when it's called. + auto resumable = make_resume_traversal_callable( + std::move(self), std::forward(hierarchy)); + + // Invoke the visitor with the current value and the + // callable object to resume the control flow. + util::invoke(visitor(), async_traverse_detach_tag{}, std::forward(value), + std::move(resumable)); + } + + /// Calls the visitor with no arguments to signalize that the + /// asynchronous traversal was finished. + void async_complete() { + bool expected = false; + if (finished_.compare_exchange_strong(expected, true)) { + util::invoke(visitor(), async_traverse_complete_tag{}, std::move(args_)); + } + } +}; + +template +struct static_async_range { + Target* target_; + + explicit static_async_range(Target* target) : target_(target) { + } + + static_async_range(static_async_range const& rhs) = default; + static_async_range(static_async_range&& rhs) : target_(rhs.target_) { + rhs.target_ = nullptr; + } + + static_async_range& operator=(static_async_range const& rhs) = default; + static_async_range& operator=(static_async_range&& rhs) { + if (&rhs != this) { + target_ = rhs.target_; + rhs.target_ = nullptr; + } + return *this; + } + + constexpr auto operator*() const noexcept + -> decltype(util::get(*target_)) { + return util::get(*target_); + } + + template + constexpr static_async_range relocate() const + noexcept { + return static_async_range{target_}; + } + + constexpr static_async_range next() const noexcept { + return static_async_range{target_}; + } + + constexpr bool is_finished() const noexcept { + return false; + } +}; + +/// Specialization for the end marker which doesn't provide +/// a particular element dereference +template +struct static_async_range { + explicit static_async_range(Target*) { + } + + constexpr bool is_finished() const noexcept { + return true; + } +}; + +/// Returns a static range for the given type +template ::type, 0U, + util::tuple_size::type>::value>> +Range make_static_range(T&& element) { + auto pointer = std::addressof(element); + return Range{pointer}; +} + +template +struct dynamic_async_range { + Begin begin_; + Sentinel sentinel_; + + dynamic_async_range& operator++() noexcept { + ++begin_; + return *this; + } + + auto operator*() const noexcept -> decltype(*std::declval()) { + return *begin_; + } + + dynamic_async_range next() const { + dynamic_async_range other = *this; + ++other; + return other; + } + + bool is_finished() const { + return begin_ == sentinel_; + } +}; + +template +using dynamic_async_range_of_t = dynamic_async_range< + typename std::decay()))>::type, + typename std::decay()))>::type>; + +/// Returns a dynamic range for the given type +template > +Range make_dynamic_async_range(T&& element) { + return Range{std::begin(element), std::end(element)}; +} + +/// Represents a particular point in a asynchronous traversal hierarchy +template +class async_traversal_point { + Frame frame_; + tuple hierarchy_; + bool& detached_; + +public: + explicit async_traversal_point(Frame frame, tuple hierarchy, + bool& detached) + : frame_(std::move(frame)), hierarchy_(std::move(hierarchy)), + detached_(detached) { + } + + // Abort the current control flow + void detach() noexcept { + HPX_ASSERT(!detached_); + detached_ = true; + } + + /// Returns true when we should abort the current control flow + bool is_detached() const noexcept { + return detached_; + } + + /// Creates a new traversal point which + template + auto push(Parent&& parent) + -> async_traversal_point::type, + Hierarchy...> { + // Create a new hierarchy which contains the + // the parent (the last traversed element). + auto hierarchy = util::tuple_cat( + util::make_tuple(std::forward(parent)), hierarchy_); + + return async_traversal_point::type, + Hierarchy...>(frame_, std::move(hierarchy), + detached_); + } + + /// Forks the current traversal point and continues the child + /// of the given parent. + template + void fork(Child&& child, Parent&& parent) { + // Push the parent on top of the hierarchy + auto point = push(std::forward(parent)); + + // Continue the traversal with the current element + point.async_traverse(std::forward(child)); + } + + /// Async traverse a single element, and do nothing. + /// This function is matched last. + template + void async_traverse_one_impl(Matcher, Current&& current) { + // Do nothing if the visitor doesn't accept the type + } + + /// Async traverse a single element which isn't a container or + /// tuple like type. This function is SFINAEd out if the element + /// isn't accepted by the visitor. + template + auto async_traverse_one_impl(container_category_tag, + Current&& current) + /// SFINAE this out if the visitor doesn't accept + /// the given element + -> typename always_void< + decltype(std::declval()->traverse(*current))>::type { + if (!frame_->traverse(*current)) { + // Store the current call hierarchy into a tuple for + // later re-entrance. + auto hierarchy = + util::tuple_cat(util::make_tuple(current.next()), hierarchy_); + + // First detach the current execution context + detach(); + + // If the traversal method returns false, we detach the + // current execution context and call the visitor with the + // element and a continue callable object again. + + frame_->async_continue(*current, std::move(hierarchy)); + } + } + + /// Async traverse a single element which is a container or + /// tuple like type. + template + void async_traverse_one_impl(container_category_tag, + Current&& current) { + auto range = make_dynamic_async_range(*current); + fork(std::move(range), std::forward(current)); + } + + /// Async traverse a single element which is a tuple like type only. + template + void async_traverse_one_impl(container_category_tag, + Current&& current) { + auto range = make_static_range(*current); + fork(std::move(range), std::forward(current)); + } + + /// Async traverse the current iterator + template + void async_traverse_one(Current&& current) { + using ElementType = typename std::decay::type; + return async_traverse_one_impl(container_category_of_t{}, + std::forward(current)); + } + + /// Async traverse the current iterator but don't traverse + /// if the control flow was detached. + template + void async_traverse_one_checked(Current&& current) { + if (!is_detached()) { + async_traverse_one(std::forward(current)); + } + } + + template + void async_traverse_static_async_range(pack_c, + Current&& current) { + int dummy[] = {0, ((void)async_traverse_one_checked( + current.template relocate()), + 0)...}; + (void)dummy; + } + + /// Traverse a static range + template + void async_traverse(static_async_range current) { + async_traverse_static_async_range( + explicit_range_sequence_of_t{}, current); + } + + /// Traverse a dynamic range + template + void async_traverse(dynamic_async_range range) { + if (!is_detached()) { + for (/**/; !range.is_finished(); ++range) { + async_traverse_one(range); + if (is_detached()) // test before increment + break; + } + } + } +}; + +/// Deduces to the traversal point class of the +/// given frame and hierarchy +template +using traversal_point_of_t = + async_traversal_point::type, + typename std::decay::type...>; + +/// A callable object which is capable of resuming an asynchronous +/// pack traversal. +struct resume_state_callable { + /// Reenter an asynchronous iterator pack and continue + /// its traversal. + template + void operator()(Frame&& frame, Current&& current, + Hierarchy&&... hierarchy) const { + bool detached = false; + next(detached, std::forward(frame), std::forward(current), + std::forward(hierarchy)...); + } + + template + void next(bool& detached, Frame&& frame, Current&& current) const { + // Only process the next element if the current iterator + // hasn't reached its end. + if (!current.is_finished()) { + traversal_point_of_t point(frame, std::make_tuple(), detached); + + point.async_traverse(std::forward(current)); + + // Don't continue the frame when the execution was detached + if (detached) { + return; + } + } + + frame->async_complete(); + } + + /// Reenter an asynchronous iterator pack and continue + /// its traversal. + template + void next(bool& detached, Frame&& frame, Current&& current, Parent&& parent, + Hierarchy&&... hierarchy) const { + // Only process the element if the current iterator + // hasn't reached its end. + if (!current.is_finished()) { + // Don't forward the arguments here, since we still need + // the objects in a valid state later. + traversal_point_of_t point( + frame, util::make_tuple(parent, hierarchy...), detached); + + point.async_traverse(std::forward(current)); + + // Don't continue the frame when the execution was detached + if (detached) { + return; + } + } + + // Pop the top element from the hierarchy, and shift the + // parent element one to the right + next(detached, std::forward(frame), + std::forward(parent).next(), + std::forward(hierarchy)...); + } +}; + +template +void resume_traversal_callable::operator()() { + auto hierarchy = std::tuple_cat(std::make_tuple(frame_), state_); + util::invoke_fused(resume_state_callable{}, std::move(hierarchy)); +} + +/// Gives access to types related to the traversal frame +template +struct async_traversal_types { + /// Deduces to the async traversal frame type of the given + /// traversal arguments and mapper + using frame_type = async_traversal_frame::type, + typename std::decay::type...>; + + /// The type of the frame pointer + using frame_pointer_type = boost::intrusive_ptr; + + /// The type of the demoted visitor type + using visitor_pointer_type = boost::intrusive_ptr; +}; + +template +struct async_traversal_types, VisitorArg, + Args...> + : async_traversal_types {}; + +/// Traverses the given pack with the given mapper +template > +auto apply_pack_transform_async(Visitor&& visitor, Args&&... args) -> + typename types::visitor_pointer_type { + // Create the frame on the heap which stores the arguments + // to traverse asynchronous. + auto frame = [&] { + auto ptr = new typename types::frame_type(std::forward(visitor), + std::forward(args)...); + + // Create an intrusive_ptr from the heap object, don't increase + // reference count (it's already 'one'). + return typename types::frame_pointer_type(ptr, false); + }(); + + // Create a static range for the top level tuple + auto range = make_static_range(frame->head()); + + auto resumer = + make_resume_traversal_callable(frame, std::make_tuple(std::move(range))); + + // Start the asynchronous traversal + resumer(); + return frame; +} +} // namespace traversal +} // namespace detail +} // namespace cti + +#endif // CONTINUABLE_DETAIL_PACK_TRAVERSAL_ASYNC_HPP_INCLUDED__ diff --git a/include/continuable/detail/pack-traversal.hpp b/include/continuable/detail/pack-traversal.hpp new file mode 100644 index 0000000..8814a4a --- /dev/null +++ b/include/continuable/detail/pack-traversal.hpp @@ -0,0 +1,856 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v2.0.0 + + Copyright(c) 2015 - 2018 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_DETAIL_PACK_TRAVERSAL_HPP_INCLUDED__ +#define CONTINUABLE_DETAIL_PACK_TRAVERSAL_HPP_INCLUDED__ + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace cti { +namespace detail { +namespace traversal { +/// Exposes useful facilities for dealing with 1:n mappings +namespace spreading { +/// A struct to mark a tuple to be unpacked into the parent context +template +class spread_box { + std::tuple boxed_; + +public: + explicit constexpr spread_box(std::tuple boxed) + : boxed_(std::move(boxed)) { + } + + std::tuple unbox() { + return std::move(boxed_); + } +}; +template <> +class spread_box<> { +public: + explicit constexpr spread_box() noexcept { + } + explicit constexpr spread_box(std::tuple<>) noexcept { + } + + constexpr std::tuple<> unbox() const noexcept { + return std::tuple<>{}; + } +}; + +/// Returns an empty spread box which represents an empty +/// mapped object. +constexpr spread_box<> empty_spread() noexcept { + return spread_box<>{}; +} + +/// Deduces to a true_type if the given type is a spread marker +template +struct is_spread : std::false_type {}; +template +struct is_spread> : std::true_type {}; + +/// Deduces to a true_type if the given type is an empty +/// spread marker +template +struct is_empty_spread : std::false_type {}; +template <> +struct is_empty_spread> : std::true_type {}; + +/// Converts types to the type and spread_box objects to its +/// underlying tuple. +template +constexpr T unpack(T&& type) { + return std::forward(type); +} +template +constexpr auto unpack(spread_box type) -> decltype(type.unbox()) { + return type.unbox(); +} + +/// Deduces to the type unpack is returning when called with the +/// the given type T. +template +using unpacked_of_t = decltype(unpack(std::declval())); + +/// Converts types to the type and spread_box objects to its +/// underlying tuple. If the type is mapped to zero elements, +/// the return type will be void. +template +constexpr auto unpack_or_void(T&& type) + -> decltype(unpack(std::forward(type))) { + return unpack(std::forward(type)); +} +inline void unpack_or_void(spread_box<>) noexcept { +} + +/// Converts types to the a tuple carrying the single type and +/// spread_box objects to its underlying tuple. +template +constexpr std::tuple undecorate(T&& type) { + return std::tuple{std::forward(type)}; +} +template +constexpr auto undecorate(spread_box type) -> decltype(type.unbox()) { + return type.unbox(); +} + +/// A callable object which maps its content back to a +/// tuple like type. +template class Type> +struct tupelizer_base { + // We overload with one argument here so Clang and GCC don't + // have any issues with overloading against zero arguments. + template + constexpr Type operator()(First&& first, T&&... args) const { + return Type{std::forward(first), + std::forward(args)...}; + } + + // Specifically return the empty object which can be different + // from a tuple. + constexpr EmptyType operator()() const noexcept(noexcept(EmptyType{})) { + return EmptyType{}; + } +}; + +/// A callable object which maps its content back to a tuple. +template