mirror of
https://github.com/Naios/continuable.git
synced 2025-12-08 01:36:46 +08:00
Take my GSoC code for nested pack traversal over
* See https://naios.github.io/gsoc2017 for details
This commit is contained in:
parent
17a4e8a8da
commit
0d3a88c4a1
54
include/continuable/detail/container-category.hpp
Normal file
54
include/continuable/detail/container-category.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
/*
|
||||
|
||||
/~` _ _ _|_. _ _ |_ | _
|
||||
\_,(_)| | | || ||_|(_||_)|(/_
|
||||
|
||||
https://github.com/Naios/continuable
|
||||
v2.0.0
|
||||
|
||||
Copyright(c) 2015 - 2018 Denis Blank <denis.blank at outlook dot com>
|
||||
|
||||
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 <continuable/continuable-api.hpp>
|
||||
|
||||
namespace cti {
|
||||
namespace detail {
|
||||
namespace traversal {
|
||||
/// A tag for dispatching based on the tuple like
|
||||
/// or container properties of a type.
|
||||
template <bool IsContainer, bool IsTupleLike>
|
||||
struct container_category_tag {};
|
||||
|
||||
/// Deduces to the container_category_tag of the given type T.
|
||||
template <typename T>
|
||||
using container_category_of_t =
|
||||
container_category_tag<false, // TODO traits::is_range<T>::value,
|
||||
false // TODO traits::is_tuple_like<T>::value
|
||||
>;
|
||||
} // namespace traversal
|
||||
} // namespace detail
|
||||
} // namespace cti
|
||||
|
||||
#endif // CONTINUABLE_DETAIL_CONTAINER_CATEGORY_HPP_INCLUDED__
|
||||
550
include/continuable/detail/pack-traversal-async.hpp
Normal file
550
include/continuable/detail/pack-traversal-async.hpp
Normal file
@ -0,0 +1,550 @@
|
||||
|
||||
/*
|
||||
|
||||
/~` _ _ _|_. _ _ |_ | _
|
||||
\_,(_)| | | || ||_|(_||_)|(/_
|
||||
|
||||
https://github.com/Naios/continuable
|
||||
v2.0.0
|
||||
|
||||
Copyright(c) 2015 - 2018 Denis Blank <denis.blank at outlook dot com>
|
||||
|
||||
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 <atomic>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <continuable/continuable-api.hpp>
|
||||
#include <continuable/detail/container-category.hpp>
|
||||
|
||||
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 <typename T>
|
||||
struct async_traverse_in_place_tag {};
|
||||
|
||||
/// Relocates the given pack with the given offset
|
||||
template <std::size_t Offset, typename Pack>
|
||||
struct relocate_index_pack;
|
||||
template <std::size_t Offset, std::size_t... Sequence>
|
||||
struct relocate_index_pack<Offset, pack_c<std::size_t, Sequence...>>
|
||||
: std::common_type<pack_c<std::size_t, (Sequence + Offset)...>> {};
|
||||
|
||||
/// Creates a sequence from begin to end explicitly
|
||||
template <std::size_t Begin, std::size_t End>
|
||||
using explicit_range_sequence_of_t = typename relocate_index_pack<
|
||||
Begin, typename make_index_pack<End - Begin>::type>::type;
|
||||
|
||||
/// Continues the traversal when the object is called
|
||||
template <typename Frame, typename State>
|
||||
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 <typename Frame, typename State>
|
||||
auto make_resume_traversal_callable(Frame&& frame, State&& state)
|
||||
-> resume_traversal_callable<typename std::decay<Frame>::type,
|
||||
typename std::decay<State>::type> {
|
||||
return resume_traversal_callable<typename std::decay<Frame>::type,
|
||||
typename std::decay<State>::type>(
|
||||
std::forward<Frame>(frame), std::forward<State>(state));
|
||||
}
|
||||
|
||||
/// Stores the visitor and the arguments to traverse
|
||||
template <typename Visitor, typename... Args>
|
||||
class async_traversal_frame : public Visitor {
|
||||
tuple<Args...> args_;
|
||||
std::atomic<bool> finished_;
|
||||
|
||||
Visitor& visitor() noexcept {
|
||||
return *static_cast<Visitor*>(this);
|
||||
}
|
||||
|
||||
Visitor const& visitor() const noexcept {
|
||||
return *static_cast<Visitor const*>(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 <typename MapperArg>
|
||||
explicit async_traversal_frame(async_traverse_in_place_tag<Visitor>,
|
||||
MapperArg&& mapper_arg, Args... args)
|
||||
: Visitor(std::forward<MapperArg>(mapper_arg)),
|
||||
args_(util::make_tuple(std::move(args)...)), finished_(false) {
|
||||
}
|
||||
|
||||
/// Returns the arguments of the frame
|
||||
tuple<Args...>& head() noexcept {
|
||||
return args_;
|
||||
}
|
||||
|
||||
/// Calls the visitor with the given element
|
||||
template <typename T>
|
||||
auto traverse(T&& value) -> decltype(util::invoke(std::declval<Visitor&>(),
|
||||
async_traverse_visit_tag{},
|
||||
std::forward<T>(value))) {
|
||||
return util::invoke(visitor(), async_traverse_visit_tag{},
|
||||
std::forward<T>(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 <typename T, typename Hierarchy>
|
||||
void async_continue(T&& value, Hierarchy&& hierarchy) {
|
||||
// Create a self reference
|
||||
boost::intrusive_ptr<async_traversal_frame> 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>(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<T>(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 <typename Target, std::size_t Begin, std::size_t End>
|
||||
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<Begin>(*target_)) {
|
||||
return util::get<Begin>(*target_);
|
||||
}
|
||||
|
||||
template <std::size_t Position>
|
||||
constexpr static_async_range<Target, Position, End> relocate() const
|
||||
noexcept {
|
||||
return static_async_range<Target, Position, End>{target_};
|
||||
}
|
||||
|
||||
constexpr static_async_range<Target, Begin + 1, End> next() const noexcept {
|
||||
return static_async_range<Target, Begin + 1, End>{target_};
|
||||
}
|
||||
|
||||
constexpr bool is_finished() const noexcept {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialization for the end marker which doesn't provide
|
||||
/// a particular element dereference
|
||||
template <typename Target, std::size_t Begin>
|
||||
struct static_async_range<Target, Begin, Begin> {
|
||||
explicit static_async_range(Target*) {
|
||||
}
|
||||
|
||||
constexpr bool is_finished() const noexcept {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns a static range for the given type
|
||||
template <typename T,
|
||||
typename Range = static_async_range<
|
||||
typename std::decay<T>::type, 0U,
|
||||
util::tuple_size<typename std::decay<T>::type>::value>>
|
||||
Range make_static_range(T&& element) {
|
||||
auto pointer = std::addressof(element);
|
||||
return Range{pointer};
|
||||
}
|
||||
|
||||
template <typename Begin, typename Sentinel>
|
||||
struct dynamic_async_range {
|
||||
Begin begin_;
|
||||
Sentinel sentinel_;
|
||||
|
||||
dynamic_async_range& operator++() noexcept {
|
||||
++begin_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator*() const noexcept -> decltype(*std::declval<Begin const&>()) {
|
||||
return *begin_;
|
||||
}
|
||||
|
||||
dynamic_async_range next() const {
|
||||
dynamic_async_range other = *this;
|
||||
++other;
|
||||
return other;
|
||||
}
|
||||
|
||||
bool is_finished() const {
|
||||
return begin_ == sentinel_;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using dynamic_async_range_of_t = dynamic_async_range<
|
||||
typename std::decay<decltype(std::begin(std::declval<T>()))>::type,
|
||||
typename std::decay<decltype(std::end(std::declval<T>()))>::type>;
|
||||
|
||||
/// Returns a dynamic range for the given type
|
||||
template <typename T, typename Range = dynamic_async_range_of_t<T>>
|
||||
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 <typename Frame, typename... Hierarchy>
|
||||
class async_traversal_point {
|
||||
Frame frame_;
|
||||
tuple<Hierarchy...> hierarchy_;
|
||||
bool& detached_;
|
||||
|
||||
public:
|
||||
explicit async_traversal_point(Frame frame, tuple<Hierarchy...> 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 <typename Parent>
|
||||
auto push(Parent&& parent)
|
||||
-> async_traversal_point<Frame, typename std::decay<Parent>::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>(parent)), hierarchy_);
|
||||
|
||||
return async_traversal_point<Frame, typename std::decay<Parent>::type,
|
||||
Hierarchy...>(frame_, std::move(hierarchy),
|
||||
detached_);
|
||||
}
|
||||
|
||||
/// Forks the current traversal point and continues the child
|
||||
/// of the given parent.
|
||||
template <typename Child, typename Parent>
|
||||
void fork(Child&& child, Parent&& parent) {
|
||||
// Push the parent on top of the hierarchy
|
||||
auto point = push(std::forward<Parent>(parent));
|
||||
|
||||
// Continue the traversal with the current element
|
||||
point.async_traverse(std::forward<Child>(child));
|
||||
}
|
||||
|
||||
/// Async traverse a single element, and do nothing.
|
||||
/// This function is matched last.
|
||||
template <typename Matcher, typename Current>
|
||||
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 <typename Current>
|
||||
auto async_traverse_one_impl(container_category_tag<false, false>,
|
||||
Current&& current)
|
||||
/// SFINAE this out if the visitor doesn't accept
|
||||
/// the given element
|
||||
-> typename always_void<
|
||||
decltype(std::declval<Frame>()->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 <bool IsTupleLike, typename Current>
|
||||
void async_traverse_one_impl(container_category_tag<true, IsTupleLike>,
|
||||
Current&& current) {
|
||||
auto range = make_dynamic_async_range(*current);
|
||||
fork(std::move(range), std::forward<Current>(current));
|
||||
}
|
||||
|
||||
/// Async traverse a single element which is a tuple like type only.
|
||||
template <typename Current>
|
||||
void async_traverse_one_impl(container_category_tag<false, true>,
|
||||
Current&& current) {
|
||||
auto range = make_static_range(*current);
|
||||
fork(std::move(range), std::forward<Current>(current));
|
||||
}
|
||||
|
||||
/// Async traverse the current iterator
|
||||
template <typename Current>
|
||||
void async_traverse_one(Current&& current) {
|
||||
using ElementType = typename std::decay<decltype(*current)>::type;
|
||||
return async_traverse_one_impl(container_category_of_t<ElementType>{},
|
||||
std::forward<Current>(current));
|
||||
}
|
||||
|
||||
/// Async traverse the current iterator but don't traverse
|
||||
/// if the control flow was detached.
|
||||
template <typename Current>
|
||||
void async_traverse_one_checked(Current&& current) {
|
||||
if (!is_detached()) {
|
||||
async_traverse_one(std::forward<Current>(current));
|
||||
}
|
||||
}
|
||||
|
||||
template <std::size_t... Sequence, typename Current>
|
||||
void async_traverse_static_async_range(pack_c<std::size_t, Sequence...>,
|
||||
Current&& current) {
|
||||
int dummy[] = {0, ((void)async_traverse_one_checked(
|
||||
current.template relocate<Sequence>()),
|
||||
0)...};
|
||||
(void)dummy;
|
||||
}
|
||||
|
||||
/// Traverse a static range
|
||||
template <typename Target, std::size_t Begin, std::size_t End>
|
||||
void async_traverse(static_async_range<Target, Begin, End> current) {
|
||||
async_traverse_static_async_range(
|
||||
explicit_range_sequence_of_t<Begin, End>{}, current);
|
||||
}
|
||||
|
||||
/// Traverse a dynamic range
|
||||
template <typename Begin, typename Sentinel>
|
||||
void async_traverse(dynamic_async_range<Begin, Sentinel> 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 <typename Frame, typename... Hierarchy>
|
||||
using traversal_point_of_t =
|
||||
async_traversal_point<typename std::decay<Frame>::type,
|
||||
typename std::decay<Hierarchy>::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 <typename Frame, typename Current, typename... Hierarchy>
|
||||
void operator()(Frame&& frame, Current&& current,
|
||||
Hierarchy&&... hierarchy) const {
|
||||
bool detached = false;
|
||||
next(detached, std::forward<Frame>(frame), std::forward<Current>(current),
|
||||
std::forward<Hierarchy>(hierarchy)...);
|
||||
}
|
||||
|
||||
template <typename Frame, typename Current>
|
||||
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<Frame> point(frame, std::make_tuple(), detached);
|
||||
|
||||
point.async_traverse(std::forward<Current>(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 <typename Frame, typename Current, typename Parent,
|
||||
typename... Hierarchy>
|
||||
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<Frame, Parent, Hierarchy...> point(
|
||||
frame, util::make_tuple(parent, hierarchy...), detached);
|
||||
|
||||
point.async_traverse(std::forward<Current>(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>(frame),
|
||||
std::forward<Parent>(parent).next(),
|
||||
std::forward<Hierarchy>(hierarchy)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Frame, typename State>
|
||||
void resume_traversal_callable<Frame, State>::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 <typename Visitor, typename... Args>
|
||||
struct async_traversal_types {
|
||||
/// Deduces to the async traversal frame type of the given
|
||||
/// traversal arguments and mapper
|
||||
using frame_type = async_traversal_frame<typename std::decay<Visitor>::type,
|
||||
typename std::decay<Args>::type...>;
|
||||
|
||||
/// The type of the frame pointer
|
||||
using frame_pointer_type = boost::intrusive_ptr<frame_type>;
|
||||
|
||||
/// The type of the demoted visitor type
|
||||
using visitor_pointer_type = boost::intrusive_ptr<Visitor>;
|
||||
};
|
||||
|
||||
template <typename Visitor, typename VisitorArg, typename... Args>
|
||||
struct async_traversal_types<async_traverse_in_place_tag<Visitor>, VisitorArg,
|
||||
Args...>
|
||||
: async_traversal_types<Visitor, Args...> {};
|
||||
|
||||
/// Traverses the given pack with the given mapper
|
||||
template <typename Visitor, typename... Args,
|
||||
typename types = async_traversal_types<Visitor, Args...>>
|
||||
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>(visitor),
|
||||
std::forward<Args>(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__
|
||||
856
include/continuable/detail/pack-traversal.hpp
Normal file
856
include/continuable/detail/pack-traversal.hpp
Normal file
@ -0,0 +1,856 @@
|
||||
|
||||
/*
|
||||
|
||||
/~` _ _ _|_. _ _ |_ | _
|
||||
\_,(_)| | | || ||_|(_||_)|(/_
|
||||
|
||||
https://github.com/Naios/continuable
|
||||
v2.0.0
|
||||
|
||||
Copyright(c) 2015 - 2018 Denis Blank <denis.blank at outlook dot com>
|
||||
|
||||
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 <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <continuable/continuable-api.hpp>
|
||||
#include <continuable/detail/container-category.hpp>
|
||||
|
||||
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 <typename... T>
|
||||
class spread_box {
|
||||
std::tuple<T...> boxed_;
|
||||
|
||||
public:
|
||||
explicit constexpr spread_box(std::tuple<T...> boxed)
|
||||
: boxed_(std::move(boxed)) {
|
||||
}
|
||||
|
||||
std::tuple<T...> 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 <typename T>
|
||||
struct is_spread : std::false_type {};
|
||||
template <typename... T>
|
||||
struct is_spread<spread_box<T...>> : std::true_type {};
|
||||
|
||||
/// Deduces to a true_type if the given type is an empty
|
||||
/// spread marker
|
||||
template <typename T>
|
||||
struct is_empty_spread : std::false_type {};
|
||||
template <>
|
||||
struct is_empty_spread<spread_box<>> : std::true_type {};
|
||||
|
||||
/// Converts types to the type and spread_box objects to its
|
||||
/// underlying tuple.
|
||||
template <typename T>
|
||||
constexpr T unpack(T&& type) {
|
||||
return std::forward<T>(type);
|
||||
}
|
||||
template <typename... T>
|
||||
constexpr auto unpack(spread_box<T...> type) -> decltype(type.unbox()) {
|
||||
return type.unbox();
|
||||
}
|
||||
|
||||
/// Deduces to the type unpack is returning when called with the
|
||||
/// the given type T.
|
||||
template <typename T>
|
||||
using unpacked_of_t = decltype(unpack(std::declval<T>()));
|
||||
|
||||
/// 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 <typename T>
|
||||
constexpr auto unpack_or_void(T&& type)
|
||||
-> decltype(unpack(std::forward<T>(type))) {
|
||||
return unpack(std::forward<T>(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 <typename T>
|
||||
constexpr std::tuple<T> undecorate(T&& type) {
|
||||
return std::tuple<T>{std::forward<T>(type)};
|
||||
}
|
||||
template <typename... T>
|
||||
constexpr auto undecorate(spread_box<T...> type) -> decltype(type.unbox()) {
|
||||
return type.unbox();
|
||||
}
|
||||
|
||||
/// A callable object which maps its content back to a
|
||||
/// tuple like type.
|
||||
template <typename EmptyType, template <typename...> 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 <typename First, typename... T>
|
||||
constexpr Type<First, T...> operator()(First&& first, T&&... args) const {
|
||||
return Type<First, T...>{std::forward<First>(first),
|
||||
std::forward<T>(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 <template <typename...> class Type = std::tuple>
|
||||
using tupelizer_of_t = tupelizer_base<std::tuple<>, Type>;
|
||||
|
||||
/// A callable object which maps its content back to a tuple like
|
||||
/// type if it wasn't empty. For empty types arguments an empty
|
||||
/// spread box is returned instead. This is useful to propagate
|
||||
/// empty mappings back to the caller.
|
||||
template <template <typename...> class Type = std::tuple>
|
||||
using flat_tupelizer_of_t = tupelizer_base<spread_box<>, Type>;
|
||||
|
||||
/// A callable object which maps its content back to an
|
||||
/// array like type.
|
||||
/// This transform can only be used for (flat) mappings which
|
||||
/// return an empty mapping back to the caller.
|
||||
template <template <typename, std::size_t> class Type>
|
||||
struct flat_arraylizer {
|
||||
/// Deduces to the array type when the array is instantiated
|
||||
/// with the given arguments.
|
||||
template <typename First, typename... Rest>
|
||||
using array_type_of_t =
|
||||
Type<typename std::decay<First>::type, 1 + sizeof...(Rest)>;
|
||||
|
||||
// We overload with one argument here so Clang and GCC don't
|
||||
// have any issues with overloading against zero arguments.
|
||||
template <typename First, typename... T>
|
||||
constexpr auto operator()(First&& first, T&&... args) const
|
||||
-> array_type_of_t<First, T...> {
|
||||
return array_type_of_t<First, T...>{
|
||||
{std::forward<First>(first), std::forward<T>(args)...}};
|
||||
}
|
||||
|
||||
constexpr auto operator()() const noexcept -> decltype(empty_spread()) {
|
||||
return empty_spread();
|
||||
}
|
||||
};
|
||||
|
||||
/// Use the recursive instantiation for a variadic pack which
|
||||
/// may contain spread types
|
||||
template <typename C, typename... T>
|
||||
constexpr auto apply_spread_impl(std::true_type, C&& callable, T&&... args)
|
||||
-> decltype(
|
||||
invoke_fused(std::forward<C>(callable),
|
||||
std::tuple_cat(undecorate(std::forward<T>(args))...))) {
|
||||
return invoke_fused(std::forward<C>(callable),
|
||||
std::tuple_cat(undecorate(std::forward<T>(args))...));
|
||||
}
|
||||
|
||||
/// Use the linear instantiation for variadic packs which don't
|
||||
/// contain spread types.
|
||||
template <typename C, typename... T>
|
||||
constexpr auto apply_spread_impl(std::false_type, C&& callable, T&&... args) ->
|
||||
typename invoke_result<C, T...>::type {
|
||||
return hpx::util::invoke(std::forward<C>(callable), std::forward<T>(args)...);
|
||||
}
|
||||
|
||||
/// Deduces to a true_type if any of the given types marks
|
||||
/// the underlying type to be spread into the current context.
|
||||
template <typename... T>
|
||||
using is_any_spread_t = any_of<is_spread<T>...>;
|
||||
|
||||
template <typename C, typename... T>
|
||||
constexpr auto map_spread(C&& callable, T&&... args)
|
||||
-> decltype(apply_spread_impl(is_any_spread_t<T...>{},
|
||||
std::forward<C>(callable),
|
||||
std::forward<T>(args)...)) {
|
||||
// Check whether any of the args is a detail::flatted_tuple_t,
|
||||
// if not, use the linear called version for better
|
||||
// compilation speed.
|
||||
return apply_spread_impl(is_any_spread_t<T...>{}, std::forward<C>(callable),
|
||||
std::forward<T>(args)...);
|
||||
}
|
||||
|
||||
/// Converts the given variadic arguments into a tuple in a way
|
||||
/// that spread return values are inserted into the current pack.
|
||||
template <typename... T>
|
||||
constexpr auto tupelize(T&&... args)
|
||||
-> decltype(map_spread(tupelizer_of_t<>{}, std::forward<T>(args)...)) {
|
||||
return map_spread(tupelizer_of_t<>{}, std::forward<T>(args)...);
|
||||
}
|
||||
|
||||
/// Converts the given variadic arguments into a tuple in a way
|
||||
/// that spread return values are inserted into the current pack.
|
||||
/// If the arguments were mapped to zero arguments, the empty
|
||||
/// mapping is propagated backwards to the caller.
|
||||
template <template <typename...> class Type, typename... T>
|
||||
constexpr auto flat_tupelize_to(T&&... args)
|
||||
-> decltype(map_spread(flat_tupelizer_of_t<Type>{},
|
||||
std::forward<T>(args)...)) {
|
||||
return map_spread(flat_tupelizer_of_t<Type>{}, std::forward<T>(args)...);
|
||||
}
|
||||
|
||||
/// Converts the given variadic arguments into an array in a way
|
||||
/// that spread return values are inserted into the current pack.
|
||||
/// Through this the size of the array like type might change.
|
||||
/// If the arguments were mapped to zero arguments, the empty
|
||||
/// mapping is propagated backwards to the caller.
|
||||
template <template <typename, std::size_t> class Type, typename... T>
|
||||
constexpr auto flat_arraylize_to(T&&... args)
|
||||
-> decltype(map_spread(flat_arraylizer<Type>{}, std::forward<T>(args)...)) {
|
||||
return map_spread(flat_arraylizer<Type>{}, std::forward<T>(args)...);
|
||||
}
|
||||
|
||||
/// Converts an empty tuple to void
|
||||
template <typename First, typename... Rest>
|
||||
constexpr std::tuple<First, Rest...>
|
||||
voidify_empty_tuple(std::tuple<First, Rest...> val) {
|
||||
return std::move(val);
|
||||
}
|
||||
inline void voidify_empty_tuple(std::tuple<>) noexcept {
|
||||
}
|
||||
|
||||
/// Converts the given variadic arguments into a tuple in a way
|
||||
/// that spread return values are inserted into the current pack.
|
||||
///
|
||||
/// If the returned tuple is empty, voidis returned instead.
|
||||
template <typename... T>
|
||||
constexpr auto tupelize_or_void(T&&... args)
|
||||
-> decltype(voidify_empty_tuple(tupelize(std::forward<T>(args)...))) {
|
||||
return voidify_empty_tuple(tupelize(std::forward<T>(args)...));
|
||||
}
|
||||
} // end namespace spreading
|
||||
|
||||
/// Just traverses the pack with the given callable object,
|
||||
/// no result is returned or preserved.
|
||||
struct strategy_traverse_tag {};
|
||||
/// Remaps the variadic pack with the return values from the mapper.
|
||||
struct strategy_remap_tag {};
|
||||
|
||||
/// Deduces to a true type if the type leads to at least one effective
|
||||
/// call to the mapper.
|
||||
template <typename Mapper, typename T>
|
||||
using is_effective_t = traits::is_invocable<typename Mapper::traversor_type, T>;
|
||||
|
||||
/// Deduces to a true type if any type leads to at least one effective
|
||||
/// call to the mapper.
|
||||
template <typename Mapper, typename... T>
|
||||
struct is_effective_any_of_t;
|
||||
|
||||
template <typename Mapper, typename First, typename... Rest>
|
||||
struct is_effective_any_of_t<Mapper, First, Rest...>
|
||||
: std::conditional<is_effective_t<Mapper, First>::value, std::true_type,
|
||||
is_effective_any_of_t<Mapper, Rest...>>::type {};
|
||||
template <typename Mapper>
|
||||
struct is_effective_any_of_t<Mapper> : std::false_type {};
|
||||
|
||||
/// Provides utilities for remapping the whole content of a
|
||||
/// container like type to the same container holding different types.
|
||||
namespace container_remapping {
|
||||
/// Deduces to a true type if the given parameter T
|
||||
/// has a push_back method that accepts a type of E.
|
||||
template <typename T, typename E, typename = void>
|
||||
struct has_push_back : std::false_type {};
|
||||
template <typename T, typename E>
|
||||
struct has_push_back<
|
||||
T, E, std::void_t<decltype(std::declval<T>().push_back(std::declval<E>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
/// Specialization for a container with a single type T
|
||||
template <typename NewType, template <class> class Base, typename OldType>
|
||||
auto rebind_container(Base<OldType> const & /*container*/) -> Base<NewType> {
|
||||
return Base<NewType>();
|
||||
}
|
||||
|
||||
/// Specialization for a container with a single type T and
|
||||
/// a particular allocator,
|
||||
/// which is preserved across the remap.
|
||||
/// -> We remap the allocator through std::allocator_traits.
|
||||
template <
|
||||
typename NewType, template <class, class> class Base, typename OldType,
|
||||
typename OldAllocator,
|
||||
// Check whether the second argument of the container was
|
||||
// the used allocator.
|
||||
typename std::enable_if<std::uses_allocator<
|
||||
Base<OldType, OldAllocator>, OldAllocator>::value>::type* = nullptr,
|
||||
typename NewAllocator = typename std::allocator_traits<
|
||||
OldAllocator>::template rebind_alloc<NewType>>
|
||||
auto rebind_container(Base<OldType, OldAllocator> const& container)
|
||||
-> Base<NewType, NewAllocator> {
|
||||
// Create a new version of the allocator, that is capable of
|
||||
// allocating the mapped type.
|
||||
return Base<NewType, NewAllocator>(NewAllocator(container.get_allocator()));
|
||||
}
|
||||
|
||||
/// Returns the default iterators of the container in case
|
||||
/// the container was passed as an l-value reference.
|
||||
/// Otherwise move iterators of the container are returned.
|
||||
template <typename C, typename = void>
|
||||
class container_accessor {
|
||||
static_assert(std::is_lvalue_reference<C>::value,
|
||||
"This should be a lvalue reference here!");
|
||||
|
||||
C container_;
|
||||
|
||||
public:
|
||||
container_accessor(C container) : container_(container) {
|
||||
}
|
||||
|
||||
auto begin() -> decltype(container_.begin()) {
|
||||
return container_.begin();
|
||||
}
|
||||
|
||||
auto end() -> decltype(container_.end()) {
|
||||
return container_.end();
|
||||
}
|
||||
};
|
||||
template <typename C>
|
||||
class container_accessor<
|
||||
C, typename std::enable_if<std::is_rvalue_reference<C&&>::value>::type> {
|
||||
C&& container_;
|
||||
|
||||
public:
|
||||
container_accessor(C&& container) : container_(std::move(container)) {
|
||||
}
|
||||
|
||||
auto begin() -> decltype(std::make_move_iterator(container_.begin())) {
|
||||
return std::make_move_iterator(container_.begin());
|
||||
}
|
||||
|
||||
auto end() -> decltype(std::make_move_iterator(container_.end())) {
|
||||
return std::make_move_iterator(container_.end());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
container_accessor<T> container_accessor_of(T&& container) {
|
||||
// Don't use any decay here
|
||||
return container_accessor<T>(std::forward<T>(container));
|
||||
}
|
||||
|
||||
/// Deduces to the type the homogeneous container is containing
|
||||
///
|
||||
/// This alias deduces to the same type on which
|
||||
/// container_accessor<T> is iterating.
|
||||
///
|
||||
/// The basic idea is that we deduce to the type the homogeneous
|
||||
/// container T is carrying as reference while preserving the
|
||||
/// original reference type of the container:
|
||||
/// - If the container was passed as l-value its containing
|
||||
/// values are referenced through l-values.
|
||||
/// - If the container was passed as r-value its containing
|
||||
/// values are referenced through r-values.
|
||||
template <typename Container>
|
||||
using element_of_t = typename std::conditional<
|
||||
std::is_rvalue_reference<Container&&>::value,
|
||||
decltype(std::move(*(std::declval<Container>().begin()))),
|
||||
decltype(*(std::declval<Container>().begin()))>::type;
|
||||
|
||||
/// Removes all qualifier and references from the given type
|
||||
/// if the type is a l-value or r-value reference.
|
||||
template <typename T>
|
||||
using dereferenced_of_t =
|
||||
typename std::conditional<std::is_reference<T>::value,
|
||||
typename std::decay<T>::type, T>::type;
|
||||
|
||||
/// Returns the type which is resulting if the mapping is applied to
|
||||
/// an element in the container.
|
||||
///
|
||||
/// Since standard containers don't allow to be instantiated with
|
||||
/// references we try to construct the container from a copied
|
||||
/// version.
|
||||
template <typename Container, typename Mapping>
|
||||
using mapped_type_from_t = dereferenced_of_t<spreading::unpacked_of_t<
|
||||
typename invoke_result<Mapping, element_of_t<Container>>::type>>;
|
||||
|
||||
/// Deduces to a true_type if the mapping maps to zero elements.
|
||||
template <typename T, typename M>
|
||||
using is_empty_mapped = spreading::is_empty_spread<typename std::decay<
|
||||
typename invoke_result<M, element_of_t<T>>::type>::type>;
|
||||
|
||||
/// We are allowed to reuse the container if we map to the same
|
||||
/// type we are accepting and when we have
|
||||
/// the full ownership of the container.
|
||||
template <typename T, typename M>
|
||||
using can_reuse = std::integral_constant<
|
||||
bool, std::is_same<element_of_t<T>, mapped_type_from_t<T, M>>::value &&
|
||||
std::is_rvalue_reference<T&&>::value>;
|
||||
|
||||
/// Categorizes a mapping of a homogeneous container
|
||||
///
|
||||
/// \tparam IsEmptyMapped Identifies whether the mapping maps to
|
||||
/// to zero arguments.
|
||||
/// \tparam CanReuse Identifies whether the container can be
|
||||
/// re-used through the mapping.
|
||||
template <bool IsEmptyMapped, bool CanReuse>
|
||||
struct container_mapping_tag {};
|
||||
|
||||
/// Categorizes the given container through a container_mapping_tag
|
||||
template <typename T, typename M>
|
||||
using container_mapping_tag_of_t =
|
||||
container_mapping_tag<is_empty_mapped<T, M>::value, can_reuse<T, M>::value>;
|
||||
|
||||
/// We create a new container, which may hold the resulting type
|
||||
template <typename M, typename T>
|
||||
auto remap_container(container_mapping_tag<false, false>, M&& mapper,
|
||||
T&& container)
|
||||
-> decltype(rebind_container<mapped_type_from_t<T, M>>(container)) {
|
||||
static_assert(
|
||||
has_push_back<typename std::decay<T>::type, element_of_t<T>>::value,
|
||||
"Can only remap containers that provide a push_back "
|
||||
"method!");
|
||||
|
||||
// Create the new container, which is capable of holding
|
||||
// the remappped types.
|
||||
auto remapped = rebind_container<mapped_type_from_t<T, M>>(container);
|
||||
|
||||
// We try to reserve the original size from the source
|
||||
// container to the destination container.
|
||||
traits::detail::reserve_if_reservable(remapped, container.size());
|
||||
|
||||
// Perform the actual value remapping from the source to
|
||||
// the destination.
|
||||
// We could have used std::transform for this, however,
|
||||
// I didn't want to pull a whole header for it in.
|
||||
for (auto&& val : container_accessor_of(std::forward<T>(container))) {
|
||||
remapped.push_back(spreading::unpack(
|
||||
std::forward<M>(mapper)(std::forward<decltype(val)>(val))));
|
||||
}
|
||||
|
||||
return remapped; // RVO
|
||||
}
|
||||
|
||||
/// The remapper optimized for the case that we map to the same
|
||||
/// type we accepted such as int -> int.
|
||||
template <typename M, typename T>
|
||||
auto remap_container(container_mapping_tag<false, true>, M&& mapper,
|
||||
T&& container) -> typename std::decay<T>::type {
|
||||
for (auto&& val : container_accessor_of(std::forward<T>(container))) {
|
||||
val = spreading::unpack(
|
||||
std::forward<M>(mapper)(std::forward<decltype(val)>(val)));
|
||||
}
|
||||
return std::forward<T>(container);
|
||||
}
|
||||
|
||||
/// Remap the container to zero arguments
|
||||
template <typename M, typename T>
|
||||
auto remap_container(container_mapping_tag<true, false>, M&& mapper,
|
||||
T&& container) -> decltype(spreading::empty_spread()) {
|
||||
for (auto&& val : container_accessor_of(std::forward<T>(container))) {
|
||||
// Don't save the empty mapping for each invocation
|
||||
// of the mapper.
|
||||
std::forward<M>(mapper)(std::forward<decltype(val)>(val));
|
||||
}
|
||||
// Return one instance of an empty mapping for the container
|
||||
return spreading::empty_spread();
|
||||
}
|
||||
|
||||
/// Remaps the content of the given container with type T,
|
||||
/// to a container of the same type which may contain
|
||||
/// different types.
|
||||
template <typename T, typename M>
|
||||
auto remap(
|
||||
strategy_remap_tag, T&& container, M&& mapper,
|
||||
typename std::enable_if<is_effective_t<M, element_of_t<T>>::value>::type* =
|
||||
nullptr) -> decltype(remap_container(container_mapping_tag_of_t<T, M>{},
|
||||
std::forward<M>(mapper),
|
||||
std::forward<T>(container))) {
|
||||
return remap_container(container_mapping_tag_of_t<T, M>{},
|
||||
std::forward<M>(mapper), std::forward<T>(container));
|
||||
}
|
||||
|
||||
/// Just call the visitor with the content of the container
|
||||
template <typename T, typename M>
|
||||
void remap(
|
||||
strategy_traverse_tag, T&& container, M&& mapper,
|
||||
typename std::enable_if<is_effective_t<M, element_of_t<T>>::value>::type* =
|
||||
nullptr) {
|
||||
for (auto&& element : std::forward<T>(container)) {
|
||||
std::forward<M>(mapper)(std::forward<decltype(element)>(element));
|
||||
}
|
||||
}
|
||||
} // end namespace container_remapping
|
||||
|
||||
/// Provides utilities for remapping the whole content of a
|
||||
/// tuple like type to the same type holding different types.
|
||||
namespace tuple_like_remapping {
|
||||
template <typename Strategy, typename Mapper, typename T,
|
||||
typename Enable = void>
|
||||
struct tuple_like_remapper;
|
||||
|
||||
/// Specialization for std::tuple like types which contain
|
||||
/// an arbitrary amount of heterogenous arguments.
|
||||
template <typename M, template <typename...> class Base, typename... OldArgs>
|
||||
struct tuple_like_remapper<strategy_remap_tag, M, Base<OldArgs...>,
|
||||
// Support for skipping completely untouched types
|
||||
typename std::enable_if<is_effective_any_of_t<
|
||||
M, OldArgs...>::value>::type> {
|
||||
M mapper_;
|
||||
|
||||
template <typename... Args>
|
||||
auto operator()(Args&&... args) -> decltype(spreading::flat_tupelize_to<Base>(
|
||||
std::declval<M>()(std::forward<Args>(args))...)) {
|
||||
return spreading::flat_tupelize_to<Base>(
|
||||
mapper_(std::forward<Args>(args))...);
|
||||
}
|
||||
};
|
||||
template <typename M, template <typename...> class Base, typename... OldArgs>
|
||||
struct tuple_like_remapper<strategy_traverse_tag, M, Base<OldArgs...>,
|
||||
// Support for skipping completely untouched types
|
||||
typename std::enable_if<is_effective_any_of_t<
|
||||
M, OldArgs...>::value>::type> {
|
||||
M mapper_;
|
||||
|
||||
template <typename... Args>
|
||||
auto operator()(Args&&... args) ->
|
||||
typename always_void<typename invoke_result<M, OldArgs>::type...>::type {
|
||||
int dummy[] = {0, ((void)mapper_(std::forward<Args>(args)), 0)...};
|
||||
(void)dummy;
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialization for std::array like types, which contains a
|
||||
/// compile-time known amount of homogeneous types.
|
||||
template <typename M, template <typename, std::size_t> class Base,
|
||||
typename OldArg, std::size_t Size>
|
||||
struct tuple_like_remapper<
|
||||
strategy_remap_tag, M, Base<OldArg, Size>,
|
||||
// Support for skipping completely untouched types
|
||||
typename std::enable_if<is_effective_t<M, OldArg>::value>::type> {
|
||||
M mapper_;
|
||||
|
||||
template <typename... Args>
|
||||
auto operator()(Args&&... args)
|
||||
-> decltype(spreading::flat_arraylize_to<Base>(
|
||||
mapper_(std::forward<Args>(args))...)) {
|
||||
return spreading::flat_arraylize_to<Base>(
|
||||
mapper_(std::forward<Args>(args))...);
|
||||
}
|
||||
};
|
||||
template <typename M, template <typename, std::size_t> class Base,
|
||||
typename OldArg, std::size_t Size>
|
||||
struct tuple_like_remapper<
|
||||
strategy_traverse_tag, M, Base<OldArg, Size>,
|
||||
// Support for skipping completely untouched types
|
||||
typename std::enable_if<is_effective_t<M, OldArg>::value>::type> {
|
||||
M mapper_;
|
||||
|
||||
template <typename... Args>
|
||||
auto operator()(Args&&... args) ->
|
||||
typename invoke_result<typename invoke_result<M, OldArg>::type>::type {
|
||||
int dummy[] = {0, ((void)mapper_(std::forward<Args>(args)), 0)...};
|
||||
(void)dummy;
|
||||
}
|
||||
};
|
||||
|
||||
/// Remaps the content of the given tuple like type T,
|
||||
/// to a container of the same type which may contain
|
||||
/// different types.
|
||||
template <typename Strategy, typename T, typename M>
|
||||
auto remap(Strategy, T&& container, M&& mapper) -> decltype(invoke_fused(
|
||||
std::declval<tuple_like_remapper<Strategy, typename std::decay<M>::type,
|
||||
typename std::decay<T>::type>>(),
|
||||
std::forward<T>(container))) {
|
||||
return invoke_fused(
|
||||
tuple_like_remapper<Strategy, typename std::decay<M>::type,
|
||||
typename std::decay<T>::type>{
|
||||
std::forward<M>(mapper)},
|
||||
std::forward<T>(container));
|
||||
}
|
||||
} // end namespace tuple_like_remapping
|
||||
|
||||
/// Base class for making strategy dependent behaviour available
|
||||
/// to the mapping_helper class.
|
||||
template <typename Strategy>
|
||||
struct mapping_strategy_base {
|
||||
template <typename T>
|
||||
auto may_void(T&& element) const -> typename std::decay<T>::type {
|
||||
return std::forward<T>(element);
|
||||
}
|
||||
};
|
||||
template <>
|
||||
struct mapping_strategy_base<strategy_traverse_tag> {
|
||||
template <typename T>
|
||||
void may_void(T&& /*element*/) const noexcept {
|
||||
}
|
||||
};
|
||||
|
||||
/// A helper class which applies the mapping or
|
||||
/// routes the element through
|
||||
template <typename Strategy, typename M>
|
||||
class mapping_helper : protected mapping_strategy_base<Strategy> {
|
||||
M mapper_;
|
||||
|
||||
class traversal_callable_base {
|
||||
mapping_helper* helper_;
|
||||
|
||||
public:
|
||||
explicit traversal_callable_base(mapping_helper* helper) : helper_(helper) {
|
||||
}
|
||||
|
||||
protected:
|
||||
mapping_helper* get_helper() noexcept {
|
||||
return helper_;
|
||||
}
|
||||
};
|
||||
|
||||
/// A callable object which forwards its invocations
|
||||
/// to mapping_helper::traverse.
|
||||
class traversor : public traversal_callable_base {
|
||||
public:
|
||||
using traversal_callable_base::traversal_callable_base;
|
||||
|
||||
/// SFINAE helper
|
||||
template <typename T>
|
||||
auto operator()(T&& element)
|
||||
-> decltype(std::declval<traversor>().get_helper()->traverse(
|
||||
Strategy{}, std::forward<T>(element)));
|
||||
|
||||
/// An alias to this type
|
||||
using traversor_type = traversor;
|
||||
};
|
||||
|
||||
/// A callable object which forwards its invocations
|
||||
/// to mapping_helper::try_traverse.
|
||||
///
|
||||
/// This callable object will accept any input,
|
||||
/// since elements passed to it are passed through,
|
||||
/// if the provided mapper doesn't accept it.
|
||||
class try_traversor : public traversal_callable_base {
|
||||
public:
|
||||
using traversal_callable_base::traversal_callable_base;
|
||||
|
||||
template <typename T>
|
||||
auto operator()(T&& element)
|
||||
-> decltype(std::declval<try_traversor>().get_helper()->try_traverse(
|
||||
Strategy{}, std::forward<T>(element))) {
|
||||
return this->get_helper()->try_traverse(Strategy{},
|
||||
std::forward<T>(element));
|
||||
}
|
||||
|
||||
/// An alias to the traversor type
|
||||
using traversor_type = traversor;
|
||||
};
|
||||
|
||||
/// Invokes the real mapper with the given element
|
||||
template <typename T>
|
||||
auto invoke_mapper(T&& element) -> decltype(
|
||||
std::declval<mapping_helper>().mapper_(std::forward<T>(element))) {
|
||||
return mapper_(std::forward<T>(element));
|
||||
}
|
||||
|
||||
/// SFINAE helper for plain elements not satisfying the tuple like
|
||||
/// or container requirements.
|
||||
///
|
||||
/// We use the proxy function invoke_mapper here,
|
||||
/// because some compilers (MSVC) tend to instantiate the invocation
|
||||
/// before matching the tag, which leads to build failures.
|
||||
template <typename T>
|
||||
auto match(container_category_tag<false, false>, T&& element) -> decltype(
|
||||
std::declval<mapping_helper>().invoke_mapper(std::forward<T>(element)));
|
||||
|
||||
/// SFINAE helper for elements satisfying the container
|
||||
/// requirements, which are not tuple like.
|
||||
template <typename T>
|
||||
auto match(container_category_tag<true, false>, T&& container)
|
||||
-> decltype(container_remapping::remap(Strategy{},
|
||||
std::forward<T>(container),
|
||||
std::declval<traversor>()));
|
||||
|
||||
/// SFINAE helper for elements which are tuple like and
|
||||
/// that also may satisfy the container requirements
|
||||
template <bool IsContainer, typename T>
|
||||
auto match(container_category_tag<IsContainer, true>, T&& tuple_like)
|
||||
-> decltype(tuple_like_remapping::remap(Strategy{},
|
||||
std::forward<T>(tuple_like),
|
||||
std::declval<traversor>()));
|
||||
|
||||
/// This method implements the functionality for routing
|
||||
/// elements through, that aren't accepted by the mapper.
|
||||
/// Since the real matcher methods below are failing through SFINAE,
|
||||
/// the compiler will try to specialize this function last,
|
||||
/// since it's the least concrete one.
|
||||
/// This works recursively, so we only call the mapper
|
||||
/// with the minimal needed set of accepted arguments.
|
||||
template <typename MatcherTag, typename T>
|
||||
auto try_match(MatcherTag, T&& element) -> decltype(
|
||||
std::declval<mapping_helper>().may_void(std::forward<T>(element))) {
|
||||
return this->may_void(std::forward<T>(element));
|
||||
}
|
||||
|
||||
/// Match plain elements not satisfying the tuple like or
|
||||
/// container requirements.
|
||||
///
|
||||
/// We use the proxy function invoke_mapper here,
|
||||
/// because some compilers (MSVC) tend to instantiate the invocation
|
||||
/// before matching the tag, which leads to build failures.
|
||||
template <typename T>
|
||||
auto try_match(container_category_tag<false, false>, T&& element) -> decltype(
|
||||
std::declval<mapping_helper>().invoke_mapper(std::forward<T>(element))) {
|
||||
// T could be any non container or non tuple like type here,
|
||||
// take int or hpx::future<int> as an example.
|
||||
return invoke_mapper(std::forward<T>(element));
|
||||
}
|
||||
|
||||
/// Match elements satisfying the container requirements,
|
||||
/// which are not tuple like.
|
||||
template <typename T>
|
||||
auto try_match(container_category_tag<true, false>, T&& container)
|
||||
-> decltype(container_remapping::remap(Strategy{},
|
||||
std::forward<T>(container),
|
||||
std::declval<try_traversor>())) {
|
||||
return container_remapping::remap(Strategy{}, std::forward<T>(container),
|
||||
try_traversor{this});
|
||||
}
|
||||
|
||||
/// Match elements which are tuple like and that also may
|
||||
/// satisfy the container requirements
|
||||
/// -> We match tuple like types over container like ones
|
||||
template <bool IsContainer, typename T>
|
||||
auto try_match(container_category_tag<IsContainer, true>, T&& tuple_like)
|
||||
-> decltype(tuple_like_remapping::remap(Strategy{},
|
||||
std::forward<T>(tuple_like),
|
||||
std::declval<try_traversor>())) {
|
||||
return tuple_like_remapping::remap(Strategy{}, std::forward<T>(tuple_like),
|
||||
try_traversor{this});
|
||||
}
|
||||
|
||||
/// Traverses a single element.
|
||||
///
|
||||
/// SFINAE helper: Doesn't allow routing through elements,
|
||||
/// that aren't accepted by the mapper
|
||||
template <typename T>
|
||||
auto traverse(Strategy, T&& element)
|
||||
-> decltype(std::declval<mapping_helper>().match(
|
||||
std::declval<container_category_of_t<typename std::decay<T>::type>>(),
|
||||
std::declval<T>()));
|
||||
|
||||
/// \copybrief traverse
|
||||
template <typename T>
|
||||
auto try_traverse(Strategy, T&& element)
|
||||
-> decltype(std::declval<mapping_helper>().try_match(
|
||||
std::declval<container_category_of_t<typename std::decay<T>::type>>(),
|
||||
std::declval<T>())) {
|
||||
// We use tag dispatching here, to categorize the type T whether
|
||||
// it satisfies the container or tuple like requirements.
|
||||
// Then we can choose the underlying implementation accordingly.
|
||||
return try_match(container_category_of_t<typename std::decay<T>::type>{},
|
||||
std::forward<T>(element));
|
||||
}
|
||||
|
||||
public:
|
||||
explicit mapping_helper(M mapper) : mapper_(std::move(mapper)) {
|
||||
}
|
||||
|
||||
/// \copybrief try_traverse
|
||||
template <typename T>
|
||||
auto init_traverse(strategy_remap_tag, T&& element)
|
||||
-> decltype(spreading::unpack_or_void(
|
||||
std::declval<mapping_helper>().try_traverse(strategy_remap_tag{},
|
||||
std::declval<T>()))) {
|
||||
return spreading::unpack_or_void(
|
||||
try_traverse(strategy_remap_tag{}, std::forward<T>(element)));
|
||||
}
|
||||
template <typename T>
|
||||
void init_traverse(strategy_traverse_tag, T&& element) {
|
||||
try_traverse(strategy_traverse_tag{}, std::forward<T>(element));
|
||||
}
|
||||
|
||||
/// Calls the traversal method for every element in the pack,
|
||||
/// and returns a tuple containing the remapped content.
|
||||
template <typename First, typename Second, typename... T>
|
||||
auto init_traverse(strategy_remap_tag strategy, First&& first,
|
||||
Second&& second, T&&... rest)
|
||||
-> decltype(spreading::tupelize_or_void(
|
||||
std::declval<mapping_helper>().try_traverse(
|
||||
strategy, std::forward<First>(first)),
|
||||
std::declval<mapping_helper>().try_traverse(
|
||||
strategy, std::forward<Second>(second)),
|
||||
std::declval<mapping_helper>().try_traverse(
|
||||
strategy, std::forward<T>(rest))...)) {
|
||||
return spreading::tupelize_or_void(
|
||||
try_traverse(strategy, std::forward<First>(first)),
|
||||
try_traverse(strategy, std::forward<Second>(second)),
|
||||
try_traverse(strategy, std::forward<T>(rest))...);
|
||||
}
|
||||
|
||||
/// Calls the traversal method for every element in the pack,
|
||||
/// without preserving the return values of the mapper.
|
||||
template <typename First, typename Second, typename... T>
|
||||
void init_traverse(strategy_traverse_tag strategy, First&& first,
|
||||
Second&& second, T&&... rest) {
|
||||
try_traverse(strategy, std::forward<First>(first));
|
||||
try_traverse(strategy, std::forward<Second>(second));
|
||||
int dummy[] = {0,
|
||||
((void)try_traverse(strategy, std::forward<T>(rest)), 0)...};
|
||||
(void)dummy;
|
||||
}
|
||||
};
|
||||
|
||||
/// Traverses the given pack with the given mapper and strategy
|
||||
template <typename Strategy, typename Mapper, typename... T>
|
||||
auto apply_pack_transform(Strategy strategy, Mapper&& mapper, T&&... pack)
|
||||
-> decltype(
|
||||
std::declval<
|
||||
mapping_helper<Strategy, typename std::decay<Mapper>::type>>()
|
||||
.init_traverse(strategy, std::forward<T>(pack)...)) {
|
||||
mapping_helper<Strategy, typename std::decay<Mapper>::type> helper(
|
||||
std::forward<Mapper>(mapper));
|
||||
return helper.init_traverse(strategy, std::forward<T>(pack)...);
|
||||
}
|
||||
} // namespace traversal
|
||||
} // namespace detail
|
||||
} // namespace cti
|
||||
|
||||
#endif // CONTINUABLE_DETAIL_PACK_TRAVERSAL_HPP_INCLUDED__
|
||||
@ -12,6 +12,9 @@ set(LIB_SOURCES_DETAIL
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/composition.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/expected.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/hints.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/container-category.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/pack-traversal.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/pack-traversal-async.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/features.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/traits.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/transforms.hpp
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user