Take my GSoC code for nested pack traversal over

* See https://naios.github.io/gsoc2017 for details
This commit is contained in:
Denis Blank 2018-01-30 21:50:13 +01:00
parent 17a4e8a8da
commit 0d3a88c4a1
4 changed files with 1463 additions and 0 deletions

View 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__

View 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__

View 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__

View File

@ -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