Skip to content

Instantly share code, notes, and snippets.

@Spongman
Last active December 22, 2022 09:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Spongman/e775a7cd44246f861c146e307fea2761 to your computer and use it in GitHub Desktop.
Save Spongman/e775a7cd44246f861c146e307fea2761 to your computer and use it in GitHub Desktop.
/// This is an automatic generated amalgamation of:
/// continuable version 4.0.0 (735697026b72a8f415d3443834cceeda9623780d)
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_HPP_INCLUDED
#define CONTINUABLE_HPP_INCLUDED
/// Declares the continuable library namespace.
///
/// The most important class is cti::continuable_base, that provides the
/// whole functionality for continuation chaining.
///
/// The class cti::continuable_base is created through the
/// cti::make_continuable() function which accepts a callback taking function.
///
/// Also there are following support functions available:
/// - cti::when_all() - connects cti::continuable_base's to an `all` connection.
/// - cti::when_any() - connects cti::continuable_base's to an `any` connection.
/// - cti::when_seq() - connects cti::continuable_base's to a sequence.
namespace cti {}
// #include <continuable/continuable-base.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_BASE_HPP_INCLUDED
#define CONTINUABLE_BASE_HPP_INCLUDED
#include <cassert>
#include <cstddef>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_PRIMITIVES_HPP_INCLUDED
#define CONTINUABLE_PRIMITIVES_HPP_INCLUDED
// #include <continuable/detail/core/types.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TYPES_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TYPES_HPP_INCLUDED
#include <type_traits>
#include <utility>
// #include <continuable/detail/features.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_FEATURES_HPP_INCLUDED
#define CONTINUABLE_DETAIL_FEATURES_HPP_INCLUDED
// Defines CONTINUABLE_WITH_NO_EXCEPTIONS when exception support is disabled
#ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
#if defined(_MSC_VER)
#if !defined(_HAS_EXCEPTIONS) || (_HAS_EXCEPTIONS == 0)
#define CONTINUABLE_WITH_NO_EXCEPTIONS
#endif
#elif defined(__clang__)
#if !(__EXCEPTIONS && __has_feature(cxx_exceptions))
#define CONTINUABLE_WITH_NO_EXCEPTIONS
#endif
#elif defined(__GNUC__)
#if !__EXCEPTIONS
#define CONTINUABLE_WITH_NO_EXCEPTIONS
#endif
#endif
#endif // CONTINUABLE_WITH_NO_EXCEPTIONS
// clang-format off
// Detect if the whole standard is available
#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || \
(__cplusplus >= 201703L)
#define CONTINUABLE_HAS_CXX17_CONSTEXPR_IF
#define CONTINUABLE_HAS_CXX17_DISJUNCTION
#define CONTINUABLE_HAS_CXX17_CONJUNCTION
#define CONTINUABLE_HAS_CXX17_VOID_T
#else
// Generic feature detection based on __has_feature
// and other preprocessor definitions based on:
// http://en.cppreference.com/w/User:D41D8CD98F/feature_testing_macros
#if defined(__has_feature)
#if !defined(CONTINUABLE_HAS_CXX17_CONSTEXPR_IF) && \
__has_feature(cxx_if_constexpr)
#define CONTINUABLE_HAS_CXX17_CONSTEXPR_IF
#endif
#endif
#if !defined(CONTINUABLE_HAS_CXX17_DISJUNCTION) && \
defined(__cpp_lib_experimental_logical_traits) && \
(__cpp_lib_experimental_logical_traits >= 201511)
#define CONTINUABLE_HAS_CXX17_DISJUNCTION
#endif
#if !defined(CONTINUABLE_HAS_CXX17_CONJUNCTION) && \
defined(__cpp_lib_experimental_logical_traits) && \
(__cpp_lib_experimental_logical_traits >= 201511)
#define CONTINUABLE_HAS_CXX17_CONJUNCTION
#endif
#if !defined(CONTINUABLE_HAS_CXX17_VOID_T) && \
defined(__cpp_lib_void_t) && \
(__cpp_lib_void_t >= 201411)
#define CONTINUABLE_HAS_CXX17_VOID_T
#endif
#endif
/// Usually this is enabled by the CMake project
#if !defined(CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE)
/// Define CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE when
/// CONTINUABLE_WITH_EXPERIMENTAL_COROUTINE is defined.
#if defined(CONTINUABLE_WITH_EXPERIMENTAL_COROUTINE)
#define CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
#elif defined(_MSC_VER)
#if _MSC_FULL_VER >= 190023506
#if defined(_RESUMABLE_FUNCTIONS_SUPPORTED)
#define CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
#endif // defined(_RESUMABLE_FUNCTIONS_SUPPORTED)
#endif // _MSC_FULL_VER >= 190023506
#elif defined(__clang__)
#if defined(__cpp_coroutines) && (__cpp_coroutines >= 201707)
#define CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
#endif // defined(__cpp_coroutines) && (__cpp_coroutines >= 201707)
#endif // defined(__clang__)
#endif // !defined(CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE)
/// Define CONTINUABLE_HAS_EXCEPTIONS when exceptions are used
#if !defined(CONTINUABLE_WITH_CUSTOM_ERROR_TYPE) && \
!defined(CONTINUABLE_WITH_NO_EXCEPTIONS)
#define CONTINUABLE_HAS_EXCEPTIONS 1
#else
#undef CONTINUABLE_HAS_EXCEPTIONS
#endif
/// Define CONTINUABLE_HAS_IMMEDIATE_TYPES when either
/// - CONTINUABLE_WITH_IMMEDIATE_TYPES is defined
/// - Building in release mode (NDEBUG is defined)
///
/// Build error messages will become more readable in debug mode while
/// we don't suffer any runtime penalty in release.
#if defined(CONTINUABLE_WITH_IMMEDIATE_TYPES) || defined(NDEBUG)
#define CONTINUABLE_HAS_IMMEDIATE_TYPES 1
#else
#undef CONTINUABLE_HAS_IMMEDIATE_TYPES
#endif
// clang-format on
#endif // CONTINUABLE_DETAIL_FEATURES_HPP_INCLUDED
// #include <continuable/detail/utility/identity.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_IDENTITY_HPP_INCLUDED
#define CONTINUABLE_DETAIL_IDENTITY_HPP_INCLUDED
#include <type_traits>
// #include <continuable/detail/features.hpp>
namespace cti {
namespace detail {
/// A tagging type for wrapping other types
template <typename... T>
struct identity {};
template <typename>
struct is_identity : std::false_type {};
template <typename... Args>
struct is_identity<identity<Args...>> : std::true_type {};
template <typename T>
using identify = std::conditional_t<is_identity<std::decay_t<T>>::value, T,
identity<std::decay_t<T>>>;
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_IDENTITY_HPP_INCLUDED
#ifndef CONTINUABLE_WITH_CUSTOM_ERROR_TYPE
#ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
#include <exception>
#else // CONTINUABLE_WITH_NO_EXCEPTIONS
#include <system_error>
#endif // CONTINUABLE_WITH_NO_EXCEPTIONS
#endif // CONTINUABLE_WITH_CUSTOM_ERROR_TYPE
namespace cti {
template <typename Data, typename Annotation>
class continuable_base;
namespace detail {
/// Contains types used globally across the library
namespace types {
#ifdef CONTINUABLE_WITH_CUSTOM_ERROR_TYPE
using exception_t = CONTINUABLE_WITH_CUSTOM_ERROR_TYPE;
#else // CONTINUABLE_WITH_CUSTOM_ERROR_TYPE
#ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
/// Represents the exception type when exceptions are enabled
using exception_t = std::exception_ptr;
#else // CONTINUABLE_WITH_NO_EXCEPTIONS
/// Represents the error type when exceptions are disabled
using exception_t = std::error_condition;
#endif // CONTINUABLE_WITH_NO_EXCEPTIONS
#endif // CONTINUABLE_WITH_CUSTOM_ERROR_TYPE
/// A tag which is used to execute the continuation inside the current thread
struct this_thread_executor_tag {};
/// Marks a given callable object as transformation
template <typename T>
class plain_tag {
T value_;
public:
template <typename O, std::enable_if_t<std::is_constructible<
T, std::decay_t<O>>::value>* = nullptr>
/* implicit */ plain_tag(O&& value) : value_(std::forward<O>(value)) {
}
explicit plain_tag(T value) : value_(std::move(value)) {
}
T&& consume() && {
return std::move(value_);
}
};
} // namespace types
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TYPES_HPP_INCLUDED
// #include <continuable/detail/utility/identity.hpp>
namespace cti {
/// \defgroup Primitives Primitives
/// provides basic tag types for creating a customized callbacks
/// and continuations.
///
/// For the callback and the continuation `Args...` represents the
/// asynchronous result:
/// ```cpp
/// template<typename... Args>
/// struct continuation {
/// void operator() (callback<Args...>);
/// bool operator() (cti::is_ready_arg_t) const;
/// result<Args...> operator() (cti::unpack_arg_t);
/// };
/// ```
/// ```cpp
/// template<typename... Args>
/// struct callback {
/// void operator() (Args...) &&;
/// void operator() (cti::exception_arg_t, cti::exception_t) &&;
/// };
/// ```
/// \{
/// Represents the tag type that is used to specify the signature hint
/// of a continuable_base or promise_base.
///
/// \since 4.0.0
template <typename... Args>
using signature_arg_t = detail::identity<Args...>;
/// Represents the tag type that is used to query the continuation
/// for whether it resolves the callback instantly with its arguments
/// without having side effects.
///
/// \since 4.0.0
struct is_ready_arg_t {};
/// Represents the tag type that is used to unpack the result of a continuation.
///
/// \attention It's required that the query of is_ready_arg_t returns true,
/// otherwise the behaviour when unpacking is unspecified.
///
/// \since 4.0.0
struct unpack_arg_t {};
/// \copydoc unpack_arg_t
///
/// \deprecated The query_arg_t was deprecated because of
/// its new naming unpack_arg_t.
///
[[deprecated("The dispatch_error_tag was replaced by unpack_arg_t and will "
"be removed in a later major version!")]] //
typedef unpack_arg_t query_arg_t;
/// Represents the tag type that is used to disambiguate the
/// callback operator() in order to take the exception asynchronous chain.
///
/// \note see continuable::next for details.
///
/// \since 4.0.0
struct exception_arg_t {};
/// \copydoc exception_arg_t
///
/// \deprecated The dispatch_error_tag was deprecated in order to move closer
/// to the types specified in the "A Unified Future" proposal
/// especially regarding naming types similar.
///
[[deprecated("The dispatch_error_tag was replaced by exception_arg_t and will "
"be removed in a later major version!")]] //
typedef exception_arg_t dispatch_error_tag;
/// Represents the type that is used as exception type
///
/// By default this type deduces to `std::exception_ptr`.
/// If `CONTINUABLE_WITH_NO_EXCEPTIONS` is defined the type
/// will be a `std::error_condition`.
/// A custom error type may be set through
/// defining `CONTINUABLE_WITH_CUSTOM_ERROR_TYPE`.
///
/// \since 4.0.0
using exception_t = detail::types::exception_t;
/// \copydoc exception_t
///
/// \deprecated The error_type was deprecated in order to move closer
/// to the types specified in the "A Unified Future" proposal
/// especially regarding naming types similar.
///
[[deprecated("The error_type was replaced by exception_t and will "
"be removed in a later major version!")]] //
typedef exception_t error_type;
/// Represents the type that is used to disable the special meaning of types
/// which are returned by a asynchronous result handler.
/// See cti::plain for details.
///
/// \since 4.0.0
template <typename T>
using plain_t = detail::types::plain_tag<T>;
/// \}
} // namespace cti
#endif // CONTINUABLE_PRIMITIVES_HPP_INCLUDED
// #include <continuable/continuable-result.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_RESULT_HPP_INCLUDED
#define CONTINUABLE_RESULT_HPP_INCLUDED
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/detail/utility/result-trait.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_RESULT_TRAIT_HPP_INCLUDED
#define CONTINUABLE_DETAIL_RESULT_TRAIT_HPP_INCLUDED
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/core/annotation.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_ANNOTATION_HPP_INCLUDED
#define CONTINUABLE_DETAIL_ANNOTATION_HPP_INCLUDED
#include <type_traits>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/utility/traits.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TRAITS_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TRAITS_HPP_INCLUDED
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/identity.hpp>
namespace cti {
namespace detail {
namespace traits {
/// Removes all references and qualifiers from the given type T,
/// since std::decay has too much overhead through checking for
/// function pointers and arrays also.
template <typename T>
using unrefcv_t = std::remove_cv_t<std::remove_reference_t<T>>;
namespace detail {
template <typename T, typename... Args>
struct index_of_impl;
template <typename T, typename... Args>
struct index_of_impl<T, T, Args...> : std::integral_constant<std::size_t, 0U> {
};
template <typename T, typename U, typename... Args>
struct index_of_impl<T, U, Args...>
: std::integral_constant<std::size_t,
1 + index_of_impl<T, Args...>::value> {};
} // namespace detail
/// Evaluates to the index of T in the given pack
template <typename T, typename... Args>
using index_of_t = detail::index_of_impl<T, Args...>;
/// Creates a tuple in which r-values gets copied and
/// l-values keep their l-value.
template <typename... T>
auto make_flat_tuple(T&&... args) {
return std::tuple<T...>{std::forward<T>(args)...};
}
#if defined(CONTINUABLE_HAS_CXX17_VOID_T)
using std::void_t;
#else
namespace detail {
// Equivalent to C++17's std::void_t which targets a bug in GCC,
// that prevents correct SFINAE behavior.
// See http://stackoverflow.com/questions/35753920 for details.
template <typename...>
struct deduce_to_void : std::common_type<void> {};
} // namespace detail
/// C++17 like void_t type
template <typename... T>
using void_t = typename detail::deduce_to_void<T...>::type;
#endif // CONTINUABLE_HAS_CXX17_VOID_T
namespace detail_unpack {
using std::get;
/// Calls the given unpacker with the content of the given sequenceable
template <typename U, typename F, std::size_t... I>
constexpr auto unpack_impl(U&& unpacker, F&& first_sequenceable,
std::integer_sequence<std::size_t, I...>)
-> decltype(std::forward<U>(unpacker)(
get<I>(std::forward<F>(first_sequenceable))...)) {
(void)first_sequenceable;
return std::forward<U>(unpacker)(
get<I>(std::forward<F>(first_sequenceable))...);
}
} // namespace detail_unpack
/// Calls the given callable object with the content of the given sequenceable
///
/// \note We can't use std::apply here since this implementation is SFINAE
/// aware and the std version not! This would lead to compilation errors.
template <typename Callable, typename TupleLike,
typename Sequence = std::make_index_sequence<
std::tuple_size<std::decay_t<TupleLike>>::value>>
constexpr auto unpack(Callable&& obj, TupleLike&& tuple_like)
-> decltype(detail_unpack::unpack_impl(std::forward<Callable>(obj),
std::forward<TupleLike>(tuple_like),
Sequence{})) {
return detail_unpack::unpack_impl(std::forward<Callable>(obj),
std::forward<TupleLike>(tuple_like),
Sequence{});
}
namespace detail {
template <typename T, typename Args, typename = traits::void_t<>>
struct is_invokable_impl : std::common_type<std::false_type> {};
template <typename T, typename... Args>
struct is_invokable_impl<
T, std::tuple<Args...>,
void_t<decltype(std::declval<T>()(std::declval<Args>()...))>>
: std::common_type<std::true_type> {};
} // namespace detail
/// Deduces to a std::true_type if the given type is callable with the arguments
/// inside the given tuple.
/// The main reason for implementing it with the detection idiom instead of
/// hana like detection is that MSVC has issues with capturing raw template
/// arguments inside lambda closures.
///
/// ```cpp
/// traits::is_invocable<object, std::tuple<Args...>>
/// ```
template <typename T, typename Args>
using is_invocable_from_tuple =
typename detail::is_invokable_impl<T, Args>::type;
// Checks whether the given callable object is invocable with the given
// arguments. This doesn't take member functions into account!
template <typename T, typename... Args>
using is_invocable = is_invocable_from_tuple<T, std::tuple<Args...>>;
/// Deduces to a std::false_type
template <typename T>
using fail = std::integral_constant<bool, !std::is_same<T, T>::value>;
#ifdef CONTINUABLE_HAS_CXX17_DISJUNCTION
using std::disjunction;
#else
namespace detail {
/// Declares a C++14 polyfill for C++17 std::disjunction.
template <typename Args, typename = void_t<>>
struct disjunction_impl : std::common_type<std::true_type> {};
template <typename... Args>
struct disjunction_impl<identity<Args...>,
void_t<std::enable_if_t<!bool(Args::value)>...>>
: std::common_type<std::false_type> {};
} // namespace detail
template <typename... Args>
using disjunction = typename detail::disjunction_impl<identity<Args...>>::type;
#endif // CONTINUABLE_HAS_CXX17_DISJUNCTION
#ifdef CONTINUABLE_HAS_CXX17_CONJUNCTION
using std::conjunction;
#else
namespace detail {
/// Declares a C++14 polyfill for C++17 std::conjunction.
template <typename Args, typename = void_t<>>
struct conjunction_impl : std::common_type<std::false_type> {};
template <typename... Args>
struct conjunction_impl<identity<Args...>,
void_t<std::enable_if_t<bool(Args::value)>...>>
: std::common_type<std::true_type> {};
} // namespace detail
template <typename... Args>
using conjunction = typename detail::conjunction_impl<identity<Args...>>::type;
#endif // CONTINUABLE_HAS_CXX17_CONJUNCTION
} // namespace traits
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TRAITS_HPP_INCLUDED
namespace cti {
namespace detail {
namespace hints {
/// Extracts the signature we pass to the internal continuable
/// from an argument pack as specified by make_continuable.
///
/// This is the overload taking an arbitrary amount of args
template <typename... HintArgs>
struct from_args : std::common_type<signature_arg_t<HintArgs...>> {};
template <>
struct from_args<void> : std::common_type<signature_arg_t<>> {};
} // namespace hints
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_ANNOTATION_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_UTIL_HPP_INCLUDED
#define CONTINUABLE_DETAIL_UTIL_HPP_INCLUDED
#include <cassert>
#include <cstdlib>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
/// Hint for the compiler that this point should be unreachable
#if defined(_MSC_VER)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE_INTRINSIC() __assume(false)
#elif defined(__GNUC__)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable()
#elif defined(__has_builtin) && __has_builtin(__builtin_unreachable)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE_INTRINSIC() abort()
#endif
/// Causes the application to exit abnormally
#if defined(_MSC_VER)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_TRAP() __debugbreak()
#elif defined(__GNUC__)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_TRAP() __builtin_trap()
#elif defined(__has_builtin) && __has_builtin(__builtin_trap)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_TRAP() __builtin_trap()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_TRAP() *(volatile int*)0x11 = 0
#endif
#ifndef NDEBUG
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE() ::cti::detail::util::unreachable_debug()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE() CTI_DETAIL_UNREACHABLE_INTRINSIC()
#endif
namespace cti {
namespace detail {
/// Utility namespace which provides useful meta-programming support
namespace util {
#ifndef NDEBUG
[[noreturn]] inline void unreachable_debug() {
CTI_DETAIL_TRAP();
std::abort();
}
#endif
/// Helper to trick compilers about that a parameter pack is used
template <typename... T>
constexpr void unused(T&&...) noexcept {
}
namespace detail {
template <typename T, std::size_t... I>
auto forward_except_last_impl(T&& tuple,
std::integer_sequence<std::size_t, I...>) {
(void)tuple;
return std::forward_as_tuple(std::get<I>(std::forward<T>(tuple))...);
}
template <std::size_t Size>
constexpr auto make_decreased_index_sequence(
std::integral_constant<std::size_t, Size>) noexcept {
return std::make_index_sequence<Size - 1>();
}
inline void make_decreased_index_sequence(
std::integral_constant<std::size_t, 0U>) noexcept {
// This function is only instantiated on a compiler error and
// should not be included in valid code.
// See https://github.com/Naios/continuable/issues/21 for details.
CTI_DETAIL_UNREACHABLE();
}
/// Forwards every element in the tuple except the last one
template <typename T>
auto forward_except_last(T&& sequenceable) {
static_assert(
std::tuple_size<std::decay_t<T>>::value > 0U,
"Attempt to remove a parameter from an empty tuple like type! If you see "
"this your compiler could run into possible infinite recursion! Open a "
"ticket at https://github.com/Naios/continuable/issues with a small "
"reproducible example if your compiler doesn't stop!");
constexpr auto size = std::tuple_size<std::decay_t<T>>::value;
constexpr auto sequence = make_decreased_index_sequence(
std::integral_constant<std::size_t, size>{});
return forward_except_last_impl(std::forward<T>(sequenceable), sequence);
}
template <std::size_t Keep>
struct invocation_env {
/// We are able to call the callable with the arguments given in the tuple
template <typename T, typename... Args>
static auto partial_invoke_impl(std::true_type, T&& callable,
std::tuple<Args...> args) {
return traits::unpack(std::forward<T>(callable), std::move(args));
}
/// We were unable to call the callable with the arguments in the tuple.
/// Remove the last argument from the tuple and try it again.
template <typename T, typename... Args>
static auto partial_invoke_impl(std::false_type, T&& callable,
std::tuple<Args...> args) {
// If you are encountering this assertion you tried to attach a callback
// which can't accept the arguments of the continuation.
//
// ```cpp
// continuable<int, int> c;
// std::move(c).then([](std::vector<int> v) { /*...*/ })
// ```
static_assert(
sizeof...(Args) > Keep,
"There is no way to call the given object with these arguments!");
// Remove the last argument from the tuple
auto next = forward_except_last(std::move(args));
// Test whether we are able to call the function with the given tuple
constexpr std::integral_constant<
bool, traits::is_invocable_from_tuple<decltype(callable),
decltype(next)>::value ||
(sizeof...(Args) <= Keep)>
is_callable;
return partial_invoke_impl(is_callable, std::forward<T>(callable),
std::move(next));
}
/// Shortcut - we can call the callable directly
template <typename T, typename... Args>
static auto partial_invoke_impl_shortcut(std::true_type, T&& callable,
Args&&... args) {
return std::forward<T>(callable)(std::forward<Args>(args)...);
}
/// Failed shortcut - we were unable to invoke the callable with the
/// original arguments.
template <typename T, typename... Args>
static auto partial_invoke_impl_shortcut(std::false_type failed, T&& callable,
Args&&... args) {
// Our shortcut failed, convert the arguments into a forwarding tuple
return partial_invoke_impl(
failed, std::forward<T>(callable),
std::forward_as_tuple(std::forward<Args>(args)...));
}
};
} // namespace detail
/// Partially invokes the given callable with the given arguments.
///
/// \note This function will assert statically if there is no way to call the
/// given object with less arguments.
template <std::size_t KeepArgs, typename T, typename... Args>
/*keep this inline*/ inline auto
partial_invoke(std::integral_constant<std::size_t, KeepArgs>, T&& callable,
Args&&... args) {
// Test whether we are able to call the function with the given arguments.
constexpr traits::is_invocable_from_tuple<decltype(callable),
std::tuple<Args...>>
is_invocable;
// The implementation is done in a shortcut way so there are less
// type instantiations needed to call the callable with its full signature.
using env = detail::invocation_env<KeepArgs>;
return env::partial_invoke_impl_shortcut(
is_invocable, std::forward<T>(callable), std::forward<Args>(args)...);
}
/// Invokes the given callable object with the given arguments
template <typename Callable, typename... Args>
constexpr auto invoke(Callable&& callable, Args&&... args) noexcept(
noexcept(std::forward<Callable>(callable)(std::forward<Args>(args)...)))
-> decltype(std::forward<Callable>(callable)(std::forward<Args>(args)...)) {
return std::forward<Callable>(callable)(std::forward<Args>(args)...);
}
/// Invokes the given member function pointer by reference
template <typename T, typename Type, typename Self, typename... Args>
constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
noexcept((std::forward<Self>(self).*member)(std::forward<Args>(args)...)))
-> decltype((std::forward<Self>(self).*
member)(std::forward<Args>(args)...)) {
return (std::forward<Self>(self).*member)(std::forward<Args>(args)...);
}
/// Invokes the given member function pointer by pointer
template <typename T, typename Type, typename Self, typename... Args>
constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
noexcept((std::forward<Self>(self)->*member)(std::forward<Args>(args)...)))
-> decltype(
(std::forward<Self>(self)->*member)(std::forward<Args>(args)...)) {
return (std::forward<Self>(self)->*member)(std::forward<Args>(args)...);
}
/// Returns a constant view on the object
template <typename T>
constexpr std::add_const_t<T>& as_const(T& object) noexcept {
return object;
}
// Class for making child classes non copyable
struct non_copyable {
constexpr non_copyable() = default;
non_copyable(non_copyable const&) = delete;
constexpr non_copyable(non_copyable&&) = default;
non_copyable& operator=(non_copyable const&) = delete;
non_copyable& operator=(non_copyable&&) = default;
};
// Class for making child classes non copyable and movable
struct non_movable {
constexpr non_movable() = default;
non_movable(non_movable const&) = delete;
constexpr non_movable(non_movable&&) = delete;
non_movable& operator=(non_movable const&) = delete;
non_movable& operator=(non_movable&&) = delete;
};
/// This class is responsible for holding an abstract copy- and
/// move-able ownership that is invalidated when the object
/// is moved to another instance.
class ownership {
explicit constexpr ownership(bool acquired, bool frozen)
: acquired_(acquired), frozen_(frozen) {
}
public:
constexpr ownership() : acquired_(true), frozen_(false) {
}
constexpr ownership(ownership const&) = default;
ownership(ownership&& right) noexcept
: acquired_(right.consume()), frozen_(right.is_frozen()) {
}
ownership& operator=(ownership const&) = default;
ownership& operator=(ownership&& right) noexcept {
acquired_ = right.consume();
frozen_ = right.is_frozen();
return *this;
}
// Merges both ownerships together
ownership operator|(ownership const& right) const noexcept {
return ownership(is_acquired() && right.is_acquired(),
is_frozen() || right.is_frozen());
}
constexpr bool is_acquired() const noexcept {
return acquired_;
}
constexpr bool is_frozen() const noexcept {
return frozen_;
}
void release() noexcept {
assert(is_acquired() && "Tried to release the ownership twice!");
acquired_ = false;
}
void freeze(bool enabled = true) noexcept {
assert(is_acquired() && "Tried to freeze a released object!");
frozen_ = enabled;
}
private:
bool consume() noexcept {
if (is_acquired()) {
release();
return true;
}
return false;
}
/// Is true when the object is in a valid state
bool acquired_ : 1;
/// Is true when the automatic invocation on destruction is disabled
bool frozen_ : 1;
};
} // namespace util
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_UTIL_HPP_INCLUDED
namespace cti {
namespace detail {
struct void_arg_t { };
template <typename... T>
struct result_trait;
template <>
struct result_trait<> {
using value_t = void;
using surrogate_t = void_arg_t;
static constexpr surrogate_t wrap() noexcept {
return {};
}
static constexpr void unwrap(surrogate_t) {
}
};
template <typename T>
struct result_trait<T> {
using value_t = T;
using surrogate_t = value_t;
static surrogate_t wrap(T arg) {
return std::move(arg);
}
template <typename R>
static decltype(auto) unwrap(R&& unwrap) {
return std::forward<R>(unwrap);
}
template <std::size_t I, typename Result>
static decltype(auto) get(Result&& result) {
return std::forward<Result>(result).get_value();
}
};
template <typename First, typename Second, typename... Rest>
struct result_trait<First, Second, Rest...> {
using value_t = std::tuple<First, Second, Rest...>;
using surrogate_t = value_t;
static surrogate_t wrap(First first, Second second, Rest... rest) {
return std::make_tuple(std::move(first), std::move(second),
std::move(rest)...);
}
template <typename R>
static decltype(auto) unwrap(R&& unwrap) {
return std::forward<R>(unwrap);
}
template <std::size_t I, typename Result>
static decltype(auto) get(Result&& result) {
return std::get<I>(std::forward<Result>(result).get_value());
}
};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_RESULT_TRAIT_HPP_INCLUDED
// #include <continuable/detail/utility/result-variant.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_RESULT_VARIANT_HPP_INCLUDED
#define CONTINUABLE_DETAIL_RESULT_VARIANT_HPP_INCLUDED
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <memory>
#include <type_traits>
#include <utility>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace container {
enum class result_slot_t : std::uint8_t {
slot_empty,
slot_value,
slot_exception,
};
} // namespace container
struct init_empty_arg_t {};
struct init_result_arg_t {};
struct init_exception_arg_t {};
template <typename T>
class result_variant {
static constexpr bool is_nothrow_destructible = //
std::is_nothrow_destructible<T>::value &&
std::is_nothrow_destructible<exception_t>::value;
static constexpr bool is_nothrow_move_constructible = //
std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_constructible<exception_t>::value;
public:
result_variant() = default;
~result_variant() noexcept(is_nothrow_destructible) {
destroy();
}
explicit result_variant(init_empty_arg_t) noexcept
: slot_(container::result_slot_t::slot_empty) {}
explicit result_variant(init_result_arg_t, T value) noexcept(
std::is_nothrow_destructible<T>::value&&
std::is_nothrow_move_constructible<T>::value)
: slot_(container::result_slot_t::slot_value) {
new (value_ptr()) T(std::move(value));
}
explicit result_variant(init_exception_arg_t, exception_t exception) noexcept(
std::is_nothrow_destructible<exception_t>::value&&
std::is_nothrow_move_constructible<exception_t>::value)
: slot_(container::result_slot_t::slot_exception) {
new (exception_ptr()) exception_t(std::move(exception));
}
result_variant(result_variant const&) = delete;
result_variant& operator=(result_variant const&) = delete;
result_variant(result_variant&& other) noexcept(
is_nothrow_destructible&& is_nothrow_move_constructible)
: slot_(other.slot_) {
switch (other.slot_) {
case container::result_slot_t::slot_value: {
new (value_ptr()) T(std::move(*other.value_ptr()));
break;
}
case container::result_slot_t::slot_exception: {
new (exception_ptr()) exception_t(std::move(*other.exception_ptr()));
break;
}
default: {
break;
}
}
other.destroy();
other.slot_ = container::result_slot_t::slot_empty;
}
result_variant& operator=(result_variant&& other) noexcept(
is_nothrow_destructible&& is_nothrow_move_constructible) {
destroy();
slot_ = other.slot_;
switch (other.slot_) {
case container::result_slot_t::slot_value: {
new (value_ptr()) T(std::move(*other.value_ptr()));
break;
}
case container::result_slot_t::slot_exception: {
new (exception_ptr()) exception_t(std::move(*other.exception_ptr()));
break;
}
default: {
break;
}
}
other.destroy();
other.slot_ = container::result_slot_t::slot_empty;
return *this;
}
void set_empty() {
destroy();
slot_ = container::result_slot_t::slot_empty;
}
void set_value(T value) {
destroy();
new (value_ptr()) T(std::move(value));
slot_ = container::result_slot_t::slot_value;
}
void set_exception(exception_t exception) {
destroy();
new (exception_ptr()) exception_t(std::move(exception));
slot_ = container::result_slot_t::slot_exception;
}
container::result_slot_t slot() const noexcept {
return slot_;
}
bool is_empty() const noexcept {
return slot_ == container::result_slot_t::slot_empty;
}
bool is_value() const noexcept {
return slot_ == container::result_slot_t::slot_value;
}
bool is_exception() const noexcept {
return slot_ == container::result_slot_t::slot_exception;
}
T& get_value() noexcept {
assert(is_value());
return *reinterpret_cast<T*>(&storage_);
}
T const& get_value() const noexcept {
assert(is_value());
return *reinterpret_cast<T const*>(&storage_);
}
exception_t& get_exception() noexcept {
assert(is_exception());
return *reinterpret_cast<exception_t*>(&storage_);
}
exception_t const& get_exception() const noexcept {
assert(is_exception());
return *reinterpret_cast<exception_t const*>(&storage_);
}
private:
constexpr T* value_ptr() noexcept {
return reinterpret_cast<T*>(&storage_);
}
constexpr exception_t* exception_ptr() noexcept {
return reinterpret_cast<exception_t*>(&storage_);
}
void destroy() noexcept(is_nothrow_destructible) {
switch (slot_) {
case container::result_slot_t::slot_value: {
value_ptr()->~T();
break;
}
case container::result_slot_t::slot_exception: {
exception_ptr()->~exception_t();
break;
}
default: {
break;
}
}
}
container::result_slot_t slot_{container::result_slot_t::slot_empty};
std::aligned_storage_t<
(sizeof(T) > sizeof(exception_t) ? sizeof(T) : sizeof(exception_t)),
(alignof(T) > alignof(exception_t) ? alignof(T) : alignof(exception_t))>
storage_;
};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_RESULT_VARIANT_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
namespace cti {
/// \defgroup Result Result
/// provides the \ref result class and corresponding utility functions to work
/// with the result of an asynchronous operation which can possibly yield:
/// - *no result*: If the operation didn't finish
/// - *a value*: If the operation finished successfully
/// - *an exception*: If the operation finished with an exception
/// or was cancelled.
/// \{
/// A tag which represents present void values in result.
///
/// \since 4.0.0
using void_arg_t = detail::void_arg_t;
/// A class which is convertible to any \ref result and that definitely holds no
/// value so the real result gets invalidated when this object is passed to it.
///
/// \since 4.0.0
///
struct empty_result {};
/// A class which is convertible to any \ref result and that definitely holds
/// a default constructed exception which signals the cancellation of the
/// asynchronous control flow.
///
/// \since 4.0.0
///
struct cancellation_result {};
/// A class which is convertible to any result and that holds
/// an exception which is then passed to the converted result object.
///
/// \since 4.0.0
///
class exceptional_result {
exception_t exception_;
public:
exceptional_result() = delete;
exceptional_result(exceptional_result const&) = default;
exceptional_result(exceptional_result&&) = default;
exceptional_result& operator=(exceptional_result const&) = default;
exceptional_result& operator=(exceptional_result&&) = default;
~exceptional_result() = default;
explicit exceptional_result(exception_t exception)
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
: exception_(std::move(exception)) {}
exceptional_result& operator=(exception_t exception) {
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
exception_ = std::move(exception);
return *this;
}
/// Sets an exception
void set_exception(exception_t exception) {
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
exception_ = std::move(exception);
}
/// Returns the contained exception
exception_t& get_exception() & noexcept {
return exception_;
}
/// \copydoc get_exception
exception_t const& get_exception() const& noexcept {
return exception_;
}
/// \copydoc get_exception
exception_t&& get_exception() && noexcept {
return std::move(exception_);
}
};
/// The result class can carry the three kinds of results an asynchronous
/// operation possibly can return, it's implemented in a variant like
/// data structure which is also specialized to hold arbitrary arguments.
///
/// The result can be in the following three states:
/// - *no result*: If the operation didn't finish
/// - *a value*: If the operation finished successfully
/// - *an exception*: If the operation finished with an exception
/// or was cancelled.
///
/// The interface of the result object is similar to the one proposed in
/// the `std::expected` proposal:
/// ```cpp
/// result<std::string> result = make_result("Hello World!");
/// bool(result);
/// result.is_value();
/// result.is_exception();
/// *result; // Same as result.get_value()
/// result.get_value();
/// result.get_exception();
/// ```
///
/// \since 4.0.0
///
template <typename... T>
class result {
using trait_t = detail::result_trait<T...>;
template <typename... Args>
explicit result(detail::init_result_arg_t arg, Args&&... values)
: variant_(arg, trait_t::wrap(std::forward<Args>(values)...)) {}
explicit result(detail::init_exception_arg_t arg, exception_t exception)
: variant_(arg, std::move(exception)) {}
public:
using value_t = typename trait_t::value_t;
using value_placeholder_t = typename trait_t::surrogate_t;
template <typename FirstArg, typename... Args>
explicit result(FirstArg&& first, Args&&... values)
: variant_(detail::init_result_arg_t{},
trait_t::wrap(std::forward<FirstArg>(first),
std::forward<Args>(values)...)) {}
result() = default;
result(result const&) = delete;
result(result&&) = default;
result& operator=(result const&) = delete;
result& operator=(result&&) = default;
~result() = default;
explicit result(exception_t exception)
: variant_(detail::init_exception_arg_t{}, std::move(exception)) {}
/* implicit */ result(empty_result) {}
/* implicit */ result(exceptional_result exceptional_result)
: variant_(detail::init_exception_arg_t{},
std::move(exceptional_result.get_exception())) {}
/* implicit */ result(cancellation_result)
: variant_(detail::init_exception_arg_t{}, exception_t{}) {}
result& operator=(empty_result) {
variant_.set_empty();
return *this;
}
result& operator=(value_placeholder_t value) {
variant_.set_value(std::move(value));
return *this;
}
result& operator=(exceptional_result exception) {
variant_.set_exception(std::move(exception.get_exception()));
return *this;
}
result& operator=(cancellation_result) {
variant_.set_exception({});
return *this;
}
/// Set the result to an empty state
void set_empty() {
variant_.set_empty();
}
/// Set the result to a the state which holds the corresponding value
void set_value(T... values) {
variant_.set_value(trait_t::wrap(std::move(values)...));
}
/// Set the result into a state which holds the corresponding exception
void set_exception(exception_t exception) {
variant_.set_exception(std::move(exception));
}
/// Set the result into a state which holds the cancellation token
void set_canceled() {
variant_.set_exception(exception_t{});
}
/// Returns true if the state of the result is empty
bool is_empty() const noexcept {
return variant_.is_empty();
}
/// Returns true if the state of the result holds the result
bool is_value() const noexcept {
return variant_.is_value();
}
/// Returns true if the state of the result holds a present exception
bool is_exception() const noexcept {
return variant_.is_exception();
}
/// \copydoc is_value
explicit constexpr operator bool() const noexcept {
return is_value();
}
/// Returns the values of the result, if the result doesn't hold the value
/// the behaviour is undefined but will assert in debug mode.
decltype(auto) get_value() & noexcept {
return trait_t::unwrap(variant_.get_value());
}
///\copydoc get_value
decltype(auto) get_value() const& noexcept {
return trait_t::unwrap(variant_.get_value());
}
///\copydoc get_value
decltype(auto) get_value() && noexcept {
return trait_t::unwrap(std::move(variant_.get_value()));
}
///\copydoc get_value
decltype(auto) operator*() & noexcept {
return get_value();
}
///\copydoc get_value
decltype(auto) operator*() const& noexcept {
return get_value();
}
///\copydoc get_value
decltype(auto) operator*() && noexcept {
return std::move(variant_.get_value());
}
/// Returns the exception of the result, if the result doesn't hold an
/// exception the behaviour is undefined but will assert in debug mode.
exception_t& get_exception() & noexcept {
return variant_.get_exception();
}
/// \copydoc get_exception
exception_t const& get_exception() const& noexcept {
return variant_.get_exception();
}
/// \copydoc get_exception
exception_t&& get_exception() && noexcept {
return std::move(variant_.get_exception());
}
/// Creates a present result from the given values
static result from(T... values) {
return result{detail::init_result_arg_t{}, std::move(values)...};
}
/// Creates a present result from the given exception
static result from(exception_arg_t, exception_t exception) {
return result{detail::init_exception_arg_t{}, std::move(exception)};
}
/// Creates an empty result
static result empty() {
return result{empty_result{}};
}
private:
detail::result_variant<value_placeholder_t> variant_;
};
/// Returns the value at position I of the given result
template <std::size_t I, typename... T>
decltype(auto) get(result<T...>& result) {
return detail::result_trait<T...>::template get<I>(result);
}
/// \copydoc get
template <std::size_t I, typename... T>
decltype(auto) get(result<T...> const& result) {
return detail::result_trait<T...>::template get<I>(result);
}
/// \copydoc get
template <std::size_t I, typename... T>
decltype(auto) get(result<T...>&& result) {
return detail::result_trait<T...>::template get<I>(std::move(result));
}
/// Creates a present result from the given values.
///
/// This could be used to pass the result of the next handler to the same
/// asynchronous path it came from as shown below:
/// ```cpp
/// make_ready_continuable().next([&](auto&&... args) {
/// result<> captured = make_result(std::forward<decltype(args)>(args)...);
/// return shutdown().then([captured = std::move(captured)]() mutable {
/// return std::move(captured);
/// });
/// });
/// ```
///
/// \since 4.0.0
template <typename... T,
typename Result = result<detail::traits::unrefcv_t<T>...>>
Result make_result(T&&... values) {
return Result::from(std::forward<T>(values)...);
}
/// Creates an exceptional_result from the given exception.
///
/// \copydetails make_result
///
/// \since 4.0.0
inline exceptional_result make_result(exception_arg_t, exception_t exception) {
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
return exceptional_result{std::move(exception)};
}
/// \}
} // namespace cti
namespace std {
// The GCC standard library defines tuple_size as class and struct which
// triggers a warning here.
#if defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmismatched-tags"
#endif
template <typename... Args>
struct tuple_size<cti::result<Args...>>
: std::integral_constant<size_t, sizeof...(Args)> {};
template <std::size_t I, typename... Args>
struct tuple_element<I, cti::result<Args...>>
: tuple_element<I, tuple<Args...>> {};
#if defined(__clang__)
# pragma GCC diagnostic pop
#endif
} // namespace std
#endif // CONTINUABLE_RESULT_HPP_INCLUDED
// #include <continuable/detail/connection/connection-all.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_CONNECTION_ALL_HPP_INCLUDED
#define CONTINUABLE_DETAIL_CONNECTION_ALL_HPP_INCLUDED
#include <atomic>
#include <memory>
#include <mutex>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/detail/connection/connection-aggregated.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_CONNECTION_REMAPPING_HPP_INCLUDED
#define CONTINUABLE_DETAIL_CONNECTION_REMAPPING_HPP_INCLUDED
#include <cassert>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-result.hpp>
// #include <continuable/continuable-traverse.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TRAVERSE_HPP_INCLUDED
#define CONTINUABLE_TRAVERSE_HPP_INCLUDED
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/traversal/traverse.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TRAVERSE_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TRAVERSE_HPP_INCLUDED
#include <cstddef>
#include <iterator>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/traversal/container-category.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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 <tuple>
#include <type_traits>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace traversal {
/// Deduces to a true type if the given parameter T
/// has a begin() and end() method.
// TODO Find out whether we should use std::begin and std::end instead, which
// could cause issues with plain arrays.
template <typename T, typename = void>
struct is_range : std::false_type {};
template <typename T>
struct is_range<T, traits::void_t<decltype(std::declval<T>().begin() ==
std::declval<T>().end())>>
: std::true_type {};
/// Deduces to a true type if the given parameter T
/// is accessible through std::tuple_size.
template <typename T, typename = void>
struct is_tuple_like : std::false_type {};
template <typename T>
struct is_tuple_like<T, traits::void_t<decltype(std::tuple_size<T>::value)>>
: std::true_type {};
/// A tag for dispatching based on the tuple like
/// or container properties of a type.
///
/// This type deduces to a true_type if it has any category.
template <bool IsContainer, bool IsTupleLike>
struct container_category_tag
: std::integral_constant<bool, IsContainer || IsTupleLike> {};
/// Deduces to the container_category_tag of the given type T.
template <typename T>
using container_category_of_t =
container_category_tag<is_range<T>::value, is_tuple_like<T>::value>;
} // namespace traversal
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONTAINER_CATEGORY_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace traversal {
/// Exposes useful facilities for dealing with 1:n mappings
namespace spreading {
/// \cond false
/// 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<std::decay_t<First>, 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(
traits::unpack(std::forward<C>(callable),
std::tuple_cat(undecorate(std::forward<T>(args))...))) {
return traits::unpack(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)
-> decltype(std::forward<C>(callable)(std::forward<T>(args)...)) {
return 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 = traits::disjunction<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 decltype(auto) tupelize_or_void(T&&... args) {
return voidify_empty_tuple(tupelize(std::forward<T>(args)...));
}
/// \endcond
} // 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>;
// TODO find out whether the linear compile-time instantiation is faster:
// template <typename Mapper, typename... T>
// struct is_effective_any_of_t
// : traits::disjunction<is_effective_t<Mapper, T>...> {};
// template <typename Mapper>
// struct is_effective_any_of_t<Mapper> : std::false_type {};
/// 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,
traits::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,
std::decay_t<T>, 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<decltype(
std::declval<Mapping>()(std::declval<element_of_t<Container>>()))>>;
/// 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<
std::decay_t<decltype(std::declval<M>()(std::declval<element_of_t<T>>()))>>;
/// 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>;
/// Deduces to a true type if the given parameter T supports a `reserve` method
template <typename From, typename To, typename = void>
struct is_reservable_from : std::false_type {};
template <typename From, typename To>
struct is_reservable_from<From, To,
traits::void_t<decltype(std::declval<To>().reserve(
std::declval<From>().size()))>> : std::true_type {
};
template <typename Dest, typename Source>
void reserve_if(std::true_type, Dest&& dest, Source&& source) {
// Reserve the mapped size
dest.reserve(source.size());
}
template <typename Dest, typename Source>
void reserve_if(std::false_type, Dest&&, Source&&) noexcept {
// We do nothing here, since the container doesn't support reserving
}
/// 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<std::decay_t<T>, 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 inside the destination container.
reserve_if(
is_reservable_from<std::decay_t<T>, std::decay_t<decltype(remapped)>>{},
remapped, container);
// 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) -> std::decay_t<T> {
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();
}
/// \cond false
/// 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));
}
/// \endcond
/// 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 : container_accessor_of(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) -> traits::void_t<
decltype(std::declval<M>()(std::declval<OldArgs>()))...> {
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)
-> decltype((std::declval<M>()(std::declval<OldArg>()))()) {
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(traits::unpack(
std::declval<
tuple_like_remapper<Strategy, std::decay_t<M>, std::decay_t<T>>>(),
std::forward<T>(container))) {
return traits::unpack(
tuple_like_remapper<Strategy, std::decay_t<M>, std::decay_t<T>>{
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 -> std::decay_t<T> {
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<std::decay_t<T>>>(),
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<std::decay_t<T>>>(),
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<std::decay_t<T>>{},
std::forward<T>(element));
}
public:
explicit mapping_helper(M mapper) : mapper_(std::move(mapper)) {
}
/// \copybrief try_traverse
template <typename T>
decltype(auto) init_traverse(strategy_remap_tag, T&& element) {
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>
decltype(auto) init_traverse(strategy_remap_tag strategy, First&& first,
Second&& second, 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>
decltype(auto) transform(Strategy strategy, Mapper&& mapper, T&&... pack) {
mapping_helper<Strategy, std::decay_t<Mapper>> helper(
std::forward<Mapper>(mapper));
return helper.init_traverse(strategy, std::forward<T>(pack)...);
}
} // namespace traversal
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TRAVERSE_HPP_INCLUDED
namespace cti {
/// \defgroup Traversal Traversal
/// provides functions to traverse and remap nested packs.
/// \{
/// Maps the pack with the given mapper.
///
/// This function tries to visit all plain elements which may be wrapped in:
/// - homogeneous containers (`std::vector`, `std::list`)
/// - heterogenous containers `(std::tuple`, `std::pair`, `std::array`)
/// and re-assembles the pack with the result of the mapper.
/// Mapping from one type to a different one is supported.
///
/// Elements that aren't accepted by the mapper are routed through
/// and preserved through the hierarchy.
///
/// ```cpp
/// // Maps all integers to floats
/// map_pack([](int value) {
/// return float(value);
/// },
/// 1, std::make_tuple(2, std::vector<int>{3, 4}), 5);
/// ```
///
/// \throws std::exception like objects which are thrown by an
/// invocation to the mapper.
///
/// \param mapper A callable object, which accept an arbitrary type
/// and maps it to another type or the same one.
///
/// \param pack An arbitrary variadic pack which may contain any type.
///
/// \returns The mapped element or in case the pack contains
/// multiple elements, the pack is wrapped into
/// a `std::tuple`.
///
/// \since 3.0.0
///
template <typename Mapper, typename... T>
/*keep this inline*/ inline decltype(auto) map_pack(Mapper&& mapper,
T&&... pack) {
return detail::traversal::transform(detail::traversal::strategy_remap_tag{},
std::forward<Mapper>(mapper),
std::forward<T>(pack)...);
}
/// Indicate that the result shall be spread across the parent container
/// if possible. This can be used to create a mapper function used
/// in map_pack that maps one element to an arbitrary count (1:n).
///
/// \since 3.0.0
template <typename... T>
constexpr detail::traversal::spreading::spread_box<std::decay_t<T>...>
spread_this(T&&... args) noexcept(
noexcept(std::make_tuple(std::forward<T>(args)...))) {
using type = detail::traversal::spreading::spread_box<std::decay_t<T>...>;
return type(std::make_tuple(std::forward<T>(args)...));
}
/// Traverses the pack with the given visitor.
///
/// This function works in the same way as `map_pack`,
/// however, the result of the mapper isn't preserved.
///
/// See `map_pack` for a detailed description.
///
/// \since 3.0.0
template <typename Mapper, typename... T>
void traverse_pack(Mapper&& mapper, T&&... pack) {
detail::traversal::transform(detail::traversal::strategy_traverse_tag{},
std::forward<Mapper>(mapper),
std::forward<T>(pack)...);
}
/// \}
} // namespace cti
#endif // CONTINUABLE_TRAVERSE_HPP_INCLUDED
// #include <continuable/detail/core/base.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_BASE_HPP_INCLUDED
#define CONTINUABLE_DETAIL_BASE_HPP_INCLUDED
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-result.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/result-trait.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
#include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS
namespace cti {
namespace detail {
/// The namespace `base` provides the low level API for working
/// with continuable types.
///
/// Important methods are:
/// - Creating a continuation from a callback taking functional
/// base::attorney::create_from(auto&& callback)
/// -> base::continuation<auto>
/// - Chaining a continuation together with a callback
/// base::chain_continuation(base::continuation<auto> continuation,
/// auto&& callback)
/// -> base::continuation<auto>
/// - Finally invoking the continuation chain
/// base::finalize_continuation(base::continuation<auto> continuation)
/// -> void
namespace base {
template <typename T>
struct is_continuable : std::false_type {};
template <typename Data, typename Annotation>
struct is_continuable<continuable_base<Data, Annotation>> : std::true_type {};
template <typename... Args>
struct ready_continuation {
explicit ready_continuation(result<Args...> result)
: result_(std::move(result)) {
}
ready_continuation() = delete;
~ready_continuation() = default;
ready_continuation(ready_continuation&&) = default;
ready_continuation(ready_continuation const&) = delete;
ready_continuation& operator=(ready_continuation&&) = default;
ready_continuation& operator=(ready_continuation const&) = delete;
template <typename Callback>
void operator()(Callback&& callback) {
if (result_.is_value()) {
traits::unpack(std::forward<Callback>(callback), std::move(result_));
} else if (result_.is_exception()) {
util::invoke(std::forward<Callback>(callback), exception_arg_t{},
result_.get_exception());
}
}
bool operator()(is_ready_arg_t) const noexcept {
return true;
}
result<Args...> operator()(unpack_arg_t) {
return std::move(result_);
}
private:
result<Args...> result_;
};
template <typename T>
struct ready_continuation_from_hint;
template <typename... Args>
struct ready_continuation_from_hint<identity<Args...>> {
using type = ready_continuation<Args...>;
};
template <typename T>
struct result_from_hint;
template <typename... Args>
struct result_from_hint<identity<Args...>> {
using type = result<Args...>;
};
template <typename Hint, typename Continuation>
struct proxy_continuable;
template <typename... Args, typename Continuation>
struct proxy_continuable<identity<Args...>, Continuation> : Continuation {
explicit proxy_continuable(Continuation continuation)
: Continuation(std::move(continuation)) {
}
~proxy_continuable() = default;
proxy_continuable(proxy_continuable&&) = default;
proxy_continuable(proxy_continuable const&) = delete;
proxy_continuable& operator=(proxy_continuable&&) = default;
proxy_continuable& operator=(proxy_continuable const&) = delete;
using Continuation::operator();
bool operator()(is_ready_arg_t) const noexcept {
return false;
}
result<Args...> operator()(unpack_arg_t) {
CTI_DETAIL_UNREACHABLE();
}
};
struct attorney {
/// Creates a continuable_base from the given continuation,
/// annotation and ownership.
template <typename T, typename Annotation>
static auto create_from_raw(T&& continuation, Annotation,
util::ownership ownership) {
using continuation_t = continuable_base<traits::unrefcv_t<T>, //
traits::unrefcv_t<Annotation>>;
return continuation_t{std::forward<T>(continuation), ownership};
}
/// Creates a continuable_base from the given continuation,
/// annotation and ownership.
/// This wraps the continuable to contain the is_ready and query method
/// implemented empty.
template <typename T, typename Hint>
static auto create_from(T&& continuation, Hint, util::ownership ownership) {
using hint_t = traits::unrefcv_t<Hint>;
using proxy_t = proxy_continuable<hint_t, traits::unrefcv_t<T>>;
return continuable_base<proxy_t, hint_t>{
proxy_t{std::forward<T>(continuation)}, ownership};
}
/// Returns the ownership of the given continuable_base
template <typename Continuable>
static util::ownership ownership_of(Continuable&& continuation) noexcept {
return continuation.ownership_;
}
template <typename Data, typename Annotation>
static Data&& consume(continuable_base<Data, Annotation>&& continuation) {
return std::move(continuation).consume();
}
template <typename Continuable>
static bool is_ready(Continuable&& continuation) noexcept {
return util::as_const(continuation.data_)(is_ready_arg_t{});
}
template <typename Data, typename Annotation>
static auto query(continuable_base<Data, Annotation>&& continuation) {
return std::move(continuation).consume()(unpack_arg_t{});
}
};
} // namespace base
template <typename Annotation>
struct annotation_trait;
/// Specialization for a present signature hint
template <typename... Args>
struct annotation_trait<identity<Args...>> {
template <typename Continuable>
static Continuable&& finish(Continuable&& continuable) {
return std::forward<Continuable>(continuable);
}
template <typename Continuable>
static bool is_ready(Continuable const& continuable) noexcept {
return base::attorney::is_ready(continuable);
}
};
namespace base {
/// Returns the signature hint of the given continuable
template <typename Data, typename... Args>
constexpr identity<Args...>
annotation_of(identity<continuable_base<Data, //
identity<Args...>>>) {
return {};
}
/// Invokes a continuation object in a reference correct way
template <typename Data, typename Annotation, typename Callback>
void invoke_continuation(continuable_base<Data, Annotation>&& continuation,
Callback&& callback) noexcept {
util::invoke(attorney::consume(std::move(continuation).finish()),
std::forward<Callback>(callback));
}
// Returns the invoker of a callback, the next callback
// and the arguments of the previous continuation.
//
// The return type of the invokerOf function matches a callable of:
// void(auto&& callback, auto&& next_callback, auto&&... args)
//
// The invoker decorates the result type in the following way
// - void -> next_callback()
// - ? -> next_callback(?)
// - std::pair<?, ?> -> next_callback(?, ?)
// - std::tuple<?...> -> next_callback(?...)
//
// When the result is a continuation itself pass the callback to it
// - continuation<?...> -> result(next_callback);
namespace decoration {
/// Helper class wrapping the underlaying unwrapping lambda
/// in order to extend it with a hint method.
template <typename T, typename Hint>
class invoker : public T {
public:
constexpr explicit invoker(T invoke) : T(std::move(invoke)) {
}
using T::operator();
/// Returns the underlaying signature hint
static constexpr Hint hint() noexcept {
return {};
}
};
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
#define CONTINUABLE_BLOCK_TRY_BEGIN try {
#define CONTINUABLE_BLOCK_TRY_END \
} \
catch (...) { \
std::forward<decltype(next_callback)>(next_callback)( \
exception_arg_t{}, std::current_exception()); \
}
#else // CONTINUABLE_HAS_EXCEPTIONS
#define CONTINUABLE_BLOCK_TRY_BEGIN {
#define CONTINUABLE_BLOCK_TRY_END }
#endif // CONTINUABLE_HAS_EXCEPTIONS
/// Invokes the callback partially, keeps the exception_arg_t such that
/// we don't jump accidentally from the exception path to the result path.
template <typename T, typename... Args>
constexpr auto invoke_callback(T&& callable, exception_arg_t exception_arg,
Args&&... args) {
return util::partial_invoke(std::integral_constant<std::size_t, 01>{},
std::forward<T>(callable), exception_arg,
std::forward<Args>(args)...);
}
template <typename T, typename... Args>
constexpr auto invoke_callback(T&& callable, Args&&... args) {
return util::partial_invoke(std::integral_constant<std::size_t, 0U>{},
std::forward<T>(callable),
std::forward<Args>(args)...);
}
/// Invokes the given callable object with the given arguments while
/// marking the operation as non exceptional.
template <typename T, typename... Args>
constexpr void invoke_no_except(T&& callable, Args&&... args) noexcept {
std::forward<T>(callable)(std::forward<Args>(args)...);
}
template <typename... Args, typename T>
void invoke_void_no_except(identity<exception_arg_t, Args...>,
T&& /*callable*/) noexcept {
// Don't invoke the next failure handler when being in an exception handler
}
template <typename... Args, typename T>
void invoke_void_no_except(identity<Args...>, T&& callable) noexcept {
std::forward<T>(callable)();
}
template <typename T, typename... Args>
constexpr auto make_invoker(T&& invoke, identity<Args...>) {
return invoker<std::decay_t<T>, identity<Args...>>(std::forward<T>(invoke));
}
/// - continuable<?...> -> result(next_callback);
template <typename Data, typename Annotation>
constexpr auto invoker_of(identity<continuable_base<Data, Annotation>>) {
/// Get the hint of the unwrapped returned continuable
using Type =
decltype(std::declval<continuable_base<Data, Annotation>>().finish());
auto constexpr const hint = base::annotation_of(identify<Type>{});
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
auto continuation_ =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_continuation(
std::move(continuation_),
std::forward<decltype(next_callback)>(next_callback));
CONTINUABLE_BLOCK_TRY_END
},
hint);
}
/// - ? -> next_callback(?)
template <typename T>
constexpr auto invoker_of(identity<T>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
auto result =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
std::move(result));
CONTINUABLE_BLOCK_TRY_END
},
identify<T>{});
}
/// - plain_tag<?> -> next_callback(?)
template <typename T>
constexpr auto invoker_of(identity<types::plain_tag<T>>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
types::plain_tag<T> result =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
std::move(result).consume());
CONTINUABLE_BLOCK_TRY_END
},
identify<T>{});
}
/// - void -> next_callback()
inline auto invoker_of(identity<void>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_void_no_except(
identity<traits::unrefcv_t<decltype(args)>...>{},
std::forward<decltype(next_callback)>(next_callback));
CONTINUABLE_BLOCK_TRY_END
},
identity<>{});
}
/// - empty_result -> <abort>
inline auto invoker_of(identity<empty_result>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
(void)next_callback;
CONTINUABLE_BLOCK_TRY_BEGIN
empty_result result =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Don't invoke anything here since returning an empty result
// aborts the asynchronous chain effectively.
(void)result;
CONTINUABLE_BLOCK_TRY_END
},
identity<>{});
}
/// - cancellation_result -> <cancel>
inline auto invoker_of(identity<cancellation_result>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
(void)next_callback;
CONTINUABLE_BLOCK_TRY_BEGIN
cancellation_result result = invoke_callback(
std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Forward the cancellation to the next available exception handler
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
exception_arg_t{}, exception_t{});
(void)result;
CONTINUABLE_BLOCK_TRY_END
},
identity<>{});
}
/// - exceptional_result -> <throw>
inline auto invoker_of(identity<exceptional_result>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
util::unused(callback, next_callback, args...);
CONTINUABLE_BLOCK_TRY_BEGIN
exceptional_result result =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Forward the exception to the next available exception handler
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
exception_arg_t{},
std::move(result).get_exception());
CONTINUABLE_BLOCK_TRY_END
},
identity<>{});
}
/// - result<?...> -> next_callback(?...)
template <typename... Args>
auto invoker_of(identity<result<Args...>>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
result<Args...> result =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
if (result.is_value()) {
// Workaround for MSVC not capturing the reference
// correctly inside the lambda.
using Next = decltype(next_callback);
traits::unpack(
[&](auto&&... values) {
invoke_no_except(std::forward<Next>(next_callback),
std::forward<decltype(values)>(values)...);
},
std::move(result));
} else if (result.is_exception()) {
// Forward the exception to the next available handler
invoke_no_except(std::forward<decltype(next_callback)>(
next_callback),
exception_arg_t{},
std::move(result).get_exception());
} else {
// Aborts the continuation of the chain
assert(result.is_empty());
}
// Otherwise the result is empty and we are cancelling our
// asynchronous chain.
CONTINUABLE_BLOCK_TRY_END
},
identity<Args...>{});
}
/// Returns a sequenced invoker which is able to invoke
/// objects where std::get is applicable.
inline auto sequenced_unpack_invoker() {
return [](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
auto result = invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Workaround for MSVC not capturing the reference
// correctly inside the lambda.
using Next = decltype(next_callback);
traits::unpack(
[&](auto&&... values) {
invoke_no_except(std::forward<Next>(next_callback),
std::forward<decltype(values)>(values)...);
},
std::move(result));
CONTINUABLE_BLOCK_TRY_END
};
} // namespace decoration
// - std::pair<?, ?> -> next_callback(?, ?)
template <typename First, typename Second>
constexpr auto invoker_of(identity<std::pair<First, Second>>) {
return make_invoker(sequenced_unpack_invoker(), identity<First, Second>{});
}
// - std::tuple<?...> -> next_callback(?...)
template <typename... Args>
constexpr auto invoker_of(identity<std::tuple<Args...>>) {
return make_invoker(sequenced_unpack_invoker(), identity<Args...>{});
}
#undef CONTINUABLE_BLOCK_TRY_BEGIN
#undef CONTINUABLE_BLOCK_TRY_END
} // namespace decoration
/// Invoke the callback immediately
template <typename Invoker, typename Callback, typename NextCallback,
typename... Args>
void on_executor(types::this_thread_executor_tag, Invoker&& invoker,
Callback&& callback, NextCallback&& next_callback,
Args&&... args) {
// Invoke the callback with the decorated invoker immediately
std::forward<Invoker>(invoker)(std::forward<Callback>(callback),
std::forward<NextCallback>(next_callback),
std::forward<Args>(args)...);
}
template <typename Invoker, typename Callback, typename NextCallback,
typename... Args>
class work_proxy {
public:
work_proxy(Invoker&& invoker, Callback&& callback,
NextCallback&& next_callback, std::tuple<Args...>&& args)
: invoker_(std::move(invoker)), callback_(std::move(callback)),
next_callback_(std::move(next_callback)), args_(std::move(args)) {
}
~work_proxy() = default;
work_proxy(work_proxy&&) = default;
work_proxy(work_proxy const&) = delete;
work_proxy& operator=(work_proxy&&) = default;
work_proxy& operator=(work_proxy const&) = delete;
void set_value() noexcept {
traits::unpack(
[&](auto&&... captured_args) {
// Just use the packed dispatch method which dispatches the work_proxy
// on the current thread.
std::move(invoker_)(
std::move(callback_), std::move(next_callback_),
std::forward<decltype(captured_args)>(captured_args)...);
},
std::move(args_));
}
void operator()() && noexcept {
std::move(*this).set_value();
}
void operator()(exception_arg_t, exception_t exception) && noexcept {
std::move(next_callback_)(exception_arg_t{}, std::move(exception));
}
void set_exception(exception_t exception) noexcept {
std::move(next_callback_)(exception_arg_t{}, std::move(exception));
}
void set_canceled() noexcept {
std::move(next_callback_)(exception_arg_t{}, exception_t{});
}
explicit operator bool() const noexcept {
return true;
}
private:
Invoker invoker_;
Callback callback_;
NextCallback next_callback_;
std::tuple<Args...> args_;
};
/// Invoke the callback through the given executor
template <typename Executor, typename Invoker, typename Callback,
typename NextCallback, typename... Args>
void on_executor(Executor&& executor, Invoker&& invoker, Callback&& callback,
NextCallback&& next_callback, Args&&... args) {
// Create a work_proxy object which when invoked calls the callback with the
// the returned arguments and pass the work_proxy callable object to the
// executor
using work_proxy_t =
work_proxy<Invoker, std::decay_t<Callback>, std::decay_t<NextCallback>,
std::decay_t<Args>...>;
std::forward<Executor>(executor)(work_proxy_t(
std::forward<Invoker>(invoker), std::forward<Callback>(callback),
std::forward<NextCallback>(next_callback),
std::make_tuple(std::forward<Args>(args)...)));
}
/// Tells whether we potentially move the chain upwards and handle the result
enum class handle_results {
no, //< The result is forwarded to the next callable
yes //< The result is handled by the current callable
};
// Silences a doxygen bug, it tries to map forward to std::forward
/// \cond false
/// Tells whether we handle the error through the callback
enum class handle_errors {
no, //< The error is forwarded to the next callable
forward //< The error is forwarded to the callable while keeping its tag
};
/// \endcond
namespace callbacks {
namespace proto {
template <handle_results HandleResults, typename Base, typename Hint>
struct result_handler_base;
template <typename Base, typename... Args>
struct result_handler_base<handle_results::no, Base, identity<Args...>> {
void operator()(Args... args) && {
// Forward the arguments to the next callback
std::move(static_cast<Base*>(this)->next_callback_)(std::move(args)...);
}
};
template <typename Base, typename... Args>
struct result_handler_base<handle_results::yes, Base, identity<Args...>> {
/// The operator which is called when the result was provided
void operator()(Args... args) && {
// In order to retrieve the correct decorator we must know what the
// result type is.
constexpr auto result = identify<decltype(decoration::invoke_callback(
std::move(static_cast<Base*>(this)->callback_), std::move(args)...))>{};
// Pick the correct invoker that handles decorating of the result
auto invoker = decoration::invoker_of(result);
// Invoke the callback
on_executor(std::move(static_cast<Base*>(this)->executor_),
std::move(invoker),
std::move(static_cast<Base*>(this)->callback_),
std::move(static_cast<Base*>(this)->next_callback_),
std::move(args)...);
}
};
template <handle_errors HandleErrors, typename Base>
struct error_handler_base;
template <typename Base>
struct error_handler_base<handle_errors::no, Base> {
/// The operator which is called when an error occurred
void operator()(exception_arg_t tag, exception_t exception) && {
// Forward the error to the next callback
std::move(static_cast<Base*>(this)->next_callback_)(tag,
std::move(exception));
}
};
template <typename Base>
struct error_handler_base<handle_errors::forward, Base> {
/// The operator which is called when an error occurred
void operator()(exception_arg_t, exception_t exception) && {
constexpr auto result = identify<decltype(decoration::invoke_callback(
std::move(static_cast<Base*>(this)->callback_), exception_arg_t{},
std::move(exception)))>{};
auto invoker = decoration::invoker_of(result);
// Invoke the error handler
on_executor(std::move(static_cast<Base*>(this)->executor_),
std::move(invoker),
std::move(static_cast<Base*>(this)->callback_),
std::move(static_cast<Base*>(this)->next_callback_),
exception_arg_t{}, std::move(exception));
}
};
} // namespace proto
template <typename Hint, handle_results HandleResults,
handle_errors HandleErrors, typename Callback, typename Executor,
typename NextCallback>
struct callback_base;
template <typename... Args, handle_results HandleResults,
handle_errors HandleErrors, typename Callback, typename Executor,
typename NextCallback>
struct callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
Executor, NextCallback>
: proto::result_handler_base<
HandleResults,
callback_base<identity<Args...>, HandleResults, HandleErrors,
Callback, Executor, NextCallback>,
identity<Args...>>,
proto::error_handler_base<
HandleErrors,
callback_base<identity<Args...>, HandleResults, HandleErrors,
Callback, Executor, NextCallback>>,
util::non_copyable {
Callback callback_;
Executor executor_;
NextCallback next_callback_;
explicit callback_base(Callback callback, Executor executor,
NextCallback next_callback)
: callback_(std::move(callback)), executor_(std::move(executor)),
next_callback_(std::move(next_callback)) {
}
/// Pull the result handling operator() in
using proto::result_handler_base<
HandleResults,
callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
Executor, NextCallback>,
identity<Args...>>::operator();
/// Pull the error handling operator() in
using proto::error_handler_base<
HandleErrors,
callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
Executor, NextCallback>>::operator();
/// Resolves the continuation with the given values
void set_value(Args... args) noexcept {
std::move (*this)(std::move(args)...);
}
/// Resolves the continuation with the given exception.
void set_exception(exception_t exception) noexcept {
std::move (*this)(exception_arg_t{}, std::move(exception));
}
void set_canceled() noexcept {
std::move (*this)(exception_arg_t{}, exception_t{});
}
/// Returns true because this is a present continuation
explicit operator bool() const noexcept {
return true;
}
};
template <typename Hint, handle_results HandleResults,
handle_errors HandleErrors, typename Callback, typename Executor,
typename NextCallback>
auto make_callback(Callback&& callback, Executor&& executor,
NextCallback&& next_callback) {
return callback_base<Hint, HandleResults, HandleErrors,
std::decay_t<Callback>, std::decay_t<Executor>,
std::decay_t<NextCallback>>{
std::forward<Callback>(callback), std::forward<Executor>(executor),
std::forward<NextCallback>(next_callback)};
}
/// Represents the last callback in the asynchronous continuation chain
template <typename... Args>
struct final_callback : util::non_copyable {
void operator()(Args... /*args*/) && {
}
void operator()(exception_arg_t, exception_t exception) && {
// Only handle the exception when it is present, otherwise handle it as
// a cancellation of the control flow.
// This behaviour is intentionally correct for
// - `std::exception_ptr`
// - `std::error_code`
// - `std::error_condition`
// which allow to be default constructed and then return false
// by their corresponding `operator bool()`.
if (bool(exception)) {
#ifndef CONTINUABLE_WITH_UNHANDLED_EXCEPTIONS
// There were unhandled errors inside the asynchronous call chain!
// Define `CONTINUABLE_WITH_UNHANDLED_EXCEPTIONS` in order
// to ignore unhandled errors!"
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
try {
std::rethrow_exception(exception);
} catch (std::exception const& unhandled) {
char const* const what = unhandled.what();
(void)what;
CTI_DETAIL_TRAP();
} catch (...) {
CTI_DETAIL_TRAP();
}
#else
CTI_DETAIL_TRAP();
#endif
#endif // CONTINUABLE_WITH_UNHANDLED_EXCEPTIONS
}
}
void set_value(Args... args) noexcept {
std::move (*this)(std::forward<Args>(args)...);
}
void set_exception(exception_t exception) noexcept {
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
std::move (*this)(exception_arg_t{}, std::move(exception));
}
void set_canceled() noexcept {
std::move (*this)(exception_arg_t{}, exception_t{});
}
explicit operator bool() const noexcept {
return true;
}
};
} // namespace callbacks
/// Returns the next hint when the callback is invoked with the given hint
template <typename T, typename... Args>
constexpr auto
next_hint_of(std::integral_constant<handle_results, handle_results::yes>,
identity<T> /*callback*/, identity<Args...> /*current*/) {
// Partial Invoke the given callback
using Result = decltype(
decoration::invoke_callback(std::declval<T>(), std::declval<Args>()...));
// Return the hint of thr given invoker
return decltype(decoration::invoker_of(identify<Result>{}).hint()){};
}
/// Don't progress the hint when we don't continue
template <typename T, typename... Args>
constexpr auto
next_hint_of(std::integral_constant<handle_results, handle_results::no>,
identity<T> /*callback*/, identity<Args...> current) {
return current;
}
namespace detail {
template <typename Callable>
struct exception_stripper_proxy {
Callable callable_;
template <typename... Args>
auto operator()(exception_arg_t, Args&&... args)
-> decltype(util::invoke(std::declval<Callable>(), //
std::declval<Args>()...)) {
return util::invoke(std::move(callable_), //
std::forward<decltype(args)>(args)...);
}
};
} // namespace detail
/// Removes the exception_arg_t from the arguments passed to the given callable
template <typename Callable>
auto strip_exception_arg(Callable&& callable) {
using proxy = detail::exception_stripper_proxy<traits::unrefcv_t<Callable>>;
return proxy{std::forward<Callable>(callable)};
}
template <typename Hint, typename NextHint, handle_results HandleResults,
handle_errors HandleErrors, typename Continuation, typename Callback,
typename Executor>
struct chained_continuation;
template <typename... Args, typename... NextArgs, handle_results HandleResults,
handle_errors HandleErrors, typename Continuation, typename Callback,
typename Executor>
struct chained_continuation<identity<Args...>, identity<NextArgs...>,
HandleResults, HandleErrors, Continuation, Callback,
Executor> {
Continuation continuation_;
Callback callback_;
Executor executor_;
explicit chained_continuation(Continuation continuation, Callback callback,
Executor executor)
: continuation_(std::move(continuation)), callback_(std::move(callback)),
executor_(std::move(executor)) {
}
chained_continuation() = delete;
~chained_continuation() = default;
chained_continuation(chained_continuation const&) = delete;
chained_continuation(chained_continuation&&) = default;
chained_continuation& operator=(chained_continuation const&) = delete;
chained_continuation& operator=(chained_continuation&&) = default;
template <typename NextCallback>
void operator()(NextCallback&& next_callback) {
// Invokes a continuation with a given callback.
// Passes the next callback to the resulting continuable or
// invokes the next callback directly if possible.
//
// For example given:
// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
// - Callback: [](std::string) { }
// - NextCallback: []() { }
auto proxy = callbacks::make_callback<identity<Args...>, HandleResults,
HandleErrors>(
std::move(callback_), std::move(executor_),
std::forward<decltype(next_callback)>(next_callback));
// Check whether the continuation is ready
bool const is_ready = util::as_const(continuation_)(is_ready_arg_t{});
if (is_ready) {
// Invoke the proxy callback directly with the result to
// avoid a potential type erasure.
auto result = std::move(continuation_)(unpack_arg_t{});
if (result.is_value()) {
traits::unpack(std::move(proxy), std::move(result));
} else if (result.is_exception()) {
util::invoke(std::move(proxy), exception_arg_t{},
std::move(result.get_exception()));
} else {
assert(result.is_empty());
}
} else {
// Invoke the continuation with a proxy callback.
// The proxy callback is responsible for passing
// the result to the callback as well as decorating it.
util::invoke(std::move(continuation_), std::move(proxy));
}
}
bool operator()(is_ready_arg_t) const noexcept {
return false;
}
result<NextArgs...> operator()(unpack_arg_t) {
CTI_DETAIL_UNREACHABLE();
}
};
// Specialization to unpack ready continuables directly
template <typename... Args, typename... NextArgs, handle_results HandleResults,
handle_errors HandleErrors, typename Callback, typename Executor>
struct chained_continuation<identity<Args...>, identity<NextArgs...>,
HandleResults, HandleErrors,
ready_continuation<Args...>, Callback, Executor> {
ready_continuation<Args...> continuation_;
Callback callback_;
Executor executor_;
explicit chained_continuation(ready_continuation<Args...> continuation,
Callback callback, Executor executor)
: continuation_(std::move(continuation)), callback_(std::move(callback)),
executor_(std::move(executor)) {
}
chained_continuation() = delete;
~chained_continuation() = default;
chained_continuation(chained_continuation const&) = delete;
chained_continuation(chained_continuation&&) = default;
chained_continuation& operator=(chained_continuation const&) = delete;
chained_continuation& operator=(chained_continuation&&) = default;
template <typename NextCallback>
void operator()(NextCallback&& next_callback) {
auto proxy = callbacks::make_callback<identity<Args...>, HandleResults,
HandleErrors>(
std::move(callback_), std::move(executor_),
std::forward<decltype(next_callback)>(next_callback));
// Extract the result out of the ready continuable
auto result = std::move(continuation_)(unpack_arg_t{});
if (result.is_value()) {
traits::unpack(std::move(proxy), std::move(result));
} else if (result.is_exception()) {
util::invoke(std::move(proxy), exception_arg_t{},
std::move(result.get_exception()));
} else {
assert(result.is_empty());
}
}
bool operator()(is_ready_arg_t) const noexcept {
return false;
}
result<NextArgs...> operator()(unpack_arg_t) {
CTI_DETAIL_UNREACHABLE();
}
};
/// Chains a callback together with a continuation and returns a
/// continuation:
///
/// For example given:
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
/// - Callback: [](std::string) { }
///
/// This function returns a function accepting the next callback in the
/// chain:
/// - Result: continuation<[](auto&& callback) { /*...*/ }>
///
template <handle_results HandleResults, handle_errors HandleErrors,
typename Continuation, typename Callback, typename Executor>
auto chain_continuation(Continuation&& continuation, Callback&& callback,
Executor&& executor) {
static_assert(is_continuable<std::decay_t<Continuation>>{},
"Expected a continuation!");
using Hint = decltype(base::annotation_of(identify<Continuation>()));
constexpr auto next_hint =
next_hint_of(std::integral_constant<handle_results, HandleResults>{},
identify<decltype(callback)>{}, Hint{});
auto ownership = attorney::ownership_of(continuation);
auto data =
attorney::consume(std::forward<Continuation>(continuation).finish());
using continuation_t = chained_continuation<
Hint, traits::unrefcv_t<decltype(next_hint)>, HandleResults, HandleErrors,
decltype(data), traits::unrefcv_t<Callback>, traits::unrefcv_t<Executor>>;
return attorney::create_from_raw(
continuation_t(std::move(data), std::forward<Callback>(callback),
std::forward<Executor>(executor)),
next_hint, ownership);
}
/// Final invokes the given continuation chain:
///
/// For example given:
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
template <typename Data, typename... Args>
void finalize_continuation(
continuable_base<Data, identity<Args...>>&& continuation) noexcept {
#ifdef CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK
invoke_continuation(std::move(continuation),
CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK<Args...>{});
#else // CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK
invoke_continuation(std::move(continuation),
callbacks::final_callback<Args...>{});
#endif // CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK
}
/// Deduces to a true type if the given callable data can be wrapped
/// with the given hint and converted to the given Data.
template <typename Data, typename Annotation, typename Continuation>
struct can_accept_continuation : std::false_type {};
template <typename Data, typename... Args, typename Continuation>
struct can_accept_continuation<Data, identity<Args...>, Continuation>
: traits::conjunction<
traits::is_invocable<Continuation,
callbacks::final_callback<Args...>>,
std::is_convertible<
proxy_continuable<identity<Args...>, Continuation>, Data>> {};
/// Workaround for GCC bug:
/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095
template <typename T>
class supplier_callback {
T data_;
public:
explicit supplier_callback(T data) : data_(std::move(data)) {
}
template <typename... Args>
auto operator()(Args...) {
return std::move(data_);
}
};
/// Returns a continuable into a callable object returning the continuable
template <typename Continuation>
auto wrap_continuation(Continuation&& continuation) {
continuation.freeze();
return supplier_callback<std::decay_t<Continuation>>(
std::forward<Continuation>(continuation));
}
/// Callback which converts its input to the given set of arguments
template <typename... Args>
struct convert_to {
std::tuple<Args...> operator()(Args... args) {
return std::make_tuple(std::move(args)...);
}
};
template <typename T>
struct convert_to<T> {
T operator()(T arg) noexcept {
return std::move(arg);
}
};
template <>
struct convert_to<> {
void operator()() noexcept {
}
};
} // namespace base
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_BASE_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace connection {
/// This namespace provides utilities for performing compound
/// connections between deeply nested continuables and values.
///
/// We create the result pack from the provides values and
/// the async values if those are default constructible,
/// otherwise use a lazy initialization wrapper and unwrap
/// the whole pack when the connection is finished.
/// - value -> value
/// - single async value -> single value
/// - multiple async value -> tuple of async values.
namespace aggregated {
/// Guards a type to be default constructible,
/// and wraps it into an optional type if it isn't default constructible.
template <typename T>
using lazy_value_t = std::conditional_t<std::is_default_constructible<T>::value,
T, result<T>>;
template <typename T>
decltype(auto) unpack_lazy(std::true_type /*is_default_constructible*/,
T&& value) {
return std::forward<T>(value);
}
template <typename T>
T&& unpack_lazy(std::false_type /*is_default_constructible*/,
result<T>&& value) {
assert(value.is_value() &&
"The connection was finalized before all values were present!");
return std::move(value).get_value();
}
template <typename Continuable>
class continuable_box;
template <typename Data>
class continuable_box<continuable_base<Data, identity<>>> {
continuable_base<Data, identity<>> continuable_;
public:
explicit continuable_box(continuable_base<Data, identity<>>&& continuable)
: continuable_(std::move(continuable)) {}
auto const& peek() const {
return continuable_;
}
auto&& fetch() {
return std::move(continuable_);
}
void assign() {}
auto unbox() && {
return spread_this();
}
};
template <typename Data, typename First>
class continuable_box<continuable_base<Data, identity<First>>> {
continuable_base<Data, identity<First>> continuable_;
lazy_value_t<First> first_;
public:
explicit continuable_box(
continuable_base<Data, identity<First>>&& continuable)
: continuable_(std::move(continuable)) {}
auto const& peek() const {
return continuable_;
}
auto&& fetch() {
return std::move(continuable_);
}
void assign(First first) {
first_ = std::move(first);
}
auto unbox() && {
return unpack_lazy(std::is_default_constructible<First>{},
std::move(first_));
}
};
template <typename Data, typename First, typename Second, typename... Rest>
class continuable_box<
continuable_base<Data, identity<First, Second, Rest...>>> {
continuable_base<Data, identity<First, Second, Rest...>> continuable_;
lazy_value_t<std::tuple<First, Second, Rest...>> args_;
public:
explicit continuable_box(
continuable_base<Data, identity<First, Second, Rest...>>&& continuable)
: continuable_(std::move(continuable)) {}
auto const& peek() const {
return continuable_;
}
auto&& fetch() {
return std::move(continuable_);
}
void assign(First first, Second second, Rest... rest) {
args_ = std::make_tuple(std::move(first), std::move(second),
std::move(rest)...);
}
auto unbox() && {
return traits::unpack(
[](auto&&... args) {
return spread_this(std::forward<decltype(args)>(args)...);
},
unpack_lazy(
std::is_default_constructible<std::tuple<First, Second, Rest...>>{},
std::move(args_)));
}
};
template <typename T>
struct is_continuable_box : std::false_type {};
template <typename Continuable>
struct is_continuable_box<continuable_box<Continuable>> : std::true_type {};
namespace detail {
/// Maps a deeply nested pack of continuables to a continuable_box
struct continuable_box_packer {
template <
typename T,
std::enable_if_t<base::is_continuable<std::decay_t<T>>::value>* = nullptr>
auto operator()(T&& continuable) {
return continuable_box<std::decay_t<T>>{std::forward<T>(continuable)};
}
};
/// Maps a deeply nested pack of continuable_boxes to its result
struct continuable_box_unpacker {
template <
typename T,
std::enable_if_t<is_continuable_box<std::decay_t<T>>::value>* = nullptr>
auto operator()(T&& box) {
return std::forward<T>(box).unbox();
}
};
} // namespace detail
/// Returns the boxed pack of the given deeply nested pack.
/// This transforms all continuables into a continuable_box which is
/// capable of caching the result from the corresponding continuable.
template <typename... Args>
constexpr auto box_continuables(Args&&... args) {
return cti::map_pack(detail::continuable_box_packer{},
std::forward<Args>(args)...);
}
/// Returns the unboxed pack of the given deeply nested boxed pack.
/// This transforms all continuable_boxes into its result.
template <typename... Args>
constexpr auto unbox_continuables(Args&&... args) {
return cti::map_pack(detail::continuable_box_unpacker{},
std::forward<Args>(args)...);
}
namespace detail {
template <typename Callback, typename Data>
constexpr auto finalize_impl(identity<void>, Callback&& callback, Data&&) {
return std::forward<Callback>(callback)();
}
template <typename... Args, typename Callback, typename Data>
constexpr auto finalize_impl(identity<std::tuple<Args...>>, Callback&& callback,
Data&& data) {
// Call the final callback with the cleaned result
return traits::unpack(std::forward<Callback>(callback),
unbox_continuables(std::forward<Data>(data)));
}
struct hint_mapper {
template <typename... T>
constexpr auto operator()(T...) -> identity<T...> {
return {};
}
};
} // namespace detail
template <typename Callback, typename Data>
constexpr auto finalize_data(Callback&& callback, Data&& data) {
using result_t = decltype(unbox_continuables(std::forward<Data>(data)));
// Guard the final result against void
return detail::finalize_impl(identity<std::decay_t<result_t>>{},
std::forward<Callback>(callback),
std::forward<Data>(data));
}
template <typename Data>
constexpr auto hint_of_data() {
return decltype(finalize_data(detail::hint_mapper{}, std::declval<Data>())){};
}
} // namespace aggregated
} // namespace connection
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONNECTION_REMAPPING_HPP_INCLUDED
// #include <continuable/detail/connection/connection.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_CONNECTION_HPP_INCLUDED
#define CONTINUABLE_DETAIL_CONNECTION_HPP_INCLUDED
#include <cassert>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-traverse.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
namespace cti {
namespace detail {
/// The namespace `connection` offers methods to chain continuations together
/// with `all`, `any` or `seq` logic.
namespace connection {
template <typename T>
struct is_connection_strategy // ...
: std::false_type {};
/// Adds the given continuation tuple to the left connection
template <typename... LeftArgs, typename... RightArgs>
auto chain_connection(std::tuple<LeftArgs...> leftPack,
std::tuple<RightArgs...> rightPack) {
return std::tuple_cat(std::move(leftPack), std::move(rightPack));
}
/// Normalizes a continuation to a tuple holding an arbitrary count of
/// continuations matching the given strategy.
///
/// Basically we can encounter 3 cases:
/// - The continuable isn't in any strategy:
/// -> make a tuple containing the continuable as only element
template <
typename Strategy, typename Data, typename Annotation,
std::enable_if_t<!is_connection_strategy<Annotation>::value>* = nullptr>
auto normalize(Strategy /*strategy*/,
continuable_base<Data, Annotation>&& continuation) {
// If the continuation isn't a strategy initialize the strategy
return std::make_tuple(std::move(continuation));
}
/// - The continuable is in a different strategy then the current one:
/// -> materialize it
template <
typename Strategy, typename Data, typename Annotation,
std::enable_if_t<is_connection_strategy<Annotation>::value>* = nullptr>
auto normalize(Strategy /*strategy*/,
continuable_base<Data, Annotation>&& continuation) {
// If the right continuation is a different strategy materialize it
// in order to keep the precedence in cases where: `c1 && (c2 || c3)`.
return std::make_tuple(std::move(continuation).finish());
}
/// - The continuable is inside the current strategy state:
/// -> return the data of the tuple
template <typename Strategy, typename Data>
auto normalize(Strategy /*strategy*/,
continuable_base<Data, Strategy>&& continuation) {
// If we are in the given strategy we can just use the data of the continuable
return base::attorney::consume(std::move(continuation));
}
/// Entry function for connecting two continuables with a given strategy.
template <typename Strategy, typename LData, typename LAnnotation,
typename RData, typename RAnnotation>
auto connect(Strategy strategy, continuable_base<LData, LAnnotation>&& left,
continuable_base<RData, RAnnotation>&& right) {
auto ownership_ =
base::attorney::ownership_of(left) | base::attorney::ownership_of(right);
left.freeze();
right.freeze();
// Make the new data which consists of a tuple containing
// all connected continuables.
auto data = chain_connection(normalize(strategy, std::move(left)),
normalize(strategy, std::move(right)));
// Return a new continuable containing the tuple and holding
// the current strategy as annotation.
return base::attorney::create_from_raw(std::move(data), strategy, ownership_);
}
/// All strategies should specialize this class in order to provide:
/// - A finalize static method that creates the callable object which
/// is invoked with the callback to call when the connection is finished.
/// - A static method hint that returns the new signature hint.
template <typename Strategy>
struct connection_finalizer;
template <typename Strategy>
struct connection_annotation_trait {
/// Finalizes the connection logic of a given connection
template <typename Continuable>
static auto finish(Continuable&& continuable) {
using finalizer = connection_finalizer<Strategy>;
util::ownership ownership = base::attorney::ownership_of(continuable);
auto connection =
base::attorney::consume(std::forward<Continuable>(continuable));
// Return a new continuable which
return finalizer::finalize(std::move(connection), std::move(ownership));
}
template <typename Continuable>
static bool is_ready(Continuable const& /*continuable*/) noexcept {
return false;
}
};
class prepare_continuables {
util::ownership& ownership_;
public:
explicit constexpr prepare_continuables(util::ownership& ownership)
: ownership_(ownership) {
}
template <typename Continuable,
std::enable_if_t<base::is_continuable<
std::decay_t<Continuable>>::value>* = nullptr>
auto operator()(Continuable&& continuable) noexcept {
util::ownership current = base::attorney::ownership_of(continuable);
assert(current.is_acquired() &&
"Only valid continuables should be passed!");
// Propagate a frozen state to the new continuable
if (!ownership_.is_frozen() && current.is_frozen()) {
ownership_.freeze();
}
// Freeze the continuable since it is stored for later usage
continuable.freeze();
// Materialize every continuable
// TODO Actually we would just need to consume the data here
return std::forward<Continuable>(continuable).finish();
}
};
template <typename Strategy, typename... Args>
auto apply_connection(Strategy, Args&&... args) {
using finalizer = connection_finalizer<Strategy>;
// Freeze every continuable inside the given arguments,
// and freeze the ownership if one of the continuables
// is frozen already.
// Additionally test whether every continuable is acquired.
// Also materialize every continuable.
util::ownership ownership;
auto connection = map_pack(prepare_continuables{ownership},
std::make_tuple(std::forward<Args>(args)...));
return finalizer::finalize(std::move(connection), std::move(ownership));
}
} // namespace connection
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONNECTION_HPP_INCLUDED
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace connection {
namespace all {
/// Caches the partial results and invokes the callback when all results
/// are arrived. This class is thread safe.
template <typename Callback, typename Result>
class result_submitter
: public std::enable_shared_from_this<result_submitter<Callback, Result>>,
public util::non_movable {
Callback callback_;
Result result_;
std::atomic<std::size_t> left_;
std::once_flag flag_;
// Invokes the callback with the cached result
void invoke() {
assert((left_ == 0U) && "Expected that the submitter is finished!");
std::atomic_thread_fence(std::memory_order_acquire);
// Call the final callback with the cleaned result
std::call_once(flag_, [&] {
aggregated::finalize_data(std::move(callback_), std::move(result_));
});
}
// Completes one result
void complete_one() {
assert((left_ > 0U) && "Expected that the submitter isn't finished!");
auto const current = --left_;
if (!current) {
invoke();
}
}
template <typename Box>
struct partial_all_callback {
Box* box;
std::shared_ptr<result_submitter> me;
template <typename... Args>
void operator()(Args&&... args) && {
// Assign the result to the target
box->assign(std::forward<decltype(args)>(args)...);
// Complete one result
me->complete_one();
}
template <typename... PartialArgs>
void operator()(exception_arg_t tag, exception_t exception) && {
// We never complete the connection, but we forward the first error
// which was raised.
std::call_once(me->flag_, std::move(me->callback_), tag,
std::move(exception));
}
};
public:
explicit result_submitter(Callback callback, Result&& result)
: callback_(std::move(callback)), result_(std::move(result)), left_(1) {
}
/// Creates a submitter which submits it's result into the storage
template <typename Box>
auto create_callback(Box* box) {
left_.fetch_add(1, std::memory_order_seq_cst);
return partial_all_callback<std::decay_t<Box>>{box,
this->shared_from_this()};
}
/// Initially the counter is created with an initial count of 1 in order
/// to prevent that the connection is finished before all callbacks
/// were registered.
void accept() {
complete_one();
}
constexpr auto& head() noexcept {
return result_;
}
};
template <typename Submitter>
struct continuable_dispatcher {
std::shared_ptr<Submitter>& submitter;
template <typename Box, std::enable_if_t<aggregated::is_continuable_box<
std::decay_t<Box>>::value>* = nullptr>
void operator()(Box&& box) const {
// Retrieve a callback from the submitter and attach it to the continuable
box.fetch().next(submitter->create_callback(std::addressof(box))).done();
}
};
} // namespace all
struct connection_strategy_all_tag {};
template <>
struct is_connection_strategy<connection_strategy_all_tag> // ...
: std::true_type {};
/// Finalizes the all logic of a given connection
template <>
struct connection_finalizer<connection_strategy_all_tag> {
/// Finalizes the all logic of a given connection
template <typename Connection>
static auto finalize(Connection&& connection, util::ownership ownership) {
// Create the target result from the connection
auto res =
aggregated::box_continuables(std::forward<Connection>(connection));
auto signature = aggregated::hint_of_data<decltype(res)>();
return base::attorney::create_from(
[res = std::move(res)](auto&& callback) mutable {
using submitter_t =
all::result_submitter<std::decay_t<decltype(callback)>,
std::decay_t<decltype(res)>>;
// Create the shared state which holds the result
// and the final callback
auto state = std::make_shared<submitter_t>(
std::forward<decltype(callback)>(callback), std::move(res));
// Dispatch the continuables and store its partial result
// in the whole result
traverse_pack(all::continuable_dispatcher<submitter_t>{state},
state->head());
// Finalize the connection if all results arrived in-place
state->accept();
},
signature, std::move(ownership));
}
};
} // namespace connection
/// Specialization for a connection annotation
template <>
struct annotation_trait<connection::connection_strategy_all_tag>
: connection::connection_annotation_trait<
connection::connection_strategy_all_tag> {};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONNECTION_ALL_HPP_INCLUDED
// #include <continuable/detail/connection/connection-any.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_CONNECTION_ANY_HPP_INCLUDED
#define CONTINUABLE_DETAIL_CONNECTION_ANY_HPP_INCLUDED
#include <atomic>
#include <memory>
#include <mutex>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-promise-base.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_PROMISE_BASE_HPP_INCLUDED
#define CONTINUABLE_PROMISE_BASE_HPP_INCLUDED
#include <cassert>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
namespace cti {
/// \defgroup Base Base
/// provides classes and functions to create continuable_base objects.
/// \{
/// The promise_base makes it possible to resolve an asynchronous
/// continuable through it's result or through an error type.
///
/// Use the promise type defined in `continuable/continuable_types.hpp`,
/// in order to use this class.
///
/// If we want to resolve the promise_base trough the call operator,
/// and we want to resolve it through an exception, we must call it with a
/// exception_arg_t as first and the exception as second argument.
/// Additionally the promise is resolveable only through its call
/// operator when invoked as an r-value.
///
/// \since 2.0.0
// clang-format off
template <typename Data, typename Hint>
class promise_base
/// \cond false
;
template <typename Data, typename... Args>
class promise_base<Data, detail::identity<Args...>>
: detail::util::non_copyable
/// \endcond
{ // clang-format on
/// \cond false
// The callback type
Data data_;
/// \endcond
public:
/// Constructor for constructing an empty promise
explicit promise_base() = default;
/// Constructor accepting the data object
explicit promise_base(Data data) : data_(std::move(data)) {
}
/// \cond false
promise_base(promise_base&&) = default;
promise_base(promise_base const&) = delete;
promise_base& operator=(promise_base&&) = default;
promise_base& operator=(promise_base const&) = delete;
/// \endcond
/// Constructor accepting any object convertible to the data object
template <typename OData,
std::enable_if_t<std::is_convertible<
detail::traits::unrefcv_t<OData>, Data>::value>* = nullptr>
/* implicit */ promise_base(OData&& data) : data_(std::forward<OData>(data)) {
}
/// Assignment operator accepting any object convertible to the data object
template <typename OData,
std::enable_if_t<std::is_convertible<
detail::traits::unrefcv_t<OData>, Data>::value>* = nullptr>
promise_base& operator=(OData&& data) {
data_ = std::forward<OData>(data);
return *this;
}
/// Resolves the continuation with the given values.
///
/// \throws This method never throws an exception.
///
/// \attention This method may only be called once,
/// when the promise is valid operator bool() returns true.
/// Calling this method will invalidate the promise such that
/// subsequent calls to operator bool() will return false.
/// This behaviour is only consistent in promise_base and
/// non type erased promises may behave differently.
/// Invoking an invalid promise_base is undefined!
///
/// \since 2.0.0
void operator()(Args... args) && noexcept {
assert(data_);
std::move(data_)(std::move(args)...);
data_ = nullptr;
}
/// Resolves the continuation with the given exception.
///
/// \throws This method never throws an exception.
///
/// \attention This method may only be called once,
/// when the promise is valid operator bool() returns true.
/// Calling this method will invalidate the promise such that
/// subsequent calls to operator bool() will return false.
/// This behaviour is only consistent in promise_base and
/// non type erased promises may behave differently.
/// Invoking an invalid promise_base is undefined!
///
/// \since 2.0.0
void operator()(exception_arg_t tag, exception_t exception) && noexcept {
assert(data_);
std::move(data_)(tag, std::move(exception));
data_ = nullptr;
}
/// Resolves the continuation with the given values.
///
/// \throws This method never throws an exception.
///
/// \attention This method may only be called once,
/// when the promise is valid operator bool() returns true.
/// Calling this method will invalidate the promise such that
/// subsequent calls to operator bool() will return false.
/// This behaviour is only consistent in promise_base and
/// non type erased promises may behave differently.
/// Invoking an invalid promise_base is undefined!
///
/// \since 2.0.0
void set_value(Args... args) noexcept {
// assert(data_);
std::move(data_)(std::move(args)...);
data_ = nullptr;
}
/// Resolves the continuation with the given exception.
///
/// \throws This method never throws an exception.
///
/// \attention This method may only be called once,
/// when the promise is valid operator bool() returns true.
/// Calling this method will invalidate the promise such that
/// subsequent calls to operator bool() will return false.
/// This behaviour is only consistent in promise_base and
/// non type erased promises may behave differently.
/// Invoking an invalid promise_base is undefined!
///
/// \since 2.0.0
void set_exception(exception_t exception) noexcept {
assert(data_);
std::move(data_)(exception_arg_t{}, std::move(exception));
data_ = nullptr;
}
/// Resolves the continuation with the cancellation token which is represented
/// by a default constructed exception_t.
///
/// \throws This method never throws an exception.
///
/// \attention This method may only be called once,
/// when the promise is valid operator bool() returns true.
/// Calling this method will invalidate the promise such that
/// subsequent calls to operator bool() will return false.
/// This behaviour is only consistent in promise_base and
/// non type erased promises may behave differently.
/// Invoking an invalid promise_base is undefined!
///
/// \since 4.0.0
void set_canceled() noexcept {
assert(data_);
std::move(data_)(exception_arg_t{}, exception_t{});
data_ = nullptr;
}
/// Returns true if the continuation is valid (non empty).
///
/// \throws This method never throws an exception.
///
/// \since 4.0.0
explicit operator bool() const noexcept {
return bool(data_);
}
};
/// \}
} // namespace cti
#endif // CONTINUABLE_PROMISE_BASE_HPP_INCLUDED
// #include <continuable/continuable-traverse.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/traversal/container-category.hpp>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace connection {
namespace any {
/// Invokes the callback with the first arriving result
template <typename T>
class any_result_submitter
: public std::enable_shared_from_this<any_result_submitter<T>>,
public util::non_movable {
T callback_;
std::once_flag flag_;
struct any_callback {
std::shared_ptr<any_result_submitter> me_;
template <typename... PartialArgs>
void operator()(PartialArgs&&... args) && {
me_->invoke(std::forward<decltype(args)>(args)...);
}
};
public:
explicit any_result_submitter(T callback) : callback_(std::move(callback)) {
}
/// Creates a submitter which submits it's result to the callback
auto create_callback() {
return any_callback{this->shared_from_this()};
}
private:
// Invokes the callback with the given arguments
template <typename... ActualArgs>
void invoke(ActualArgs&&... args) {
std::call_once(flag_, std::move(callback_),
std::forward<ActualArgs>(args)...);
}
};
struct result_deducer {
template <typename T>
static auto deduce_one(std::false_type, identity<T>) {
static_assert(traits::fail<T>::value,
"Non continuable types except tuple like and homogeneous "
"containers aren't allowed inside an any expression!");
}
template <typename T>
static auto deduce_one(std::true_type, identity<T> id) {
return base::annotation_of(id);
}
template <typename T>
static auto deduce(traversal::container_category_tag<false, false>,
identity<T> id) {
return deduce_one<T>(base::is_continuable<T>{}, id);
}
/// Deduce a homogeneous container
template <bool IsTupleLike, typename T>
static auto deduce(traversal::container_category_tag<true, IsTupleLike>,
identity<T>) {
// Deduce the containing type
using element_t = std::decay_t<decltype(*std::declval<T>().begin())>;
return deduce(traversal::container_category_of_t<element_t>{},
identity<element_t>{});
}
template <typename First, typename... T>
static auto deduce_same_hints(First first, T...) {
static_assert(traits::conjunction<std::is_same<First, T>...>::value,
"The continuables inside the given pack must have the "
"same signature hint!");
return first;
}
template <std::size_t... I, typename T>
static auto deduce_tuple_like(std::integer_sequence<std::size_t, I...>,
identity<T>) {
return deduce_same_hints(deduce(
traversal::container_category_of_t<
std::decay_t<decltype(std::get<I>(std::declval<T>()))>>{},
identity<std::decay_t<decltype(std::get<I>(std::declval<T>()))>>{})...);
}
/// Traverse tuple like container
template <typename T>
static auto deduce(traversal::container_category_tag<false, true>,
identity<T> id) {
constexpr auto const size = std::tuple_size<T>::value;
return deduce_tuple_like(std::make_index_sequence<size>{}, id);
}
};
template <typename Submitter>
struct continuable_dispatcher {
std::shared_ptr<Submitter>& submitter;
template <typename Continuable,
std::enable_if_t<base::is_continuable<
std::decay_t<Continuable>>::value>* = nullptr>
void operator()(Continuable&& continuable) {
// Retrieve a callback from the submitter and attach it to the continuable
std::forward<Continuable>(continuable)
.next(submitter->create_callback())
.done();
}
};
} // namespace any
struct connection_strategy_any_tag {};
template <>
struct is_connection_strategy<connection_strategy_any_tag> // ...
: std::true_type {};
/// Finalizes the any logic of a given connection
template <>
struct connection_finalizer<connection_strategy_any_tag> {
template <typename Connection>
static auto finalize(Connection&& connection, util::ownership ownership) {
constexpr auto const signature = decltype(any::result_deducer::deduce(
traversal::container_category_of_t<std::decay_t<Connection>>{},
identity<std::decay_t<Connection>>{})){};
return base::attorney::create_from(
[connection =
std::forward<Connection>(connection)](auto&& callback) mutable {
using submitter_t =
any::any_result_submitter<std::decay_t<decltype(callback)>>;
// Create the submitter which calls the given callback once at the
// first callback invocation.
auto submitter = std::make_shared<submitter_t>(
std::forward<decltype(callback)>(callback));
traverse_pack(any::continuable_dispatcher<submitter_t>{submitter},
std::move(connection));
},
signature, std::move(ownership));
}
};
} // namespace connection
/// Specialization for a connection annotation
template <>
struct annotation_trait<connection::connection_strategy_any_tag>
: connection::connection_annotation_trait<
connection::connection_strategy_any_tag> {};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONNECTION_ANY_HPP_INCLUDED
// #include <continuable/detail/connection/connection-seq.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_CONNECTION_SEQ_HPP_INCLUDED
#define CONTINUABLE_DETAIL_CONNECTION_SEQ_HPP_INCLUDED
#include <cassert>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-traverse-async.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TRAVERSE_ASYNC_HPP_INCLUDED
#define CONTINUABLE_TRAVERSE_ASYNC_HPP_INCLUDED
#include <utility>
// #include <continuable/detail/traversal/traverse-async.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TRAVERSE_ASYNC_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TRAVERSE_ASYNC_HPP_INCLUDED
#include <atomic>
#include <cassert>
#include <cstddef>
#include <iterator>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/traversal/container-category.hpp>
// #include <continuable/detail/utility/traits.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,
std::integer_sequence<std::size_t, Sequence...>>
: std::common_type<
std::integer_sequence<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,
std::make_index_sequence<End - Begin>>::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<std::decay_t<Frame>, std::decay_t<State>> {
return resume_traversal_callable<std::decay_t<Frame>, std::decay_t<State>>(
std::forward<Frame>(frame), std::forward<State>(state));
}
template <typename T, typename = void>
struct has_head : std::false_type {};
template <typename T>
struct has_head<T, traits::void_t<decltype(std::declval<T>().head())>>
: std::true_type {};
template <typename Visitor, typename... Args>
class async_traversal_frame_data : public Visitor {
std::tuple<Args...> args_;
public:
explicit async_traversal_frame_data(Visitor visitor, Args... args)
: Visitor(std::move(visitor)),
args_(std::make_tuple(std::move(args)...)) {
}
template <typename MapperArg>
explicit async_traversal_frame_data(async_traverse_in_place_tag<Visitor>,
MapperArg&& mapper_arg, Args... args)
: Visitor(std::forward<MapperArg>(mapper_arg)),
args_(std::make_tuple(std::move(args)...)) {
}
/// Returns the arguments of the frame
std::tuple<Args...>& head() noexcept {
return args_;
}
};
template <typename Visitor>
class async_traversal_frame_no_data : public Visitor {
public:
explicit async_traversal_frame_no_data(Visitor visitor)
: Visitor(std::move(visitor)) {
}
template <typename MapperArg>
explicit async_traversal_frame_no_data(async_traverse_in_place_tag<Visitor>,
MapperArg&& mapper_arg)
: Visitor(std::forward<MapperArg>(mapper_arg)) {
}
};
template <typename Visitor, typename... Args>
using data_layout_t =
std::conditional_t<has_head<Visitor>::value,
async_traversal_frame_no_data<Visitor>,
async_traversal_frame_data<Visitor, Args...>>;
/// Stores the visitor and the arguments to traverse
template <typename Visitor, typename... Args>
class async_traversal_frame : public data_layout_t<Visitor, Args...> {
#ifndef NDEBUG
std::atomic<bool> finished_;
#endif // NDEBUG
Visitor& visitor() noexcept {
return *static_cast<Visitor*>(this);
}
Visitor const& visitor() const noexcept {
return *static_cast<Visitor const*>(this);
}
public:
template <typename... T>
explicit async_traversal_frame(T&&... args)
: data_layout_t<Visitor, Args...>(std::forward<T>(args)...)
#ifndef NDEBUG
,
finished_(false)
#endif // NDEBUG
{
}
/// We require a virtual base
virtual ~async_traversal_frame() override = default;
/// Calls the visitor with the given element
template <typename T>
auto traverse(T&& value) -> decltype(visitor()(async_traverse_visit_tag{},
std::forward<T>(value))) {
return 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) {
// Cast the frame up
auto frame = std::static_pointer_cast<async_traversal_frame>(
this->shared_from_this());
// Create a callable object which resumes the current
// traversal when it's called.
auto resumable = make_resume_traversal_callable(
std::move(frame), std::forward<Hierarchy>(hierarchy));
// Invoke the visitor with the current value and the
// callable object to resume the control flow.
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() {
#ifndef NDEBUG
{
bool expected = false;
assert(finished_.compare_exchange_strong(expected, true));
}
#endif // NDEBUG
visitor()(async_traverse_complete_tag{}, std::move(this->head()));
}
};
template <typename Target, std::size_t Begin, std::size_t End>
struct static_async_range {
Target* target_;
constexpr decltype(auto) operator*() const noexcept {
return std::get<Begin>(*target_);
}
template <std::size_t Position>
constexpr auto relocate(std::integral_constant<std::size_t, Position>) const
noexcept {
return static_async_range<Target, Position, End>{target_};
}
constexpr auto 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>
auto make_static_range(T&& element) {
using range_t = static_async_range<std::decay_t<T>, 0U,
std::tuple_size<std::decay_t<T>>::value>;
return range_t{std::addressof(element)};
}
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<std::decay_t<decltype(std::begin(std::declval<T>()))>,
std::decay_t<decltype(std::end(std::declval<T>()))>>;
/// Returns a dynamic range for the given type
template <typename T>
auto make_dynamic_async_range(T&& element) {
using range_t = dynamic_async_range_of_t<T>;
return range_t{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_;
std::tuple<Hierarchy...> hierarchy_;
bool& detached_;
public:
explicit async_traversal_point(Frame frame,
std::tuple<Hierarchy...> hierarchy,
bool& detached)
: frame_(std::move(frame)), hierarchy_(std::move(hierarchy)),
detached_(detached) {
}
// Abort the current control flow
void detach() noexcept {
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, std::decay_t<Parent>, Hierarchy...> {
// Create a new hierarchy which contains the
// the parent (the last traversed element).
auto hierarchy = std::tuple_cat(
std::make_tuple(std::forward<Parent>(parent)), hierarchy_);
return async_traversal_point<Frame, std::decay_t<Parent>, 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
-> traits::void_t<decltype(std::declval<Frame>()->traverse(*current))> {
if (!frame_->traverse(*current)) {
// Store the current call hierarchy into a tuple for
// later re-entrance.
auto hierarchy =
std::tuple_cat(std::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 = std::decay_t<decltype(*current)>;
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(
std::integer_sequence<std::size_t, Sequence...>, Current&& current) {
int dummy[] = {0, (async_traverse_one_checked(current.relocate(
std::integral_constant<std::size_t, Sequence>{})),
0)...};
(void)dummy;
(void)current;
}
/// 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<std::decay_t<Frame>, std::decay_t<Hierarchy>...>;
/// 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, std::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_);
traits::unpack(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_t =
async_traversal_frame<std::decay_t<Visitor>, std::decay_t<Args>...>;
/// The type of the demoted visitor type
using visitor_t = 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>
auto apply_pack_transform_async(Visitor&& visitor, Args&&... args) {
// Provide the frame and visitor type
using types = async_traversal_types<Visitor, Args...>;
using frame_t = typename types::frame_t;
using visitor_t = typename types::visitor_t;
// Check whether the visitor inherits enable_shared_from_this
static_assert(std::is_base_of<std::enable_shared_from_this<visitor_t>,
visitor_t>::value,
"The visitor must inherit std::enable_shared_from_this!");
// Check whether the visitor is virtual destructible
static_assert(std::has_virtual_destructor<visitor_t>::value,
"The visitor must have a virtual destructor!");
// Create the frame on the heap which stores the arguments
// to traverse asynchronous. It persists until the
// traversal frame isn't referenced anymore.
auto frame = std::make_shared<frame_t>(std::forward<Visitor>(visitor),
std::forward<Args>(args)...);
// Create a static range for the top level tuple
auto range = std::make_tuple(make_static_range(frame->head()));
// Create a resumer to start the asynchronous traversal
auto resumer = make_resume_traversal_callable(frame, std::move(range));
// Start the asynchronous traversal
resumer();
// Cast the shared_ptr down to the given visitor type
// for implementation invisibility
return std::static_pointer_cast<visitor_t>(std::move(frame));
}
} // namespace traversal
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TRAVERSE_ASYNC_HPP_INCLUDED
namespace cti {
/// \defgroup Traversal Traversal
/// provides functions to traverse and remap nested packs.
/// \{
/// A tag which is passed to the `operator()` of the visitor
/// if an element is visited synchronously through \ref traverse_pack_async.
///
/// \since 3.0.0
using async_traverse_visit_tag = detail::traversal::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 through \ref traverse_pack_async.
///
/// \since 3.0.0
using async_traverse_detach_tag = detail::traversal::async_traverse_detach_tag;
/// A tag which is passed to the `operator()` of the visitor if the
/// asynchronous pack traversal was finished through \ref traverse_pack_async.
///
/// \since 3.0.0
using async_traverse_complete_tag =
detail::traversal::async_traverse_complete_tag;
/// A tag to identify that a mapper shall be constructed in-place
/// from the first argument passed to \ref traverse_pack_async.
///
/// \since 3.0.0
template <typename T>
using async_traverse_in_place_tag =
detail::traversal::async_traverse_in_place_tag<T>;
/// Traverses the pack with the given visitor in an asynchronous way.
///
/// This function works in the same way as `traverse_pack`,
/// however, we are able to suspend and continue the traversal at
/// later time.
/// Thus we require a visitor callable object which provides three
/// `operator()` overloads as depicted by the code sample below:
/// ```cpp
/// struct my_async_visitor {
/// /// The synchronous overload is called for each object,
/// /// it may return false to suspend the current control flow.
/// /// In that case the overload below is called.
/// template <typename T>
/// bool operator()(async_traverse_visit_tag, T&& element) {
/// return true;
/// }
///
/// /// The asynchronous overload this is called when the
/// /// synchronous overload returned false.
/// /// In addition to the current visited element the overload is
/// /// called with a contnuation callable object which resumes the
/// /// traversal when it's called later.
/// /// The continuation next may be stored and called later or
/// /// dropped completely to abort the traversal early.
/// template <typename T, typename N>
/// void operator()(async_traverse_detach_tag, T&& element, N&& next) {
/// }
///
/// /// The overload is called when the traversal was finished.
/// /// As argument the whole pack is passed over which we
/// /// traversed asynchrnously.
/// template <typename T>
/// void operator()(async_traverse_complete_tag, T&& pack) {
/// }
/// };
/// ```
///
/// \param visitor A visitor object which provides the three `operator()`
/// overloads that were described above.
/// Additionally the visitor must be compatible
/// for referencing it from a `boost::intrusive_ptr`.
/// The visitor should must have a virtual destructor!
///
/// \param pack The arbitrary parameter pack which is traversed
/// asynchronously. Nested objects inside containers and
/// tuple like types are traversed recursively.
///
/// \returns A std::shared_ptr that references an instance of
/// the given visitor object.
///
/// \since 3.0.0
///
/// See `traverse_pack` for a detailed description about the
/// traversal behaviour and capabilities.
///
template <typename Visitor, typename... T>
auto traverse_pack_async(Visitor&& visitor, T&&... pack) {
return detail::traversal::apply_pack_transform_async(
std::forward<Visitor>(visitor), std::forward<T>(pack)...);
}
/// \}
} // namespace cti
#endif // CONTINUABLE_TRAVERSE_ASYNC_HPP_INCLUDED
// #include <continuable/detail/connection/connection-aggregated.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
namespace cti {
namespace detail {
namespace connection {
namespace seq {
/// Connects the left and the right continuable to a sequence
///
/// \note This is implemented in an eager way because we would not gain
/// any profit from chaining sequences lazily.
template <typename Left, typename Right>
auto sequential_connect(Left&& left, Right&& right) {
left.freeze(right.is_frozen());
right.freeze();
return std::forward<Left>(left).then(
[right = std::forward<Right>(right)](auto&&... args) mutable {
return std::move(right).then(
[previous = std::make_tuple(std::forward<decltype(args)>(args)...)](
auto&&... args) mutable {
return std::tuple_cat(
std::move(previous),
std::make_tuple(std::forward<decltype(args)>(args)...));
});
});
}
template <typename Callback, typename Box>
struct sequential_dispatch_data {
Callback callback;
Box box;
};
template <typename Data>
class sequential_dispatch_visitor
: public std::enable_shared_from_this<sequential_dispatch_visitor<Data>>,
public util::non_movable {
Data data_;
public:
explicit sequential_dispatch_visitor(Data&& data) : data_(std::move(data)) {
}
virtual ~sequential_dispatch_visitor() = default;
/// Returns the pack that should be traversed
auto& head() {
return data_.box;
}
template <typename Box, std::enable_if_t<aggregated::is_continuable_box<
std::decay_t<Box>>::value>* = nullptr>
bool operator()(async_traverse_visit_tag, Box&& box) {
if (base::attorney::is_ready(box.peek())) {
// The result can be resolved directly
traits::unpack(
[&](auto&&... args) mutable {
box.assign(std::forward<decltype(args)>(args)...);
},
base::attorney::query(box.fetch()));
return true;
} else {
return false;
}
}
template <typename Box, typename N>
void operator()(async_traverse_detach_tag, Box&& box, N&& next) {
box.fetch()
.then([box = std::addressof(box),
next = std::forward<N>(next)](auto&&... args) mutable {
// Assign the result to the target
box->assign(std::forward<decltype(args)>(args)...);
// Continue the asynchronous sequential traversal
next();
})
.fail([me = this->shared_from_this()](exception_t exception) {
// Abort the traversal when an error occurred
std::move(me->data_.callback)(exception_arg_t{},
std::move(exception));
})
.done();
}
template <typename T>
void operator()(async_traverse_complete_tag, T&& /*pack*/) {
return aggregated::finalize_data(std::move(data_.callback),
std::move(data_.box));
}
};
} // namespace seq
struct connection_strategy_seq_tag {};
template <>
struct is_connection_strategy<connection_strategy_seq_tag> // ...
: std::true_type {};
/// Finalizes the seq logic of a given connection
template <>
struct connection_finalizer<connection_strategy_seq_tag> {
/// Finalizes the all logic of a given connection
template <typename Connection>
static auto finalize(Connection&& connection, util::ownership ownership) {
auto result =
aggregated::box_continuables(std::forward<Connection>(connection));
auto signature = aggregated::hint_of_data<decltype(result)>();
return base::attorney::create_from(
[result = std::move(result)](auto&& callback) mutable {
// The data from which the visitor is constructed in-place
using data_t =
seq::sequential_dispatch_data<std::decay_t<decltype(callback)>,
std::decay_t<decltype(result)>>;
// The visitor type
using visitor_t = seq::sequential_dispatch_visitor<data_t>;
traverse_pack_async(async_traverse_in_place_tag<visitor_t>{},
data_t{std::forward<decltype(callback)>(callback),
std::move(result)});
},
signature, std::move(ownership));
}
};
} // namespace connection
/// Specialization for a connection annotation
template <>
struct annotation_trait<connection::connection_strategy_seq_tag>
: connection::connection_annotation_trait<
connection::connection_strategy_seq_tag> {};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONNECTION_SEQ_HPP_INCLUDED
// #include <continuable/detail/connection/connection.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
# include <experimental/coroutine>
// # include <continuable/detail/other/coroutines.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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.
**/
// Exclude this header when coroutines are not available
#ifndef CONTINUABLE_DETAIL_AWAITING_HPP_INCLUDED
#define CONTINUABLE_DETAIL_AWAITING_HPP_INCLUDED
#include <cassert>
#include <type_traits>
#include <experimental/coroutine>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-result.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
# include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS
namespace cti {
namespace detail {
namespace awaiting {
/// We import the coroutine handle in our namespace
using std::experimental::coroutine_handle;
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
class await_canceled_exception : public std::exception {
public:
await_canceled_exception() noexcept = default;
char const* what() const noexcept override {
return "co_await canceled due to cancellation of the continuation";
}
};
#endif // CONTINUABLE_HAS_EXCEPTIONS
template <typename T>
struct result_from_identity;
template <typename... T>
struct result_from_identity<identity<T...>> {
using result_t = result<T...>;
};
/// An object which provides the internal buffer and helper methods
/// for waiting on a continuable in a stackless coroutine.
template <typename Continuable>
class awaitable {
using hint_t = decltype(base::annotation_of(identify<Continuable>{}));
using result_t = typename result_from_identity<hint_t>::result_t;
/// The continuable which is invoked upon suspension
Continuable continuable_;
/// A cache which is used to pass the result of the continuation
/// to the coroutine.
result_t result_;
public:
explicit constexpr awaitable(Continuable&& continuable)
: continuable_(std::move(continuable)) {
// If the continuable is ready resolve the result from the
// continuable immediately.
if (base::attorney::is_ready(continuable_)) {
assert(result_.is_empty());
result_ = base::attorney::query(std::move(continuable_));
}
}
/// Return whether the continuable can provide its result instantly,
/// which also means its execution is side-effect free.
bool await_ready() const noexcept {
return !result_.is_empty();
}
/// Suspend the current context
// TODO Convert this to an r-value function once possible
void await_suspend(coroutine_handle<> h) {
assert(result_.is_empty());
// Forward every result to the current awaitable
std::move(continuable_)
.next([h, this](auto&&... args) mutable {
assert(result_.is_empty());
result_ = result_t::from(std::forward<decltype(args)>(args)...);
h.resume();
})
.done();
}
/// Resume the coroutine represented by the handle
typename result_t::value_t await_resume() noexcept(false) {
if (result_.is_value()) {
// When the result was resolved return it
return std::move(result_).get_value();
}
assert(result_.is_exception());
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
if (exception_t e = result_.get_exception()) {
std::rethrow_exception(std::move(e));
} else {
throw await_canceled_exception();
}
#else // CONTINUABLE_HAS_EXCEPTIONS
// Returning error types from co_await isn't supported!
CTI_DETAIL_TRAP();
#endif // CONTINUABLE_HAS_EXCEPTIONS
}
};
/// Converts a continuable into an awaitable object as described by
/// the C++ coroutine TS.
template <typename T>
constexpr auto create_awaiter(T&& continuable) {
return awaitable<std::decay_t<T>>(std::forward<T>(continuable));
}
/// This makes it possible to take the coroutine_handle over on suspension
struct handle_takeover {
coroutine_handle<>& handle_;
bool await_ready() noexcept {
return false;
}
void await_suspend(coroutine_handle<> handle) noexcept {
handle_ = handle;
}
void await_resume() noexcept {}
};
/// The type which is passed to the compiler that describes the properties
/// of a continuable_base used as coroutine promise type.
template <typename Continuable, typename Promise, typename... Args>
struct promise_type;
/// Implements the resolving method return_void and return_value accordingly
template <typename Base>
struct promise_resolver_base;
template <typename Continuable, typename Promise>
struct promise_resolver_base<promise_type<Continuable, Promise>> {
void return_void() {
auto me = static_cast<promise_type<Continuable, Promise>*>(this);
me->promise_.set_value();
}
};
template <typename Continuable, typename Promise, typename T>
struct promise_resolver_base<promise_type<Continuable, Promise, T>> {
void return_value(T value) {
auto me = static_cast<promise_type<Continuable, Promise, T>*>(this);
me->promise_.set_value(std::move(value));
}
};
template <typename Continuable, typename Promise, typename... Args>
struct promise_resolver_base<promise_type<Continuable, Promise, Args...>> {
template <typename T>
void return_value(T&& tuple_like) {
auto me = static_cast<promise_type<Continuable, Promise, Args...>*>(this);
traits::unpack(std::move(me->promise_), std::forward<T>(tuple_like));
}
};
template <typename Continuable, typename Promise, typename... Args>
struct promise_type
: promise_resolver_base<promise_type<Continuable, Promise, Args...>> {
coroutine_handle<> handle_;
Promise promise_;
explicit promise_type() = default;
Continuable get_return_object() {
return [this](auto&& promise) {
promise_ = std::forward<decltype(promise)>(promise);
handle_.resume();
};
}
handle_takeover initial_suspend() {
return {handle_};
}
std::experimental::suspend_never final_suspend() {
return {};
}
void unhandled_exception() noexcept {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
try {
std::rethrow_exception(std::current_exception());
} catch (await_canceled_exception const&) {
promise_.set_canceled();
} catch (...) {
promise_.set_exception(std::current_exception());
}
#else // CONTINUABLE_HAS_EXCEPTIONS
// Returning exception types from a coroutine isn't supported
CTI_DETAIL_TRAP();
#endif // CONTINUABLE_HAS_EXCEPTIONS
}
};
} // namespace awaiting
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_UTIL_HPP_INCLUDED
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
namespace cti {
/// \defgroup Base Base
/// provides classes and functions to create continuable_base objects.
/// \{
/// Deduces to a true_type if the given type is a continuable_base.
///
/// \since 3.0.0
template <typename T>
using is_continuable = detail::base::is_continuable<T>;
/// The main class of the continuable library, it provides the functionality
/// for chaining callbacks and continuations together to a unified hierarchy.
///
/// The most important method is the cti::continuable_base::then() method,
/// which allows to attach a callback to the continuable.
///
/// Use the continuable types defined in `continuable/continuable.hpp`,
/// in order to use this class.
///
/// \tparam Data The internal data which is used to store the current
/// continuation and intermediate lazy connection result.
///
/// \tparam Annotation The internal data used to store the current signature
/// hint or strategy used for combining lazy connections.
///
/// \note Nearly all methods of the cti::continuable_base are required to be
/// called as r-value. This is required because the continuable carries
/// variables which are consumed when the object is transformed as part
/// of a method call.
///
/// \attention The continuable_base objects aren't intended to be stored.
/// If you want to store a continuble_base you should always
/// call the continuable_base::freeze method for disabling the
/// invocation on destruction.
///
/// \since 1.0.0
template <typename Data, typename Annotation>
class continuable_base {
/// \cond false
using ownership = detail::util::ownership;
using annotation_trait = detail::annotation_trait<Annotation>;
template <typename, typename>
friend class continuable_base;
friend struct detail::base::attorney;
// The continuation type or intermediate result
Data data_;
// The transferable state which represents the validity of the object
ownership ownership_;
/// \endcond
/// Constructor accepting the data object while erasing the annotation
explicit continuable_base(Data data, ownership ownership)
: data_(std::move(data))
, ownership_(std::move(ownership)) {}
public:
/// Constructor accepting the data object while erasing the annotation
explicit continuable_base(Data data)
: data_(std::move(data)) {}
/// Constructor accepting any object convertible to the data object,
/// while erasing the annotation
template <typename OtherData,
std::enable_if_t<detail::base::can_accept_continuation<
Data, Annotation,
detail::traits::unrefcv_t<OtherData>>::value>* = nullptr>
/* implicit */ continuable_base(OtherData&& data)
: data_(
detail::base::proxy_continuable<Annotation,
detail::traits::unrefcv_t<OtherData>>(
std::forward<OtherData>(data))) {}
/// Constructor taking the data of other continuable_base objects
/// while erasing the hint.
///
/// This constructor makes it possible to replace the internal data object of
/// the continuable by any object which is useful for type-erasure.
template <typename OData,
std::enable_if_t<std::is_convertible<
detail::traits::unrefcv_t<OData>, Data>::value>* = nullptr>
/* implicit */ continuable_base(continuable_base<OData, Annotation>&& other)
: data_(std::move(other).consume()) {}
/// Constructor taking the data of other continuable_base objects
/// while erasing the hint.
///
/// This constructor makes it possible to replace the internal data object of
/// the continuable by any object which is useful for type-erasure.
template <typename OData, typename OAnnotation>
/* implicit */ continuable_base(continuable_base<OData, OAnnotation>&& other)
: continuable_base(std::move(other).finish().consume()) {}
/// \cond false
continuable_base(continuable_base&&) = default;
continuable_base(continuable_base const&) = delete;
continuable_base& operator=(continuable_base&&) = default;
continuable_base& operator=(continuable_base const&) = delete;
/// \endcond
/// The destructor automatically invokes the continuable_base
/// if it wasn't consumed yet.
///
/// In order to invoke the continuable early you may call the
/// continuable_base::done() method.
///
/// The continuable_base::freeze method disables the automatic
/// invocation on destruction without invalidating the object.
///
/// \since 1.0.0
~continuable_base() {
if (ownership_.is_acquired() && !ownership_.is_frozen()) {
std::move(*this).done();
}
assert((!ownership_.is_acquired() || ownership_.is_frozen()) &&
"Ownership should be released!");
}
/// Main method of the continuable_base to chain the current continuation
/// with a new callback.
///
/// \param callback The callback which is used to process the current
/// asynchronous result on arrival. The callback is required to accept
/// the current result at least partially (or nothing of the result).
/// ```cpp
/// (http_request("github.com") && http_request("atom.io"))
/// .then([](std::string github, std::string atom) {
/// // We use the whole result
/// });
///
/// (http_request("github.com") && http_request("atom.io"))
/// .then([](std::string github) {
/// // We only use the result partially
/// });
///
/// (http_request("github.com") && http_request("atom.io"))
/// .then([] {
/// // We discard the result
/// });
/// ```
///
/// \param executor The optional executor which is used to dispatch
/// the callback. The executor needs to accept callable objects
/// callable through an `operator()` through its operator() itself.
/// The executor can be move-only, but it's not required to.
/// The default executor which is used when omitting the argument
/// dispatches the callback on the current executing thread.
/// Consider the example shown below:
/// ```cpp
/// auto executor = [](auto&& work) {
/// // Dispatch the work here or forward it to an executor of
/// // your choice.
/// std::forward<decltype(work)>(work)();
/// };
///
/// http_request("github.com")
/// .then([](std::string github) {
/// // Do something...
/// }, executor);
/// ```
///
/// \returns Returns a continuable_base with an asynchronous return type
/// depending on the return value of the callback:
/// | Callback returns | Resulting type |
/// | : ---------------------- : | : --------------------------------------- |
/// | `void` | `continuable_base with <>` |
/// | `Arg` | `continuable_base with <Arg>` |
/// | `std::pair<First, Second>` | `continuable_base with <First, Second>` |
/// | `std::tuple<Args...>` | `continuable_base with <Args...>` |
/// | `cti::result<Args...>` | `continuable_base with <Args...>` |
/// | `continuable_base<Arg...>` | `continuable_base with <Args...>` |
/// Which means the result type of the continuable_base is equal to
/// the plain types the callback returns (`std::tuple` and
/// `std::pair` arguments are unwrapped).
/// A single continuable_base as argument is resolved and the result
/// type is equal to the resolved continuable_base.
/// A cti::result can be used to cancel the continuation or to
/// transition to the exception handler.
/// The special unwrapping of types can be disabled through wrapping
/// such objects through a call to cti::make_plain.
/// Consider the following examples:
/// ```cpp
/// http_request("github.com")
/// .then([](std::string github) { return; })
/// .then([] { }); // <void>
///
/// http_request("github.com")
/// .then([](std::string github) { return 0; })
/// .then([](int a) { }); // <int>
///
/// http_request("github.com")
/// .then([](std::string github) { return std::make_pair(1, 2); })
/// .then([](int a, int b) { }); // <int, int>
///
/// http_request("github.com")
/// .then([](std::string github) { return std::make_tuple(1, 2, 3); })
/// .then([](int a, int b, int c) { }); // <int, int, int>
///
/// http_request("github.com")
/// .then([](std::string github) { return http_request("atom.io"); })
/// .then([](std::string atom) { }); // <std::string>
///
/// http_request("example.com")
/// .then([](std::string content) -> result<std::string> {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// })
/// .fail([] -> result<std::string> {
/// return recover("Hello World!");
/// })
/// .then([](std::string content) -> result<std::string> {
/// return cancel();
/// })
/// ```
///
/// \since 1.0.0
template <typename T, typename E = detail::types::this_thread_executor_tag>
auto then(T&& callback,
E&& executor = detail::types::this_thread_executor_tag{}) && {
return detail::base::chain_continuation<detail::base::handle_results::yes,
detail::base::handle_errors::no>(
std::move(*this).finish(), std::forward<T>(callback),
std::forward<E>(executor));
}
/// Additional overload of the continuable_base::then() method
/// which is accepting a continuable_base itself.
///
/// \param continuation A continuable_base reflecting the continuation
/// which is used to continue the call hierarchy.
/// The result of the current continuable is discarded and the given
/// continuation is invoked as shown below.
/// ```cpp
/// http_request("github.com")
/// .then(http_request("atom.io"))
/// .then([](std::string atom) {
/// // ...
/// });
/// ```
///
/// \returns Returns a continuable_base representing the next asynchronous
/// result to continue within the asynchronous call hierarchy.
///
/// \since 1.0.0
template <typename OData, typename OAnnotation>
auto then(continuable_base<OData, OAnnotation>&& continuation) && {
return std::move(*this).then(
detail::base::wrap_continuation(std::move(continuation).finish()));
}
/// Main method of the continuable_base to catch exceptions and error codes
/// in case the asynchronous control flow failed and was resolved
/// through an error code or exception.
///
/// \param callback The callback which is used to process the current
/// asynchronous error result on arrival.
/// In case the continuable_base is using exceptions,
/// the usage is as shown below:
///
/// ```cpp
/// http_request("github.com")
/// .then([](std::string github) { })
/// .fail([](std::exception_ptr ep) {
/// // Check whether the exception_ptr is valid (not default constructed)
/// // if bool(ep) == false this means that the operation was cancelled
/// // by the user or application (promise.set_canceled() or
/// // make_cancelling_continuable()).
/// if (ep) {
/// // Handle the error here
/// try {
/// std::rethrow_exception(ep);
/// } catch (std::exception& e) {
/// e.what(); // Handle the exception
/// }
/// }
/// });
/// ```
/// In case exceptions are disabled, `std::error_condition` is
/// used as error result instead of `std::exception_ptr`.
/// ```cpp
/// http_request("github.com")
/// .then([](std::string github) { })
/// .fail([](std::error_condition error) {
/// error.message(); // Handle the error here
/// });
/// ```
///
/// \param executor The optional executor which is used to dispatch
/// the callback. See the description in `then` above.
///
/// \returns Returns a continuable_base with an asynchronous return type
/// depending on the previous result type.
///
/// \attention The given exception type exception_t can be passed to the
/// handler in a default constructed state <br>`bool(e) == false`.
/// This always means that the operation was cancelled by the user,
/// possibly through:
/// - \ref promise_base::set_canceled
/// - \ref make_cancelling_continuable
/// - \ref result::set_canceled
/// - \ref cancel<br>
/// In that case the exception can be ignored safely (but it is
/// recommended not to proceed, although it is possible to
/// recover from the cancellation).
///
/// \since 2.0.0
template <typename T, typename E = detail::types::this_thread_executor_tag>
auto fail(T&& callback,
E&& executor = detail::types::this_thread_executor_tag{}) && {
return detail::base::chain_continuation<
detail::base::handle_results::no, detail::base::handle_errors::forward>(
std::move(*this).finish(),
detail::base::strip_exception_arg(std::forward<T>(callback)),
std::forward<E>(executor));
}
/// Additional overload of the continuable_base::fail() method
/// which is accepting a continuable_base itself.
///
/// \param continuation A continuable_base reflecting the continuation
/// which is used to continue the call hierarchy on errors.
/// The result of the current continuable is discarded and the given
/// continuation is invoked as shown below.
/// ```cpp
/// http_request("github.com")
/// .fail(http_request("atom.io"))
/// ```
///
/// \returns Returns a continuable_base with an asynchronous return type
/// depending on the previous result type.
///
/// \since 2.0.0
template <typename OData, typename OAnnotation>
auto fail(continuable_base<OData, OAnnotation>&& continuation) && {
return std::move(*this) //
.fail([continuation = std::move(continuation).freeze()] //
(exception_t) mutable {
std::move(continuation).done(); //
});
}
/// A method which allows to use an overloaded callable for the error
/// as well as the valid result path.
///
/// \param callback The callback which is used to process the current
/// asynchronous result and error on arrival.
///
/// ```cpp
/// struct my_callable {
/// void operator() (std::string result) {
/// // ...
/// }
/// void operator() (cti::exception_arg_t, cti::exception_t) {
/// // ...
/// }
///
/// // Will receive errors and results
/// http_request("github.com")
/// .next(my_callable{});
/// ```
///
/// \param executor The optional executor which is used to dispatch
/// the callback. See the description in `then` above.
///
/// \returns Returns a continuable_base with an asynchronous return type
/// depending on the current result type.
///
/// \since 2.0.0
template <typename T, typename E = detail::types::this_thread_executor_tag>
auto next(T&& callback,
E&& executor = detail::types::this_thread_executor_tag{}) && {
return detail::base::chain_continuation<
detail::base::handle_results::yes,
detail::base::handle_errors::forward>(std::move(*this).finish(),
std::forward<T>(callback),
std::forward<E>(executor));
}
/// Returns a continuable_base which will have its signature converted
/// to the given Args.
///
/// A signature can only be converted if it can be partially applied
/// from the previous one as shown below:
/// ```cpp
/// continuable<long> c = make_ready_continuable(0, 1, 2).as<long>();
/// ```
///
/// \returns Returns a continuable_base with an asynchronous return type
/// matching the given Args.
///
/// \since 4.0.0
template <typename... Args>
auto as() && {
return std::move(*this).then(detail::base::convert_to<Args...>{});
}
/// A method which allows to apply a callable object to this continuable.
///
/// \param transform A callable objects that transforms a continuable
/// to a different object.
///
/// \returns Returns the result of the given transform when this
/// continuable is passed into it.
///
/// \since 4.0.0
template <typename T>
auto apply(T&& transform) && {
return std::forward<T>(transform)(std::move(*this).finish());
}
/// The pipe operator | is an alias for the continuable::then method.
///
/// \param right The argument on the right-hand side to connect.
///
/// \returns See the corresponding continuable_base::then method for the
/// explanation of the return type.
///
/// \since 2.0.0
template <typename T>
auto operator|(T&& right) && {
return std::move(*this).then(std::forward<T>(right));
}
/// Invokes both continuable_base objects parallel and calls the
/// callback with the result of both continuable_base objects.
///
/// \param right The continuable on the right-hand side to connect.
///
/// \returns Returns a continuable_base with a result type matching
/// the result of the left continuable_base combined with the
/// right continuable_base.
/// The returned continuable_base will be in an intermediate lazy
/// state, further calls to its continuable_base::operator &&
/// will add other continuable_base objects to the current
/// invocation chain.
/// ```cpp
/// (http_request("github.com") && http_request("atom.io"))
/// .then([](std::string github, std::string atom) {
/// // ...
/// });
///
/// auto request = http_request("github.com") && http_request("atom.io");
/// (std::move(request) && http_request("travis-ci.org"))
/// // All three requests are invoked in parallel although we added
/// // the request to "travis-ci.org" last.
/// .then([](std::string github, std::string atom, std::string travis) {
/// // ...
/// });
/// ```
///
/// \note The continuable_base objects are invoked all at onve,
/// because the `all` strategy tries to resolve
/// the continuations as fast as possible.
/// Sequential invocation is also supported through the
/// continuable_base::operator>> method.
///
/// \since 1.0.0
template <typename OData, typename OAnnotation>
auto operator&&(continuable_base<OData, OAnnotation>&& right) && {
return detail::connection::connect(
detail::connection::connection_strategy_all_tag{}, std::move(*this),
std::move(right));
}
/// Invokes both continuable_base objects parallel and calls the
/// callback once with the first result available.
///
/// \param right The continuable on the right-hand side to connect.
/// The right continuable is required to have the same
/// result as the left connected continuable_base.
///
/// \returns Returns a continuable_base with a result type matching
/// the combined result which of all connected
/// continuable_base objects.
/// The returned continuable_base will be in an intermediate lazy
/// state, further calls to its continuable_base::operator ||
/// will add other continuable_base objects to the current
/// invocation chain.
/// ```cpp
/// (http_request("github.com") || http_request("atom.io"))
/// .then([](std::string github_or_atom) {
/// // ...
/// });
///
/// (make_ready_continuable(10, 'A') || make_ready_continuable(29, 'B'))
/// .then([](int a, char b) {
/// // ...
/// });
/// ```
///
/// \note The continuable_base objects are invoked all at once,
/// however, the callback is only called once with
/// the first result or exception which becomes available.
///
/// \since 1.0.0
template <typename OData, typename OAnnotation>
auto operator||(continuable_base<OData, OAnnotation>&& right) && {
return detail::connection::connect(
detail::connection::connection_strategy_any_tag{}, std::move(*this),
std::move(right));
}
/// Invokes both continuable_base objects sequential and calls the
/// callback with the result of both continuable_base objects.
///
/// \param right The continuable on the right-hand side to connect.
///
/// \returns Returns a continuable_base with a result type matching
/// the result of the left continuable_base combined with the
/// right continuable_base.
/// ```cpp
/// (http_request("github.com") >> http_request("atom.io"))
/// .then([](std::string github, std::string atom) {
/// // The callback is called with the result of both requests,
/// // however, the request to atom was started after the request
/// // to github was finished.
/// });
/// ```
///
/// \note The continuable_base objects are invoked sequential one after
/// the previous one was finished. Parallel invocation is also
/// supported through the continuable_base::operator && method.
///
/// \since 1.0.0
template <typename OData, typename OAnnotation>
auto operator>>(continuable_base<OData, OAnnotation>&& right) && {
return detail::connection::seq::sequential_connect(std::move(*this),
std::move(right));
}
/// Invokes the continuation chain manually even before the
/// cti::continuable_base is destructed. This will release the object.
///
/// \see continuable_base::~continuable_base() for further details about
/// the continuation invocation on destruction.
///
/// \attention This method will trigger an assertion if the
/// continuable_base was released already.
///
/// \since 1.0.0
void done() && {
detail::base::finalize_continuation(std::move(*this).finish());
}
/// Materializes the continuation expression template and finishes
/// the current applied strategy such that the resulting continuable
/// will always be a concrete type and Continuable::is_concrete holds.
///
/// This can be used in the case where we are chaining continuations lazily
/// through a strategy, for instance when applying operators for
/// expressing connections and then want to return a materialized
/// continuable_base which uses the strategy respectively.
/// ```cpp
/// auto do_both() {
/// return (wait(10s) || wait_key_pressed(KEY_SPACE)).finish();
/// }
///
/// // Without a call to finish() this would lead to
/// // an unintended evaluation strategy:
/// do_both() || wait(5s);
/// ```
///
/// \note When using a type erased continuable_base such as
/// `continuable<...>` this method doesn't need to be called
/// since the continuable_base is materialized automatically
/// on conversion.
///
/// \since 4.0.0
auto finish() && {
return annotation_trait::finish(std::move(*this));
}
/// Returns true when the continuable can provide its result immediately,
/// and its lazy invocation would be side-effect free.
///
/// \since 4.0.0
bool is_ready() const noexcept {
return annotation_trait::is_ready(*this);
}
/// Invalidates the continuable and returns its immediate invocation result.
///
/// This method can be used to specialize the asynchronous control flow
/// based on whether the continuable_base is_ready at every time,
/// which is true for a continuable created through the following functions:
/// - make_ready_continuable
/// - make_exceptional_continuable
///
/// \returns A result<Args...> where Args... represent the current
/// asynchronous parameters or the currently stored exception.
///
/// \attention unpack requires that continuable_base::is_ready returned true
/// in a previous check, otherwise its behaviour is unspecified.
///
/// \since 4.0.0
auto unpack() && {
assert(ownership_.is_acquired());
assert(is_ready());
return detail::base::attorney::query(std::move(*this).finish());
}
/// Predicate to check whether the cti::continuable_base is frozen or not.
///
/// \returns Returns true when the continuable_base is frozen.
///
/// \see continuable_base::freeze for further details.
///
/// \attention This method will trigger an assertion if the
/// continuable_base was released already.
///
/// \since 1.0.0
bool is_frozen() const noexcept {
assert_acquired();
return ownership_.is_frozen();
}
/// Prevents the automatic invocation of the continuation chain
/// which happens on destruction of the continuable_base.
/// You may still invoke the chain through the continuable_base::done method.
///
/// This is useful for storing a continuable_base inside a continuation
/// chain while storing it for further usage.
///
/// \param enabled Indicates whether the freeze is enabled or disabled.
///
/// \see continuable_base::~continuable_base() for further details about
/// the continuation invocation on destruction.
///
/// \attention This method will trigger an assertion if the
/// continuable_base was released already.
///
/// \since 1.0.0
continuable_base& freeze(bool enabled = true) & noexcept {
ownership_.freeze(enabled);
return *this;
}
/// \copydoc continuable_base::freeze
continuable_base&& freeze(bool enabled = true) && noexcept {
ownership_.freeze(enabled);
return std::move(*this);
}
/// \cond false
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
/// \endcond
/// Implements the operator for awaiting on continuables using `co_await`.
///
/// The operator is only enabled if `CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE`
/// is defined and the toolchain supports experimental coroutines.
///
/// The return type of the `co_await` expression is specified as following:
/// | Continuation type | co_await returns |
/// | : ------------------------------- | : -------------------------------- |
/// | `continuable_base with <>` | `void` |
/// | `continuable_base with <Arg>` | `Arg` |
/// | `continuable_base with <Args...>` | `std::tuple<Args...>` |
///
/// When exceptions are used the usage is as intuitive as shown below:
/// ```cpp
/// // Handling the exception isn't required and
/// // the try catch clause may be omitted.
/// try {
/// std::string response = co_await http_request("github.com");
/// } (std::exception& e) {
/// e.what();
/// }
/// ```
///
/// In case the library is configured to use error codes or a custom
/// exception type the return type of the co_await expression is changed.
/// The result is returned through a cti::result<...>.
/// | Continuation type | co_await returns |
/// | : ------------------------------- | : -------------------------------- |
/// | `continuable_base with <>` | `result<void>` |
/// | `continuable_base with <Arg>` | `result<Arg>` |
/// | `continuable_base with <Args...>` | `result<Args...>` |
///
/// \note Using continuable_base as return type for coroutines
/// is supported. The coroutine is initially stopped and
/// resumed when the continuation is requested in order to
/// keep the lazy evaluation semantics of the continuable_base.
/// ```cpp
/// cti::continuable<> resolve_async_void() {
/// co_await http_request("github.com");
/// // ...
/// co_return;
/// }
///
/// cti::continuable<int> resolve_async() {
/// co_await http_request("github.com");
/// // ...
/// co_return 0;
/// }
/// ```
/// It's possible to return multiple return values from coroutines
/// by wrapping those in a tuple like type:
/// ```cpp
/// cti::continuable<int, int, int> resolve_async_multiple() {
/// co_await http_request("github.com");
/// // ...
/// co_return std::make_tuple(0, 1, 2);
/// }
/// ```
///
/// \since 2.0.0
auto operator co_await() && {
return detail::awaiting::create_awaiter(std::move(*this).finish());
}
/// \cond false
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
/// \endcond
private:
void release() noexcept {
ownership_.release();
}
Data&& consume() && {
assert_acquired();
release();
return std::move(data_);
}
void assert_acquired() const {
assert(ownership_.is_acquired() && "Tried to use a released continuable!");
}
};
/// Creates a continuable_base from a promise/callback taking function.
///
/// \tparam Args The types (signature hint) the given promise is resolved with.
/// * **Some arguments** indicate the types the promise will be invoked with.
/// ```cpp
/// auto ct = cti::make_continuable<int, std::string>([](auto&& promise) {
/// promise.set_value(200, "<html>...</html>");
/// });
/// ```
/// * `void` **as argument** indicates that the promise will be invoked
/// with no arguments:
/// ```cpp
/// auto ct = cti::make_continuable<void>([](auto&& promise) {
/// promise.set_value();
/// });
/// ```
/// * **No arguments** Since version 3.0.0 make_continuable always requires
/// to be given valid arguments!
/// You should always give the type hint a callback is called with because
/// it's required for intermediate actions like connecting continuables.
/// You may omit the signature hint if you are erasing the type of
/// the continuable right after creation.
/// ```cpp
/// // This won't work because the arguments are missing:
/// auto ct = cti::make_continuable([](auto&& promise) {
/// promise.set_value(0.f, 'c');
/// });
///
/// // However, you are allowed to do this:
/// cti::continuable<float, char> ct = [](auto&& promise) {
/// promise.set_value(callback)(0.f, 'c');
/// };
/// ```
///
/// \param continuation The continuation the continuable is created from.
/// The continuation must be a callable type accepting a callback parameter
/// which represents the object invokable with the asynchronous result of this
/// continuable.
/// ```cpp
/// auto ct = cti::make_continuable<std::string>([](auto&& promise) {
/// promise.set_value("result");
/// });
/// ```
/// The callback may be stored or moved.
/// In some cases the callback may be copied if supported by the underlying
/// callback chain, in order to invoke the call chain multiple times.
/// It's recommended to accept any callback instead of erasing it.
/// ```cpp
/// // Good practice:
/// auto ct = cti::make_continuable<std::string>([](auto&& promise) {
/// promise.set_value("result");
/// });
///
/// // Good practice using a callable object:
/// struct Continuation {
/// template<typename T>
/// void operator() (T&& continuation) && {
/// // ...
/// }
/// }
///
/// auto ct = cti::make_continuable<std::string>(Continuation{});
///
/// // Bad practice (because of unnecessary type erasure):
/// auto ct = cti::make_continuable<std::string>(
/// [](cti::promise<std::string> promise) {
/// promise.set_value("result");
/// });
/// ```
///
/// \returns A continuable_base with unspecified template parameters which
/// wraps the given continuation.
/// In order to convert the continuable_base to a known type
/// you need to apply type erasure through the
/// \link cti::continuable continuable\endlink or
/// \link cti::promise promise\endlink facilities.
///
/// \note You should always turn the callback/promise into a r-value if possible
/// (`std::move` or `std::forward`) for qualifier correct invokation.
/// Additionally it's important to know that all continuable promises
/// are callbacks and just expose their call operator nicely through
/// \link cti::promise_base::set_value set_value \endlink and
/// \link cti::promise_base::set_exception set_exception \endlink.
///
/// \since 1.0.0
template <typename... Args, typename Continuation>
constexpr auto make_continuable(Continuation&& continuation) {
static_assert(sizeof...(Args) > 0,
"Since version 3.0.0 make_continuable requires an exact "
"signature! If you did intend to create a void continuable "
"use make_continuable<void>(...). Continuables with an exact "
"signature may be created through make_continuable<Args...>.");
return detail::base::attorney::create_from(
std::forward<Continuation>(continuation),
typename detail::hints::from_args<Args...>::type{},
detail::util::ownership{});
}
/// Returns a continuable_base with no result which instantly resolves
/// the promise with no values.
///
/// \attention Usually using this function isn't needed at all since
/// the continuable library is capable of working with
/// plain values in most cases.
/// Try not to use it since it causes unnecessary recursive
/// function calls.
///
/// \since 3.0.0
template <typename... Args>
auto make_ready_continuable(Args&&... args) {
return detail::base::attorney::create_from_raw(
detail::base::ready_continuation<detail::traits::unrefcv_t<Args>...>(
result<detail::traits::unrefcv_t<Args>...>::from(
std::forward<Args>(args)...)),
detail::identity<detail::traits::unrefcv_t<Args>...>{},
detail::util::ownership{});
}
/// Returns a continuable_base with the parameterized result which instantly
/// resolves the promise with the given error type.
///
/// See an example below:
/// ```cpp
/// std::logic_error exception("Some issue!");
/// auto ptr = std::make_exception_ptr(exception);
/// auto ct = cti::make_exceptional_continuable<int>(ptr);
/// ```
///
/// \tparam Args The fake signature of the returned continuable.
///
/// \since 3.0.0
template <typename... Args, typename Exception>
constexpr auto make_exceptional_continuable(Exception&& exception) {
static_assert(sizeof...(Args) > 0,
"Requires at least one type for the fake signature!");
using hint_t = typename detail::hints::from_args<Args...>::type;
using ready_continuation_t = typename detail::base::
ready_continuation_from_hint<hint_t>::type;
using result_t = typename detail::base::result_from_hint<hint_t>::type;
return detail::base::attorney::create_from_raw(
ready_continuation_t(result_t::from(exception_arg_t{},
std::forward<Exception>(exception))),
hint_t{}, detail::util::ownership{});
}
/// Returns a continuable_base with the parameterized result which never
/// resolves its promise and thus cancels the asynchronous continuation chain
/// through throwing a default constructed exception_t.
///
/// This can be used to cancel an asynchronous continuation chain when
/// returning a continuable_base from a handler where other paths could
/// possibly continue the asynchronous chain. See an example below:
/// ```cpp
/// do_sth().then([weak = this->weak_from_this()]() -> continuable<> {
/// if (auto me = weak.lock()) {
/// return do_sth_more();
/// } else {
/// // Abort the asynchronous continuation chain since the
/// // weakly referenced object expired previously.
/// return make_cancelling_continuable<void>();
/// }
/// });
/// ```
/// The default unhandled exception handler ignores exception types
/// that don't evaluate to true when being converted to a bool.
/// This saves expensive construction of std::exception_ptr or similar types,
/// where only one exception type is used for signaling the cancellation.
///
/// \tparam Signature The fake signature of the returned continuable.
///
/// \since 4.0.0
template <typename... Signature>
auto make_cancelling_continuable() {
static_assert(sizeof...(Signature) > 0,
"Requires at least one type for the fake signature!");
return make_exceptional_continuable<Signature...>(exception_t{});
}
/// Can be used to disable the special meaning for a returned value in
/// asynchronous handler functions.
///
/// Several types have a special meaning when being returned from a callable
/// passed to asynchronous handler functions like:
/// - continuable_base::then
/// - continuable_base::fail
/// - continuable_base::next
///
/// For instance such types are std::tuple, std::pair and cti::result.
///
/// Wrapping such an object through a call to make_plain disables the special
/// meaning for such objects as shown below:
/// ```cpp
/// continuable<result<int, int> c = http_request("example.com")
/// .then([](std::string content) {
/// return make_plain(make_result(0, 1));
/// })
/// ```
///
/// \since 4.0.0
///
template <typename T>
auto make_plain(T&& value) {
return plain_t<detail::traits::unrefcv_t<T>>(std::forward<T>(value));
}
/// Can be used to recover to from a failure handler,
/// the result handler which comes after will be called with the
/// corresponding result.
///
/// The \ref exceptional_result returned by this function can be returned
/// from any result or failure handler in order to rethrow the exception.
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) {
/// return recover(1, 2);
/// })
/// .fail([](cti::exception_t exception) {
/// return recover(1, 2);
/// })
/// .then([](int a, int b) {
/// // Recovered from the failure
/// })
/// ```
/// A corresponding \ref result is returned by \ref recover
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) -> cti::result<int, int> {
/// return recover(1, 2);
/// })
/// .fail([](cti::exception_t exception) -> cti::result<int, int> {
/// return recover(1, 2);
/// })
/// .then([](int a, int b) -> cti::result<int, int> {
/// // Recovered from the failure
/// })
/// ```
///
/// \since 4.0.0
///
template <typename... Args>
result<detail::traits::unrefcv_t<Args>...> recover(Args&&... args) {
return make_result(std::forward<Args>(args)...);
}
/// Can be used to rethrow an exception to the asynchronous continuation chain,
/// the failure handler which comes after will be called with the
/// corresponding exception.
///
/// The \ref exceptional_result returned by this function can be returned
/// from any result or failure handler in order to rethrow the exception.
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// })
/// .fail([](cti::exception_t exception) {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// })
/// .next([](auto&&...) {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// });
/// ```
/// The returned \ref exceptional_result is convertible to
/// any \ref result as shown below:
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) -> cti::result<> {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// })
/// .fail([](cti::exception_t exception) -> cti::result<> {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// })
/// .next([](auto&&...) -> cti::result<> {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// });
/// ```
///
/// \since 4.0.0
///
// NOLINTNEXTLINE(performance-unnecessary-value-param)
inline exceptional_result rethrow(exception_t exception) {
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
return exceptional_result{std::move(exception)};
}
/// Can be used to cancel an asynchronous continuation chain,
/// the next failure handler which comes after cancel will be called
/// with a default constructed exception_t object.
///
/// The \ref cancellation_result returned by this function can be returned from
/// any result or failure handler in order to cancel the chain.
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) {
/// return cancel();
/// })
/// .fail([](cti::exception_t exception) {
/// return cancel();
/// })
/// .next([](auto&&...) {
/// return cancel();
/// });
/// ```
/// The returned \ref empty_result is convertible to
/// any \ref result as shown below:
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) -> cti::result<> {
/// return cancel();
/// })
/// .fail([](cti::exception_t exception) -> cti::result<> {
/// return cancel();
/// })
/// .next([](auto&&...) -> cti::result<> {
/// return cancel();
/// });
/// ```
///
/// \since 4.0.0
///
inline cancellation_result cancel() {
return {};
}
/// Can be used to stop an asynchronous continuation chain,
/// no handler which comes after stop was received won't be called.
///
/// \since 4.0.0
///
inline empty_result stop() {
return {};
}
/// \}
} // namespace cti
#endif // CONTINUABLE_BASE_HPP_INCLUDED
// #include <continuable/continuable-connections.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_CONNECTIONS_HPP_INCLUDED
#define CONTINUABLE_CONNECTIONS_HPP_INCLUDED
#include <initializer_list>
#include <memory>
#include <utility>
#include <vector>
// #include <continuable/detail/connection/connection-all.hpp>
// #include <continuable/detail/connection/connection-any.hpp>
// #include <continuable/detail/connection/connection-seq.hpp>
// #include <continuable/detail/connection/connection.hpp>
// #include <continuable/detail/traversal/range.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_RANGE_HPP_INCLUDED
#define CONTINUABLE_DETAIL_RANGE_HPP_INCLUDED
#include <iterator>
#include <type_traits>
#include <utility>
#include <vector>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace range {
/// Deduces to a true_type if the given type is an interator
template <typename T, typename = void>
struct is_iterator : std::false_type {};
template <typename T>
struct is_iterator<T,
traits::void_t<typename std::iterator_traits<T>::value_type>>
: std::true_type {};
/// Moves the content of the given iterators to a persistent storage
template <typename Iterator>
auto persist_range(Iterator begin, Iterator end) {
std::vector<typename std::iterator_traits<Iterator>::value_type> storage;
// TODO Find out why the superior idiom below has issues with move only types:
// storage.insert(storage.end(), std::make_move_iterator(begin),
// std::make_move_iterator(end));
std::move(begin, end, std::back_inserter(storage));
return storage;
}
} // namespace range
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_RANGE_HPP_INCLUDED
namespace cti {
/// \defgroup Connections Connections
/// provides functions to connect \link continuable_base
/// continuable_bases\endlink through various strategies.
/// \{
/// Connects the given arguments with an all logic.
/// All continuables contained inside the given nested pack are
/// invoked at once. On completion the final handler is called
/// with the aggregated result of all continuables.
///
/// \param args Arbitrary arguments which are connected.
/// Every type is allowed as arguments, continuables may be
/// contained inside tuple like types (`std::tuple`)
/// or in homogeneous containers such as `std::vector`.
/// Non continuable arguments are preserved and passed
/// to the final result as shown below:
/// ```cpp
/// cti::when_all(
/// cti::make_ready_continuable(0, 1),
/// 2, //< See this plain value
/// cti::populate(cti::make_ready_continuable(3), // Creates a runtime
/// cti::make_ready_continuable(4)), // sized container.
/// std::make_tuple(std::make_tuple(cti::make_ready_continuable(5))))
/// .then([](int r0, int r1, int r2, std::vector<int> r34,
/// std::tuple<std::tuple<int>> r5) {
/// // ...
/// });
/// ```
///
/// \see continuable_base::operator&& for details.
///
/// \since 1.1.0
template <typename... Args>
auto when_all(Args&&... args) {
return detail::connection::apply_connection(
detail::connection::connection_strategy_all_tag{},
std::forward<Args>(args)...);
}
/// Connects the given arguments with an all logic.
/// The content of the iterator is moved out and converted
/// to a temporary `std::vector` which is then passed to when_all.
///
/// ```cpp
/// // cti::populate just creates a std::vector from the two continuables.
/// auto v = cti::populate(cti::make_ready_continuable(0),
/// cti::make_ready_continuable(1));
///
/// cti::when_all(v.begin(), v.end())
/// .then([](std::vector<int> r01) {
/// // ...
/// });
/// ```
///
/// \param begin The begin iterator to the range which will be moved out
/// and used as the arguments to the all connection
///
/// \param end The end iterator to the range which will be moved out
/// and used as the arguments to the all connection
///
/// \see when_all for details.
///
/// \attention Prefer to invoke when_all with the whole container the
/// iterators were taken from, since this saves us
/// the creation of a temporary storage.
///
/// \since 3.0.0
template <
typename Iterator,
std::enable_if_t<detail::range::is_iterator<Iterator>::value>* = nullptr>
auto when_all(Iterator begin, Iterator end) {
return when_all(detail::range::persist_range(begin, end));
}
/// Connects the given arguments with a sequential logic.
/// All continuables contained inside the given nested pack are
/// invoked one after one. On completion the final handler is called
/// with the aggregated result of all continuables.
///
/// \param args Arbitrary arguments which are connected.
/// Every type is allowed as arguments, continuables may be
/// contained inside tuple like types (`std::tuple`)
/// or in homogeneous containers such as `std::vector`.
/// Non continuable arguments are preserved and passed
/// to the final result as shown below:
/// ```cpp
/// cti::when_seq(
/// cti::make_ready_continuable(0, 1),
/// 2, //< See this plain value
/// cti::populate(cti::make_ready_continuable(3), // Creates a runtime
/// cti::make_ready_continuable(4)), // sized container.
/// std::make_tuple(std::make_tuple(cti::make_ready_continuable(5))))
/// .then([](int r0, int r1, int r2, std::vector<int> r34,
/// std::tuple<std::tuple<int>> r5) {
/// // ...
/// });
/// ```
///
/// \see continuable_base::operator>> for details.
///
/// \since 1.1.0
template <typename... Args>
auto when_seq(Args&&... args) {
return detail::connection::apply_connection(
detail::connection::connection_strategy_seq_tag{},
std::forward<Args>(args)...);
}
/// Connects the given arguments with a sequential logic.
/// The content of the iterator is moved out and converted
/// to a temporary `std::vector` which is then passed to when_seq.
///
/// ```cpp
/// // cti::populate just creates a std::vector from the two continuables.
/// auto v = cti::populate(cti::make_ready_continuable(0),
/// cti::make_ready_continuable(1));
///
/// cti::when_seq(v.begin(), v.end())
/// .then([](std::vector<int> r01) {
/// // ...
/// });
/// ```
///
/// \param begin The begin iterator to the range which will be moved out
/// and used as the arguments to the sequential connection
///
/// \param end The end iterator to the range which will be moved out
/// and used as the arguments to the sequential connection
///
/// \see when_seq for details.
///
/// \attention Prefer to invoke when_seq with the whole container the
/// iterators were taken from, since this saves us
/// the creation of a temporary storage.
///
/// \since 3.0.0
template <
typename Iterator,
std::enable_if_t<detail::range::is_iterator<Iterator>::value>* = nullptr>
auto when_seq(Iterator begin, Iterator end) {
return when_seq(detail::range::persist_range(begin, end));
}
/// Connects the given arguments with an any logic.
/// All continuables contained inside the given nested pack are
/// invoked at once. On completion of one continuable the final handler
/// is called with the result of the resolved continuable.
///
/// \param args Arbitrary arguments which are connected.
/// Every type is allowed as arguments, continuables may be
/// contained inside tuple like types (`std::tuple`)
/// or in homogeneous containers such as `std::vector`.
/// Non continuable arguments are preserved and passed
/// to the final result as shown below:
/// ```cpp
/// cti::when_any(
/// cti::make_ready_continuable(0, 1),
/// 2, //< See this plain value
/// cti::populate(cti::make_ready_continuable(3), // Creates a runtime
/// cti::make_ready_continuable(4)), // sized container.
/// std::make_tuple(std::make_tuple(cti::make_ready_continuable(5))))
/// .then([](int r0) {
/// // ...
/// });
/// ```
///
/// \see continuable_base::operator|| for details.
///
/// \since 1.1.0
template <typename... Args>
auto when_any(Args&&... args) {
return detail::connection::apply_connection(
detail::connection::connection_strategy_any_tag{},
std::forward<Args>(args)...);
}
/// Connects the given arguments with an any logic.
/// The content of the iterator is moved out and converted
/// to a temporary `std::vector` which is then passed to when_all.
///
/// ```cpp
/// // cti::populate just creates a std::vector from the two continuables.
/// auto v = cti::populate(cti::make_ready_continuable(0),
/// cti::make_ready_continuable(1));
///
/// cti::when_any(v.begin(), v.end())
/// .then([](int r01) {
/// // ...
/// });
/// ```
///
/// \param begin The begin iterator to the range which will be moved out
/// and used as the arguments to the all connection
///
/// \param end The end iterator to the range which will be moved out
/// and used as the arguments to the all connection
///
/// \see when_any for details.
///
/// \attention Prefer to invoke when_any with the whole container the
/// iterators were taken from, since this saves us
/// the creation of a temporary storage.
///
/// \since 3.0.0
template <
typename Iterator,
std::enable_if_t<detail::range::is_iterator<Iterator>::value>* = nullptr>
auto when_any(Iterator begin, Iterator end) {
return when_any(detail::range::persist_range(begin, end));
}
/// Populates a homogeneous container from the given arguments.
/// All arguments need to be convertible to the first one,
/// by default `std::vector` is used as container type.
///
/// This method mainly helps to create a homogeneous container from
/// a runtime known count of continuables which type isn't exactly known.
/// All continuables which are passed to this function should be originating
/// from the same source or a method called with the same types of arguments:
/// ```cpp
/// auto container = cti::populate(cti::make_ready_continuable(0),
/// cti::make_ready_continuable(1)),
///
/// for (int i = 2; i < 5; ++i) {
/// // You may add more continuables to the container afterwards
/// container.emplace_back(cti::make_ready_continuable(i));
/// }
///
/// cti::when_any(std::move(container))
/// .then([](int) {
/// // ...
/// });
/// ```
/// Additionally it is possible to change the targeted container as below:
/// ```cpp
/// auto container = cti::populate<std::list>(cti::make_ready_continuable(0),
/// cti::make_ready_continuable(1)),
/// ```
///
/// \tparam C The container type which is used to store the arguments into.
///
/// \since 3.0.0
template <template <typename, typename> class C = std::vector, typename First,
typename... Args>
C<std::decay_t<First>, std::allocator<std::decay_t<First>>>
populate(First&& first, Args&&... args) {
C<std::decay_t<First>, std::allocator<std::decay_t<First>>> container;
container.reserve(1 + sizeof...(Args));
container.emplace_back(std::forward<First>(first));
(void)std::initializer_list<int>{
0, ((void)container.emplace_back(std::forward<Args>(args)), 0)...};
return container; // RVO
}
/// \}
} // namespace cti
#endif // CONTINUABLE_CONNECTIONS_HPP_INCLUDED
// #include <continuable/continuable-coroutine.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_COROUTINE_HPP_INCLUDED
#define CONTINUABLE_COROUTINE_HPP_INCLUDED
// #include <continuable/continuable-base.hpp>
// #include <continuable/continuable-types.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TYPES_HPP_INCLUDED
#define CONTINUABLE_TYPES_HPP_INCLUDED
// #include <function2/function2.hpp>
// Copyright 2015-2020 Denis Blank <denis.blank at outlook dot com>
// Distributed under the Boost Software License, Version 1.0
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#ifndef FU2_INCLUDED_FUNCTION2_HPP_
#define FU2_INCLUDED_FUNCTION2_HPP_
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
// Defines:
// - FU2_HAS_DISABLED_EXCEPTIONS
#if defined(FU2_WITH_DISABLED_EXCEPTIONS) || \
defined(FU2_MACRO_DISABLE_EXCEPTIONS)
#define FU2_HAS_DISABLED_EXCEPTIONS
#else // FU2_WITH_DISABLED_EXCEPTIONS
#if defined(_MSC_VER)
#if !defined(_HAS_EXCEPTIONS) || (_HAS_EXCEPTIONS == 0)
#define FU2_HAS_DISABLED_EXCEPTIONS
#endif
#elif defined(__clang__)
#if !(__EXCEPTIONS && __has_feature(cxx_exceptions))
#define FU2_HAS_DISABLED_EXCEPTIONS
#endif
#elif defined(__GNUC__)
#if !__EXCEPTIONS
#define FU2_HAS_DISABLED_EXCEPTIONS
#endif
#endif
#endif // FU2_WITH_DISABLED_EXCEPTIONS
// - FU2_HAS_NO_FUNCTIONAL_HEADER
#if !defined(FU2_WITH_NO_FUNCTIONAL_HEADER) && \
!defined(FU2_NO_FUNCTIONAL_HEADER) && \
!defined(FU2_HAS_DISABLED_EXCEPTIONS)
#include <functional>
#else
#define FU2_HAS_NO_FUNCTIONAL_HEADER
#endif
// - FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#if defined(FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE)
#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#else // FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE
#if defined(_MSC_VER)
#if defined(_HAS_CXX17) && _HAS_CXX17
#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#endif
#elif defined(__cpp_noexcept_function_type)
#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#elif defined(__cplusplus) && (__cplusplus >= 201703L)
#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#endif
#endif // FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE
// - FU2_HAS_NO_EMPTY_PROPAGATION
#if defined(FU2_WITH_NO_EMPTY_PROPAGATION)
#define FU2_HAS_NO_EMPTY_PROPAGATION
#endif // FU2_WITH_NO_EMPTY_PROPAGATION
#if !defined(FU2_HAS_DISABLED_EXCEPTIONS)
#include <exception>
#endif
/// Hint for the compiler that this point should be unreachable
#if defined(_MSC_VER)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __assume(false)
#elif defined(__GNUC__)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable()
#elif defined(__has_builtin) && __has_builtin(__builtin_unreachable)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE_INTRINSIC() abort()
#endif
/// Causes the application to exit abnormally
#if defined(_MSC_VER)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_TRAP() __debugbreak()
#elif defined(__GNUC__)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_TRAP() __builtin_trap()
#elif defined(__has_builtin) && __has_builtin(__builtin_trap)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_TRAP() __builtin_trap()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_TRAP() *(volatile int*)0x11 = 0
#endif
#ifndef NDEBUG
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE() ::fu2::detail::unreachable_debug()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE() FU2_DETAIL_UNREACHABLE_INTRINSIC()
#endif
namespace fu2 {
inline namespace abi_400 {
namespace detail {
template <typename Config, typename Property>
class function;
template <typename...>
struct identity {};
// Equivalent to C++17's std::void_t which targets a bug in GCC,
// that prevents correct SFINAE behavior.
// See http://stackoverflow.com/questions/35753920 for details.
template <typename...>
struct deduce_to_void : std::common_type<void> {};
template <typename... T>
using void_t = typename deduce_to_void<T...>::type;
template <typename T>
using unrefcv_t = std::remove_cv_t<std::remove_reference_t<T>>;
// Copy enabler helper class
template <bool /*Copyable*/>
struct copyable {};
template <>
struct copyable<false> {
copyable() = default;
~copyable() = default;
copyable(copyable const&) = delete;
copyable(copyable&&) = default;
copyable& operator=(copyable const&) = delete;
copyable& operator=(copyable&&) = default;
};
/// Configuration trait to configure the function_base class.
template <bool Owning, bool Copyable, typename Capacity>
struct config {
// Is true if the function is owning.
static constexpr auto const is_owning = Owning;
// Is true if the function is copyable.
static constexpr auto const is_copyable = Copyable;
// The internal capacity of the function
// used in small functor optimization.
// The object shall expose the real capacity through Capacity::capacity
// and the intended alignment through Capacity::alignment.
using capacity = Capacity;
};
/// A config which isn't compatible to other configs
template <bool Throws, bool HasStrongExceptGuarantee, typename... Args>
struct property {
// Is true when the function throws an exception on empty invocation.
static constexpr auto const is_throwing = Throws;
// Is true when the function throws an exception on empty invocation.
static constexpr auto const is_strong_exception_guaranteed =
HasStrongExceptGuarantee;
};
#ifndef NDEBUG
[[noreturn]] inline void unreachable_debug() {
FU2_DETAIL_TRAP();
std::abort();
}
#endif
/// Provides utilities for invocing callable objects
namespace invocation {
/// Invokes the given callable object with the given arguments
template <typename Callable, typename... Args>
constexpr auto invoke(Callable&& callable, Args&&... args) noexcept(
noexcept(std::forward<Callable>(callable)(std::forward<Args>(args)...)))
-> decltype(std::forward<Callable>(callable)(std::forward<Args>(args)...)) {
return std::forward<Callable>(callable)(std::forward<Args>(args)...);
}
/// Invokes the given member function pointer by reference
template <typename T, typename Type, typename Self, typename... Args>
constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
noexcept((std::forward<Self>(self).*member)(std::forward<Args>(args)...)))
-> decltype((std::forward<Self>(self).*
member)(std::forward<Args>(args)...)) {
return (std::forward<Self>(self).*member)(std::forward<Args>(args)...);
}
/// Invokes the given member function pointer by pointer
template <typename T, typename Type, typename Self, typename... Args>
constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
noexcept((std::forward<Self>(self)->*member)(std::forward<Args>(args)...)))
-> decltype(
(std::forward<Self>(self)->*member)(std::forward<Args>(args)...)) {
return (std::forward<Self>(self)->*member)(std::forward<Args>(args)...);
}
/// Invokes the given pointer to a scalar member by reference
template <typename T, typename Type, typename Self>
constexpr auto
invoke(Type T::*member,
Self&& self) noexcept(noexcept(std::forward<Self>(self).*member))
-> decltype(std::forward<Self>(self).*member) {
return (std::forward<Self>(self).*member);
}
/// Invokes the given pointer to a scalar member by pointer
template <typename T, typename Type, typename Self>
constexpr auto
invoke(Type T::*member,
Self&& self) noexcept(noexcept(std::forward<Self>(self)->*member))
-> decltype(std::forward<Self>(self)->*member) {
return std::forward<Self>(self)->*member;
}
/// Deduces to a true type if the callable object can be invoked with
/// the given arguments.
/// We don't use invoke here because MSVC can't evaluate the nested expression
/// SFINAE here.
template <typename T, typename Args, typename = void>
struct can_invoke : std::false_type {};
template <typename T, typename... Args>
struct can_invoke<T, identity<Args...>,
decltype((void)std::declval<T>()(std::declval<Args>()...))>
: std::true_type {};
template <typename Pointer, typename T, typename... Args>
struct can_invoke<Pointer, identity<T&, Args...>,
decltype((void)((std::declval<T&>().*std::declval<Pointer>())(
std::declval<Args>()...)))> : std::true_type {};
template <typename Pointer, typename T, typename... Args>
struct can_invoke<Pointer, identity<T&&, Args...>,
decltype(
(void)((std::declval<T&&>().*std::declval<Pointer>())(
std::declval<Args>()...)))> : std::true_type {};
template <typename Pointer, typename T, typename... Args>
struct can_invoke<Pointer, identity<T*, Args...>,
decltype(
(void)((std::declval<T*>()->*std::declval<Pointer>())(
std::declval<Args>()...)))> : std::true_type {};
template <typename Pointer, typename T>
struct can_invoke<Pointer, identity<T&>,
decltype((void)(std::declval<T&>().*std::declval<Pointer>()))>
: std::true_type {};
template <typename Pointer, typename T>
struct can_invoke<Pointer, identity<T&&>,
decltype(
(void)(std::declval<T&&>().*std::declval<Pointer>()))>
: std::true_type {};
template <typename Pointer, typename T>
struct can_invoke<Pointer, identity<T*>,
decltype(
(void)(std::declval<T*>()->*std::declval<Pointer>()))>
: std::true_type {};
template <bool RequiresNoexcept, typename T, typename Args>
struct is_noexcept_correct : std::true_type {};
template <typename T, typename... Args>
struct is_noexcept_correct<true, T, identity<Args...>>
: std::integral_constant<bool, noexcept(invoke(std::declval<T>(),
std::declval<Args>()...))> {
};
} // end namespace invocation
namespace overloading {
template <typename... Args>
struct overload_impl;
template <typename Current, typename Next, typename... Rest>
struct overload_impl<Current, Next, Rest...> : Current,
overload_impl<Next, Rest...> {
explicit overload_impl(Current current, Next next, Rest... rest)
: Current(std::move(current)), overload_impl<Next, Rest...>(
std::move(next), std::move(rest)...) {
}
using Current::operator();
using overload_impl<Next, Rest...>::operator();
};
template <typename Current>
struct overload_impl<Current> : Current {
explicit overload_impl(Current current) : Current(std::move(current)) {
}
using Current::operator();
};
template <typename... T>
constexpr auto overload(T&&... callables) {
return overload_impl<std::decay_t<T>...>{std::forward<T>(callables)...};
}
} // namespace overloading
/// Declares the namespace which provides the functionality to work with a
/// type-erased object.
namespace type_erasure {
/// Specialization to work with addresses of callable objects
template <typename T, typename = void>
struct address_taker {
template <typename O>
static void* take(O&& obj) {
return std::addressof(obj);
}
static T& restore(void* ptr) {
return *static_cast<T*>(ptr);
}
static T const& restore(void const* ptr) {
return *static_cast<T const*>(ptr);
}
static T volatile& restore(void volatile* ptr) {
return *static_cast<T volatile*>(ptr);
}
static T const volatile& restore(void const volatile* ptr) {
return *static_cast<T const volatile*>(ptr);
}
};
/// Specialization to work with addresses of raw function pointers
template <typename T>
struct address_taker<T, std::enable_if_t<std::is_pointer<T>::value>> {
template <typename O>
static void* take(O&& obj) {
return reinterpret_cast<void*>(obj);
}
template <typename O>
static T restore(O ptr) {
return reinterpret_cast<T>(const_cast<void*>(ptr));
}
};
template <typename Box>
struct box_factory;
/// Store the allocator inside the box
template <bool IsCopyable, typename T, typename Allocator>
struct box : private Allocator {
friend box_factory<box>;
T value_;
explicit box(T value, Allocator allocator)
: Allocator(std::move(allocator)), value_(std::move(value)) {
}
box(box&&) = default;
box(box const&) = default;
box& operator=(box&&) = default;
box& operator=(box const&) = default;
~box() = default;
};
template <typename T, typename Allocator>
struct box<false, T, Allocator> : private Allocator {
friend box_factory<box>;
T value_;
explicit box(T value, Allocator allocator)
: Allocator(std::move(allocator)), value_(std::move(value)) {
}
box(box&&) = default;
box(box const&) = delete;
box& operator=(box&&) = default;
box& operator=(box const&) = delete;
~box() = default;
};
template <bool IsCopyable, typename T, typename Allocator>
struct box_factory<box<IsCopyable, T, Allocator>> {
using real_allocator =
typename std::allocator_traits<std::decay_t<Allocator>>::
template rebind_alloc<box<IsCopyable, T, Allocator>>;
/// Allocates space through the boxed allocator
static box<IsCopyable, T, Allocator>*
box_allocate(box<IsCopyable, T, Allocator> const* me) {
real_allocator allocator(*static_cast<Allocator const*>(me));
return static_cast<box<IsCopyable, T, Allocator>*>(
std::allocator_traits<real_allocator>::allocate(allocator, 1U));
}
/// Destroys the box through the given allocator
static void box_deallocate(box<IsCopyable, T, Allocator>* me) {
real_allocator allocator(*static_cast<Allocator const*>(me));
me->~box();
std::allocator_traits<real_allocator>::deallocate(allocator, me, 1U);
}
};
/// Creates a box containing the given value and allocator
template <bool IsCopyable, typename T, typename Allocator>
auto make_box(std::integral_constant<bool, IsCopyable>, T&& value,
Allocator&& allocator) {
return box<IsCopyable, std::decay_t<T>, std::decay_t<Allocator>>(
std::forward<T>(value), std::forward<Allocator>(allocator));
}
template <typename T>
struct is_box : std::false_type {};
template <bool IsCopyable, typename T, typename Allocator>
struct is_box<box<IsCopyable, T, Allocator>> : std::true_type {};
/// Provides access to the pointer to a heal allocated erased object
/// as well to the inplace storage.
union data_accessor {
data_accessor() = default;
explicit constexpr data_accessor(std::nullptr_t) noexcept : ptr_(nullptr) {
}
explicit constexpr data_accessor(void* ptr) noexcept : ptr_(ptr) {
}
/// The pointer we use if the object is on the heap
void* ptr_;
/// The first field of the inplace storage
std::size_t inplace_storage_;
};
/// See opcode::op_fetch_empty
constexpr void write_empty(data_accessor* accessor, bool empty) noexcept {
accessor->inplace_storage_ = std::size_t(empty);
}
template <typename From, typename To>
using transfer_const_t =
std::conditional_t<std::is_const<std::remove_pointer_t<From>>::value,
std::add_const_t<To>, To>;
template <typename From, typename To>
using transfer_volatile_t =
std::conditional_t<std::is_volatile<std::remove_pointer_t<From>>::value,
std::add_volatile_t<To>, To>;
/// The retriever when the object is allocated inplace
template <typename T, typename Accessor>
constexpr auto retrieve(std::true_type /*is_inplace*/, Accessor from,
std::size_t from_capacity) {
using type = transfer_const_t<Accessor, transfer_volatile_t<Accessor, void>>*;
/// Process the command by using the data inside the internal capacity
auto storage = &(from->inplace_storage_);
auto inplace = const_cast<void*>(static_cast<type>(storage));
return type(std::align(alignof(T), sizeof(T), inplace, from_capacity));
}
/// The retriever which is used when the object is allocated
/// through the allocator
template <typename T, typename Accessor>
constexpr auto retrieve(std::false_type /*is_inplace*/, Accessor from,
std::size_t /*from_capacity*/) {
return from->ptr_;
}
namespace invocation_table {
#if !defined(FU2_HAS_DISABLED_EXCEPTIONS)
#if defined(FU2_HAS_NO_FUNCTIONAL_HEADER)
struct bad_function_call : std::exception {
bad_function_call() noexcept {
}
char const* what() const noexcept override {
return "bad function call";
}
};
#else
using std::bad_function_call;
#endif
#endif
#ifdef FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) \
F(, , noexcept, , &) \
F(const, , noexcept, , &) \
F(, volatile, noexcept, , &) \
F(const, volatile, noexcept, , &) \
F(, , noexcept, &, &) \
F(const, , noexcept, &, &) \
F(, volatile, noexcept, &, &) \
F(const, volatile, noexcept, &, &) \
F(, , noexcept, &&, &&) \
F(const, , noexcept, &&, &&) \
F(, volatile, noexcept, &&, &&) \
F(const, volatile, noexcept, &&, &&)
#define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F) \
F(, , noexcept) \
F(const, , noexcept) \
F(, volatile, noexcept) \
F(const, volatile, noexcept)
#else // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F)
#define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F)
#endif // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#define FU2_DETAIL_EXPAND_QUALIFIERS(F) \
F(, , , , &) \
F(const, , , , &) \
F(, volatile, , , &) \
F(const, volatile, , , &) \
F(, , , &, &) \
F(const, , , &, &) \
F(, volatile, , &, &) \
F(const, volatile, , &, &) \
F(, , , &&, &&) \
F(const, , , &&, &&) \
F(, volatile, , &&, &&) \
F(const, volatile, , &&, &&) \
FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F)
#define FU2_DETAIL_EXPAND_CV(F) \
F(, , ) \
F(const, , ) \
F(, volatile, ) \
F(const, volatile, ) \
FU2_DETAIL_EXPAND_CV_NOEXCEPT(F)
/// If the function is qualified as noexcept, the call will never throw
template <bool IsNoexcept>
[[noreturn]] void throw_or_abortnoexcept(
std::integral_constant<bool, IsNoexcept> /*is_throwing*/) noexcept {
std::abort();
}
/// Calls std::abort on empty function calls
[[noreturn]] inline void
throw_or_abort(std::false_type /*is_throwing*/) noexcept {
std::abort();
}
/// Throws bad_function_call on empty funciton calls
[[noreturn]] inline void throw_or_abort(std::true_type /*is_throwing*/) {
#ifdef FU2_HAS_DISABLED_EXCEPTIONS
throw_or_abort(std::false_type{});
#else
throw bad_function_call{};
#endif
}
template <typename T>
struct function_trait;
using is_noexcept_ = std::false_type;
using is_noexcept_noexcept = std::true_type;
#define FU2_DEFINE_FUNCTION_TRAIT(CONST, VOLATILE, NOEXCEPT, OVL_REF, REF) \
template <typename Ret, typename... Args> \
struct function_trait<Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT> { \
using pointer_type = Ret (*)(data_accessor CONST VOLATILE*, \
std::size_t capacity, Args...); \
template <typename T, bool IsInplace> \
struct internal_invoker { \
static Ret invoke(data_accessor CONST VOLATILE* data, \
std::size_t capacity, Args... args) NOEXCEPT { \
auto obj = retrieve<T>(std::integral_constant<bool, IsInplace>{}, \
data, capacity); \
auto box = static_cast<T CONST VOLATILE*>(obj); \
return invocation::invoke( \
static_cast<std::decay_t<decltype(box->value_)> CONST VOLATILE \
REF>(box->value_), \
std::forward<Args>(args)...); \
} \
}; \
\
template <typename T> \
struct view_invoker { \
static Ret invoke(data_accessor CONST VOLATILE* data, std::size_t, \
Args... args) NOEXCEPT { \
\
auto ptr = static_cast<void CONST VOLATILE*>(data->ptr_); \
return invocation::invoke(address_taker<T>::restore(ptr), \
std::forward<Args>(args)...); \
} \
}; \
\
template <typename T> \
using callable = T CONST VOLATILE REF; \
\
using arguments = identity<Args...>; \
\
using is_noexcept = is_noexcept_##NOEXCEPT; \
\
template <bool Throws> \
struct empty_invoker { \
static Ret invoke(data_accessor CONST VOLATILE* /*data*/, \
std::size_t /*capacity*/, Args... /*args*/) NOEXCEPT { \
throw_or_abort##NOEXCEPT(std::integral_constant<bool, Throws>{}); \
} \
}; \
};
FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT)
#undef FU2_DEFINE_FUNCTION_TRAIT
/// Deduces to the function pointer to the given signature
template <typename Signature>
using function_pointer_of = typename function_trait<Signature>::pointer_type;
template <typename... Args>
struct invoke_table;
/// We optimize the vtable_t in case there is a single function overload
template <typename First>
struct invoke_table<First> {
using type = function_pointer_of<First>;
/// Return the function pointer itself
template <std::size_t Index>
static constexpr auto fetch(type pointer) noexcept {
static_assert(Index == 0U, "The index should be 0 here!");
return pointer;
}
/// Returns the thunk of an single overloaded callable
template <typename T, bool IsInplace>
static constexpr type get_invocation_table_of() noexcept {
return &function_trait<First>::template internal_invoker<T,
IsInplace>::invoke;
}
/// Returns the thunk of an single overloaded callable
template <typename T>
static constexpr type get_invocation_view_table_of() noexcept {
return &function_trait<First>::template view_invoker<T>::invoke;
}
/// Returns the thunk of an empty single overloaded callable
template <bool IsThrowing>
static constexpr type get_empty_invocation_table() noexcept {
return &function_trait<First>::template empty_invoker<IsThrowing>::invoke;
}
};
/// We generate a table in case of multiple function overloads
template <typename First, typename Second, typename... Args>
struct invoke_table<First, Second, Args...> {
using type =
std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
function_pointer_of<Args>...> const*;
/// Return the function pointer at the particular index
template <std::size_t Index>
static constexpr auto fetch(type table) noexcept {
return std::get<Index>(*table);
}
/// The invocation vtable for a present object
template <typename T, bool IsInplace>
struct invocation_vtable : public std::tuple<function_pointer_of<First>,
function_pointer_of<Second>,
function_pointer_of<Args>...> {
constexpr invocation_vtable() noexcept
: std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
function_pointer_of<Args>...>(std::make_tuple(
&function_trait<First>::template internal_invoker<
T, IsInplace>::invoke,
&function_trait<Second>::template internal_invoker<
T, IsInplace>::invoke,
&function_trait<Args>::template internal_invoker<
T, IsInplace>::invoke...)) {
}
};
/// Returns the thunk of an multi overloaded callable
template <typename T, bool IsInplace>
static type get_invocation_table_of() noexcept {
static invocation_vtable<T, IsInplace> const table;
return &table;
}
/// The invocation vtable for a present object
template <typename T>
struct invocation_view_vtable
: public std::tuple<function_pointer_of<First>,
function_pointer_of<Second>,
function_pointer_of<Args>...> {
constexpr invocation_view_vtable() noexcept
: std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
function_pointer_of<Args>...>(std::make_tuple(
&function_trait<First>::template view_invoker<T>::invoke,
&function_trait<Second>::template view_invoker<T>::invoke,
&function_trait<Args>::template view_invoker<T>::invoke...)) {
}
};
/// Returns the thunk of an multi overloaded callable
template <typename T>
static type get_invocation_view_table_of() noexcept {
static invocation_view_vtable<T> const table;
return &table;
}
/// The invocation table for an empty wrapper
template <bool IsThrowing>
struct empty_vtable : public std::tuple<function_pointer_of<First>,
function_pointer_of<Second>,
function_pointer_of<Args>...> {
constexpr empty_vtable() noexcept
: std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
function_pointer_of<Args>...>(
std::make_tuple(&function_trait<First>::template empty_invoker<
IsThrowing>::invoke,
&function_trait<Second>::template empty_invoker<
IsThrowing>::invoke,
&function_trait<Args>::template empty_invoker<
IsThrowing>::invoke...)) {
}
};
/// Returns the thunk of an multi single overloaded callable
template <bool IsThrowing>
static type get_empty_invocation_table() noexcept {
static empty_vtable<IsThrowing> const table;
return &table;
}
};
template <std::size_t Index, typename Function, typename... Signatures>
class operator_impl;
#define FU2_DEFINE_FUNCTION_TRAIT(CONST, VOLATILE, NOEXCEPT, OVL_REF, REF) \
template <std::size_t Index, typename Function, typename Ret, \
typename... Args, typename Next, typename... Signatures> \
class operator_impl<Index, Function, \
Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT, Next, \
Signatures...> \
: operator_impl<Index + 1, Function, Next, Signatures...> { \
\
template <std::size_t, typename, typename...> \
friend class operator_impl; \
\
protected: \
operator_impl() = default; \
~operator_impl() = default; \
operator_impl(operator_impl const&) = default; \
operator_impl(operator_impl&&) = default; \
operator_impl& operator=(operator_impl const&) = default; \
operator_impl& operator=(operator_impl&&) = default; \
\
using operator_impl<Index + 1, Function, Next, Signatures...>::operator(); \
\
Ret operator()(Args... args) CONST VOLATILE OVL_REF NOEXCEPT { \
auto parent = static_cast<Function CONST VOLATILE*>(this); \
using erasure_t = std::decay_t<decltype(parent->erasure_)>; \
\
/* `std::decay_t<decltype(parent->erasure_)>` is a workaround for a */ \
/* compiler regression of MSVC 16.3.1, see #29 for details. */ \
return std::decay_t<decltype(parent->erasure_)>::template invoke<Index>( \
static_cast<erasure_t CONST VOLATILE REF>(parent->erasure_), \
std::forward<Args>(args)...); \
} \
}; \
template <std::size_t Index, typename Config, typename Property, \
typename Ret, typename... Args> \
class operator_impl<Index, function<Config, Property>, \
Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT> \
: copyable<!Config::is_owning || Config::is_copyable> { \
\
template <std::size_t, typename, typename...> \
friend class operator_impl; \
\
protected: \
operator_impl() = default; \
~operator_impl() = default; \
operator_impl(operator_impl const&) = default; \
operator_impl(operator_impl&&) = default; \
operator_impl& operator=(operator_impl const&) = default; \
operator_impl& operator=(operator_impl&&) = default; \
\
Ret operator()(Args... args) CONST VOLATILE OVL_REF NOEXCEPT { \
auto parent = \
static_cast<function<Config, Property> CONST VOLATILE*>(this); \
using erasure_t = std::decay_t<decltype(parent->erasure_)>; \
\
/* `std::decay_t<decltype(parent->erasure_)>` is a workaround for a */ \
/* compiler regression of MSVC 16.3.1, see #29 for details. */ \
return std::decay_t<decltype(parent->erasure_)>::template invoke<Index>( \
static_cast<erasure_t CONST VOLATILE REF>(parent->erasure_), \
std::forward<Args>(args)...); \
} \
};
FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT)
#undef FU2_DEFINE_FUNCTION_TRAIT
} // namespace invocation_table
namespace tables {
/// Identifies the action which is dispatched on the erased object
enum class opcode {
op_move, //< Move the object and set the vtable
op_copy, //< Copy the object and set the vtable
op_destroy, //< Destroy the object and reset the vtable
op_weak_destroy, //< Destroy the object without resetting the vtable
op_fetch_empty, //< Stores true or false into the to storage
//< to indicate emptiness
};
/// Abstraction for a vtable together with a command table
/// TODO Add optimization for a single formal argument
/// TODO Add optimization to merge both tables if the function is size
/// optimized
template <typename Property>
class vtable;
template <bool IsThrowing, bool HasStrongExceptGuarantee,
typename... FormalArgs>
class vtable<property<IsThrowing, HasStrongExceptGuarantee, FormalArgs...>> {
using command_function_t = void (*)(vtable* /*this*/, opcode /*op*/,
data_accessor* /*from*/,
std::size_t /*from_capacity*/,
data_accessor* /*to*/,
std::size_t /*to_capacity*/);
using invoke_table_t = invocation_table::invoke_table<FormalArgs...>;
command_function_t cmd_;
typename invoke_table_t::type vtable_;
template <typename T>
struct trait {
static_assert(is_box<T>::value,
"The trait must be specialized with a box!");
/// The command table
template <bool IsInplace>
static void process_cmd(vtable* to_table, opcode op, data_accessor* from,
std::size_t from_capacity, data_accessor* to,
std::size_t to_capacity) {
switch (op) {
case opcode::op_move: {
/// Retrieve the pointer to the object
auto box = static_cast<T*>(retrieve<T>(
std::integral_constant<bool, IsInplace>{}, from, from_capacity));
assert(box && "The object must not be over aligned or null!");
if (!IsInplace) {
// Just swap both pointers if we allocated on the heap
to->ptr_ = from->ptr_;
#ifndef NDEBUG
// We don't need to null the pointer since we know that
// we don't own the data anymore through the vtable
// which is set to empty.
from->ptr_ = nullptr;
#endif
to_table->template set_allocated<T>();
}
// The object is allocated inplace
else {
construct(std::true_type{}, std::move(*box), to_table, to,
to_capacity);
box->~T();
}
return;
}
case opcode::op_copy: {
auto box = static_cast<T const*>(retrieve<T>(
std::integral_constant<bool, IsInplace>{}, from, from_capacity));
assert(box && "The object must not be over aligned or null!");
assert(std::is_copy_constructible<T>::value &&
"The box is required to be copyable here!");
// Try to allocate the object inplace
construct(std::is_copy_constructible<T>{}, *box, to_table, to,
to_capacity);
return;
}
case opcode::op_destroy:
case opcode::op_weak_destroy: {
assert(!to && !to_capacity && "Arg overflow!");
auto box = static_cast<T*>(retrieve<T>(
std::integral_constant<bool, IsInplace>{}, from, from_capacity));
if (IsInplace) {
box->~T();
} else {
box_factory<T>::box_deallocate(box);
}
if (op == opcode::op_destroy) {
to_table->set_empty();
}
return;
}
case opcode::op_fetch_empty: {
write_empty(to, false);
return;
}
}
FU2_DETAIL_UNREACHABLE();
}
template <typename Box>
static void
construct(std::true_type /*apply*/, Box&& box, vtable* to_table,
data_accessor* to,
std::size_t to_capacity) noexcept(HasStrongExceptGuarantee) {
// Try to allocate the object inplace
void* storage = retrieve<T>(std::true_type{}, to, to_capacity);
if (storage) {
to_table->template set_inplace<T>();
} else {
// Allocate the object through the allocator
to->ptr_ = storage =
box_factory<std::decay_t<Box>>::box_allocate(std::addressof(box));
to_table->template set_allocated<T>();
}
new (storage) T(std::forward<Box>(box));
}
template <typename Box>
static void
construct(std::false_type /*apply*/, Box&& /*box*/, vtable* /*to_table*/,
data_accessor* /*to*/,
std::size_t /*to_capacity*/) noexcept(HasStrongExceptGuarantee) {
}
};
/// The command table
static void empty_cmd(vtable* to_table, opcode op, data_accessor* /*from*/,
std::size_t /*from_capacity*/, data_accessor* to,
std::size_t /*to_capacity*/) {
switch (op) {
case opcode::op_move:
case opcode::op_copy: {
to_table->set_empty();
break;
}
case opcode::op_destroy:
case opcode::op_weak_destroy: {
// Do nothing
break;
}
case opcode::op_fetch_empty: {
write_empty(to, true);
break;
}
default: {
FU2_DETAIL_UNREACHABLE();
}
}
}
public:
vtable() noexcept = default;
/// Initialize an object at the given position
template <typename T>
static void init(vtable& table, T&& object, data_accessor* to,
std::size_t to_capacity) {
trait<std::decay_t<T>>::construct(std::true_type{}, std::forward<T>(object),
&table, to, to_capacity);
}
/// Moves the object at the given position
void move(vtable& to_table, data_accessor* from, std::size_t from_capacity,
data_accessor* to,
std::size_t to_capacity) noexcept(HasStrongExceptGuarantee) {
cmd_(&to_table, opcode::op_move, from, from_capacity, to, to_capacity);
set_empty();
}
/// Destroys the object at the given position
void copy(vtable& to_table, data_accessor const* from,
std::size_t from_capacity, data_accessor* to,
std::size_t to_capacity) const {
cmd_(&to_table, opcode::op_copy, const_cast<data_accessor*>(from),
from_capacity, to, to_capacity);
}
/// Destroys the object at the given position
void destroy(data_accessor* from,
std::size_t from_capacity) noexcept(HasStrongExceptGuarantee) {
cmd_(this, opcode::op_destroy, from, from_capacity, nullptr, 0U);
}
/// Destroys the object at the given position without invalidating the
/// vtable
void
weak_destroy(data_accessor* from,
std::size_t from_capacity) noexcept(HasStrongExceptGuarantee) {
cmd_(this, opcode::op_weak_destroy, from, from_capacity, nullptr, 0U);
}
/// Returns true when the vtable doesn't hold any erased object
bool empty() const noexcept {
data_accessor data;
cmd_(nullptr, opcode::op_fetch_empty, nullptr, 0U, &data, 0U);
return bool(data.inplace_storage_);
}
/// Invoke the function at the given index
template <std::size_t Index, typename... Args>
constexpr decltype(auto) invoke(Args&&... args) const {
auto thunk = invoke_table_t::template fetch<Index>(vtable_);
return thunk(std::forward<Args>(args)...);
}
/// Invoke the function at the given index
template <std::size_t Index, typename... Args>
constexpr decltype(auto) invoke(Args&&... args) const volatile {
auto thunk = invoke_table_t::template fetch<Index>(vtable_);
return thunk(std::forward<Args>(args)...);
}
template <typename T>
void set_inplace() noexcept {
using type = std::decay_t<T>;
vtable_ = invoke_table_t::template get_invocation_table_of<type, true>();
cmd_ = &trait<type>::template process_cmd<true>;
}
template <typename T>
void set_allocated() noexcept {
using type = std::decay_t<T>;
vtable_ = invoke_table_t::template get_invocation_table_of<type, false>();
cmd_ = &trait<type>::template process_cmd<false>;
}
void set_empty() noexcept {
vtable_ = invoke_table_t::template get_empty_invocation_table<IsThrowing>();
cmd_ = &empty_cmd;
}
};
} // namespace tables
/// A union which makes the pointer to the heap object share the
/// same space with the internal capacity.
/// The storage type is distinguished by multiple versions of the
/// control and vtable.
template <typename Capacity, typename = void>
struct internal_capacity {
/// We extend the union through a technique similar to the tail object hack
typedef union {
/// Tag to access the structure in a type-safe way
data_accessor accessor_;
/// The internal capacity we use to allocate in-place
std::aligned_storage_t<Capacity::capacity, Capacity::alignment> capacity_;
} type;
};
template <typename Capacity>
struct internal_capacity<
Capacity, std::enable_if_t<(Capacity::capacity < sizeof(void*))>> {
typedef struct {
/// Tag to access the structure in a type-safe way
data_accessor accessor_;
} type;
};
template <typename Capacity>
class internal_capacity_holder {
// Tag to access the structure in a type-safe way
typename internal_capacity<Capacity>::type storage_;
public:
constexpr internal_capacity_holder() = default;
constexpr data_accessor* opaque_ptr() noexcept {
return &storage_.accessor_;
}
constexpr data_accessor const* opaque_ptr() const noexcept {
return &storage_.accessor_;
}
constexpr data_accessor volatile* opaque_ptr() volatile noexcept {
return &storage_.accessor_;
}
constexpr data_accessor const volatile* opaque_ptr() const volatile noexcept {
return &storage_.accessor_;
}
static constexpr std::size_t capacity() noexcept {
return sizeof(storage_);
}
};
/// An owning erasure
template <bool IsOwning /* = true*/, typename Config, typename Property>
class erasure : internal_capacity_holder<typename Config::capacity> {
template <bool, typename, typename>
friend class erasure;
template <std::size_t, typename, typename...>
friend class operator_impl;
using vtable_t = tables::vtable<Property>;
vtable_t vtable_;
public:
/// Returns the capacity of this erasure
static constexpr std::size_t capacity() noexcept {
return internal_capacity_holder<typename Config::capacity>::capacity();
}
constexpr erasure() noexcept {
vtable_.set_empty();
}
constexpr erasure(std::nullptr_t) noexcept {
vtable_.set_empty();
}
constexpr erasure(erasure&& right) noexcept(
Property::is_strong_exception_guaranteed) {
right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
}
constexpr erasure(erasure const& right) {
right.vtable_.copy(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
}
template <typename OtherConfig>
constexpr erasure(erasure<true, OtherConfig, Property> right) noexcept(
Property::is_strong_exception_guaranteed) {
right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
}
template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
constexpr erasure(std::false_type /*use_bool_op*/, T&& callable,
Allocator&& allocator = Allocator{}) {
vtable_t::init(vtable_,
type_erasure::make_box(
std::integral_constant<bool, Config::is_copyable>{},
std::forward<T>(callable),
std::forward<Allocator>(allocator)),
this->opaque_ptr(), capacity());
}
template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
constexpr erasure(std::true_type /*use_bool_op*/, T&& callable,
Allocator&& allocator = Allocator{}) {
if (bool(callable)) {
vtable_t::init(vtable_,
type_erasure::make_box(
std::integral_constant<bool, Config::is_copyable>{},
std::forward<T>(callable),
std::forward<Allocator>(allocator)),
this->opaque_ptr(), capacity());
} else {
vtable_.set_empty();
}
}
~erasure() {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
}
constexpr erasure&
operator=(std::nullptr_t) noexcept(Property::is_strong_exception_guaranteed) {
vtable_.destroy(this->opaque_ptr(), capacity());
return *this;
}
constexpr erasure& operator=(erasure&& right) noexcept(
Property::is_strong_exception_guaranteed) {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
return *this;
}
constexpr erasure& operator=(erasure const& right) {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
right.vtable_.copy(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
return *this;
}
template <typename OtherConfig>
constexpr erasure&
operator=(erasure<true, OtherConfig, Property> right) noexcept(
Property::is_strong_exception_guaranteed) {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
return *this;
}
template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
void assign(std::false_type /*use_bool_op*/, T&& callable,
Allocator&& allocator = {}) {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
vtable_t::init(vtable_,
type_erasure::make_box(
std::integral_constant<bool, Config::is_copyable>{},
std::forward<T>(callable),
std::forward<Allocator>(allocator)),
this->opaque_ptr(), capacity());
}
template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
void assign(std::true_type /*use_bool_op*/, T&& callable,
Allocator&& allocator = {}) {
if (bool(callable)) {
assign(std::false_type{}, std::forward<T>(callable),
std::forward<Allocator>(allocator));
} else {
operator=(nullptr);
}
}
/// Returns true when the erasure doesn't hold any erased object
constexpr bool empty() const noexcept {
return vtable_.empty();
}
/// Invoke the function of the erasure at the given index
///
/// We define this out of class to be able to forward the qualified
/// erasure correctly.
template <std::size_t Index, typename Erasure, typename... Args>
static constexpr decltype(auto) invoke(Erasure&& erasure, Args&&... args) {
auto const capacity = erasure.capacity();
return erasure.vtable_.template invoke<Index>(
std::forward<Erasure>(erasure).opaque_ptr(), capacity,
std::forward<Args>(args)...);
}
};
// A non owning erasure
template </*bool IsOwning = false, */ typename Config, bool IsThrowing,
bool HasStrongExceptGuarantee, typename... Args>
class erasure<false, Config,
property<IsThrowing, HasStrongExceptGuarantee, Args...>> {
template <bool, typename, typename>
friend class erasure;
template <std::size_t, typename, typename...>
friend class operator_impl;
using property_t = property<IsThrowing, HasStrongExceptGuarantee, Args...>;
using invoke_table_t = invocation_table::invoke_table<Args...>;
typename invoke_table_t::type invoke_table_;
/// The internal pointer to the non owned object
data_accessor view_;
public:
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure() noexcept
: invoke_table_(
invoke_table_t::template get_empty_invocation_table<IsThrowing>()),
view_(nullptr) {
}
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure(std::nullptr_t) noexcept
: invoke_table_(
invoke_table_t::template get_empty_invocation_table<IsThrowing>()),
view_(nullptr) {
}
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure(erasure&& right) noexcept
: invoke_table_(right.invoke_table_), view_(right.view_) {
}
constexpr erasure(erasure const& /*right*/) = default;
template <typename OtherConfig>
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure(erasure<false, OtherConfig, property_t> right) noexcept
: invoke_table_(right.invoke_table_), view_(right.view_) {
}
template <typename T>
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure(std::false_type /*use_bool_op*/, T&& object)
: invoke_table_(invoke_table_t::template get_invocation_view_table_of<
std::decay_t<T>>()),
view_(address_taker<std::decay_t<T>>::take(std::forward<T>(object))) {
}
template <typename T>
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure(std::true_type use_bool_op, T&& object) {
this->assign(use_bool_op, std::forward<T>(object));
}
~erasure() = default;
constexpr erasure&
operator=(std::nullptr_t) noexcept(HasStrongExceptGuarantee) {
invoke_table_ =
invoke_table_t::template get_empty_invocation_table<IsThrowing>();
view_.ptr_ = nullptr;
return *this;
}
constexpr erasure& operator=(erasure&& right) noexcept {
invoke_table_ = right.invoke_table_;
view_ = right.view_;
right = nullptr;
return *this;
}
constexpr erasure& operator=(erasure const& /*right*/) = default;
template <typename OtherConfig>
constexpr erasure&
operator=(erasure<true, OtherConfig, property_t> right) noexcept {
invoke_table_ = right.invoke_table_;
view_ = right.view_;
return *this;
}
template <typename T>
constexpr void assign(std::false_type /*use_bool_op*/, T&& callable) {
invoke_table_ = invoke_table_t::template get_invocation_view_table_of<
std::decay_t<T>>();
view_.ptr_ =
address_taker<std::decay_t<T>>::take(std::forward<T>(callable));
}
template <typename T>
constexpr void assign(std::true_type /*use_bool_op*/, T&& callable) {
if (bool(callable)) {
assign(std::false_type{}, std::forward<T>(callable));
} else {
operator=(nullptr);
}
}
/// Returns true when the erasure doesn't hold any erased object
constexpr bool empty() const noexcept {
return view_.ptr_ == nullptr;
}
template <std::size_t Index, typename Erasure, typename... T>
static constexpr decltype(auto) invoke(Erasure&& erasure, T&&... args) {
auto thunk = invoke_table_t::template fetch<Index>(erasure.invoke_table_);
return thunk(&(erasure.view_), 0UL, std::forward<T>(args)...);
}
};
} // namespace type_erasure
/// Deduces to a true_type if the type T provides the given signature and the
/// signature is noexcept correct callable.
template <typename T, typename Signature,
typename Trait =
type_erasure::invocation_table::function_trait<Signature>>
struct accepts_one
: std::integral_constant<
bool, invocation::can_invoke<typename Trait::template callable<T>,
typename Trait::arguments>::value &&
invocation::is_noexcept_correct<
Trait::is_noexcept::value,
typename Trait::template callable<T>,
typename Trait::arguments>::value> {};
/// Deduces to a true_type if the type T provides all signatures
template <typename T, typename Signatures, typename = void>
struct accepts_all : std::false_type {};
template <typename T, typename... Signatures>
struct accepts_all<
T, identity<Signatures...>,
void_t<std::enable_if_t<accepts_one<T, Signatures>::value>...>>
: std::true_type {};
/// Deduces to a true_type if the type T is implementing operator bool()
/// or if the type is convertible to bool directly, this also implements an
/// optimizations for function references `void(&)()` which are can never
/// be null and for such a conversion to bool would never return false.
#if defined(FU2_HAS_NO_EMPTY_PROPAGATION)
template <typename T>
struct use_bool_op : std::false_type {};
#else
template <typename T, typename = void>
struct has_bool_op : std::false_type {};
template <typename T>
struct has_bool_op<T, void_t<decltype(bool(std::declval<T>()))>>
: std::true_type {
#ifndef NDEBUG
static_assert(!std::is_pointer<T>::value,
"Missing deduction for function pointer!");
#endif
};
template <typename T>
struct use_bool_op : has_bool_op<T> {};
#define FU2_DEFINE_USE_OP_TRAIT(CONST, VOLATILE, NOEXCEPT) \
template <typename Ret, typename... Args> \
struct use_bool_op<Ret (*CONST VOLATILE)(Args...) NOEXCEPT> \
: std::true_type {};
FU2_DETAIL_EXPAND_CV(FU2_DEFINE_USE_OP_TRAIT)
#undef FU2_DEFINE_USE_OP_TRAIT
template <typename Ret, typename... Args>
struct use_bool_op<Ret(Args...)> : std::false_type {};
#if defined(FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE)
template <typename Ret, typename... Args>
struct use_bool_op<Ret(Args...) noexcept> : std::false_type {};
#endif
#endif // FU2_HAS_NO_EMPTY_PROPAGATION
template <typename Config, typename T>
struct assert_wrong_copy_assign {
static_assert(!Config::is_owning || !Config::is_copyable ||
std::is_copy_constructible<std::decay_t<T>>::value,
"Can't wrap a non copyable object into a unique function!");
using type = void;
};
template <bool IsStrongExceptGuaranteed, typename T>
struct assert_no_strong_except_guarantee {
static_assert(
!IsStrongExceptGuaranteed ||
(std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_destructible<T>::value),
"Can't wrap a object an object that has no strong exception guarantees "
"if this is required by the wrapper!");
using type = void;
};
/// SFINAES out if the given callable is not copyable correct to the left one.
template <typename LeftConfig, typename RightConfig>
using enable_if_copyable_correct_t =
std::enable_if_t<(!LeftConfig::is_copyable || RightConfig::is_copyable)>;
template <typename LeftConfig, typename RightConfig>
using is_owning_correct =
std::integral_constant<bool,
(LeftConfig::is_owning == RightConfig::is_owning)>;
/// SFINAES out if the given function2 is not owning correct to this one
template <typename LeftConfig, typename RightConfig>
using enable_if_owning_correct_t =
std::enable_if_t<is_owning_correct<LeftConfig, RightConfig>::value>;
template <typename Config, bool IsThrowing, bool HasStrongExceptGuarantee,
typename... Args>
class function<Config, property<IsThrowing, HasStrongExceptGuarantee, Args...>>
: type_erasure::invocation_table::operator_impl<
0U,
function<Config,
property<IsThrowing, HasStrongExceptGuarantee, Args...>>,
Args...> {
template <typename, typename>
friend class function;
template <std::size_t, typename, typename...>
friend class type_erasure::invocation_table::operator_impl;
using property_t = property<IsThrowing, HasStrongExceptGuarantee, Args...>;
using erasure_t =
type_erasure::erasure<Config::is_owning, Config, property_t>;
template <typename T>
using enable_if_can_accept_all_t =
std::enable_if_t<accepts_all<std::decay_t<T>, identity<Args...>>::value>;
template <typename Function, typename = void>
struct is_convertible_to_this : std::false_type {};
template <typename RightConfig>
struct is_convertible_to_this<
function<RightConfig, property_t>,
void_t<enable_if_copyable_correct_t<Config, RightConfig>,
enable_if_owning_correct_t<Config, RightConfig>>>
: std::true_type {};
template <typename T>
using enable_if_not_convertible_to_this =
std::enable_if_t<!is_convertible_to_this<std::decay_t<T>>::value>;
template <typename T>
using enable_if_owning_t =
std::enable_if_t<std::is_same<T, T>::value && Config::is_owning>;
template <typename T>
using assert_wrong_copy_assign_t =
typename assert_wrong_copy_assign<Config, std::decay_t<T>>::type;
template <typename T>
using assert_no_strong_except_guarantee_t =
typename assert_no_strong_except_guarantee<HasStrongExceptGuarantee,
std::decay_t<T>>::type;
erasure_t erasure_;
public:
/// Default constructor which empty constructs the function
function() = default;
~function() = default;
explicit constexpr function(function const& /*right*/) = default;
explicit constexpr function(function&& /*right*/) = default;
/// Copy construction from another copyable function
template <typename RightConfig,
std::enable_if_t<RightConfig::is_copyable>* = nullptr,
enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
constexpr function(function<RightConfig, property_t> const& right)
: erasure_(right.erasure_) {
}
/// Move construction from another function
template <typename RightConfig,
enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
constexpr function(function<RightConfig, property_t>&& right)
: erasure_(std::move(right.erasure_)) {
}
/// Construction from a callable object which overloads the `()` operator
template <typename T, //
enable_if_not_convertible_to_this<T>* = nullptr,
enable_if_can_accept_all_t<T>* = nullptr,
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
constexpr function(T&& callable)
: erasure_(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable)) {
}
template <typename T, typename Allocator, //
enable_if_not_convertible_to_this<T>* = nullptr,
enable_if_can_accept_all_t<T>* = nullptr,
enable_if_owning_t<T>* = nullptr,
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
constexpr function(T&& callable, Allocator&& allocator)
: erasure_(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable),
std::forward<Allocator>(allocator)) {
}
/// Empty constructs the function
constexpr function(std::nullptr_t np) : erasure_(np) {
}
function& operator=(function const& /*right*/) = default;
function& operator=(function&& /*right*/) = default;
/// Copy assigning from another copyable function
template <typename RightConfig,
std::enable_if_t<RightConfig::is_copyable>* = nullptr,
enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
function& operator=(function<RightConfig, property_t> const& right) {
erasure_ = right.erasure_;
return *this;
}
/// Move assigning from another function
template <typename RightConfig,
enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
function& operator=(function<RightConfig, property_t>&& right) {
erasure_ = std::move(right.erasure_);
return *this;
}
/// Move assigning from a callable object
template <typename T, // ...
enable_if_not_convertible_to_this<T>* = nullptr,
enable_if_can_accept_all_t<T>* = nullptr,
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
function& operator=(T&& callable) {
erasure_.assign(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable));
return *this;
}
/// Clears the function
function& operator=(std::nullptr_t np) {
erasure_ = np;
return *this;
}
/// Returns true when the function is empty
bool empty() const noexcept {
return erasure_.empty();
}
/// Returns true when the function isn't empty
explicit operator bool() const noexcept {
return !empty();
}
/// Assigns a new target with an optional allocator
template <typename T, typename Allocator = std::allocator<std::decay_t<T>>,
enable_if_not_convertible_to_this<T>* = nullptr,
enable_if_can_accept_all_t<T>* = nullptr,
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
void assign(T&& callable, Allocator&& allocator = Allocator{}) {
erasure_.assign(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable),
std::forward<Allocator>(allocator));
}
/// Swaps this function with the given function
void swap(function& other) noexcept(HasStrongExceptGuarantee) {
if (&other == this) {
return;
}
function cache = std::move(other);
other = std::move(*this);
*this = std::move(cache);
}
/// Swaps the left function with the right one
friend void swap(function& left,
function& right) noexcept(HasStrongExceptGuarantee) {
left.swap(right);
}
/// Calls the wrapped callable object
using type_erasure::invocation_table::operator_impl<
0U, function<Config, property_t>, Args...>::operator();
};
template <typename Config, typename Property>
bool operator==(function<Config, Property> const& f, std::nullptr_t) {
return !bool(f);
}
template <typename Config, typename Property>
bool operator!=(function<Config, Property> const& f, std::nullptr_t) {
return bool(f);
}
template <typename Config, typename Property>
bool operator==(std::nullptr_t, function<Config, Property> const& f) {
return !bool(f);
}
template <typename Config, typename Property>
bool operator!=(std::nullptr_t, function<Config, Property> const& f) {
return bool(f);
}
// Default intended object size of the function
using object_size = std::integral_constant<std::size_t, 32U>;
} // namespace detail
} // namespace abi_400
/// Can be passed to function_base as template argument which causes
/// the internal small buffer to be sized according to the given size,
/// and aligned with the given alignment.
template <std::size_t Capacity,
std::size_t Alignment = alignof(std::max_align_t)>
struct capacity_fixed {
static constexpr std::size_t capacity = Capacity;
static constexpr std::size_t alignment = Alignment;
};
/// Default capacity for small functor optimization
struct capacity_default
: capacity_fixed<detail::object_size::value - (2 * sizeof(void*))> {};
/// Can be passed to function_base as template argument which causes
/// the internal small buffer to be removed from the callable wrapper.
/// The owning function_base will then allocate memory for every object
/// it applies a type erasure on.
struct capacity_none : capacity_fixed<0UL> {};
/// Can be passed to function_base as template argument which causes
/// the internal small buffer to be sized such that it can hold
/// the given object without allocating memory for an applied type erasure.
template <typename T>
struct capacity_can_hold {
static constexpr std::size_t capacity = sizeof(T);
static constexpr std::size_t alignment = alignof(T);
};
/// An adaptable function wrapper base for arbitrary functional types.
///
/// \tparam IsOwning Is true when the type erasure shall be owning the object.
///
/// \tparam IsCopyable Defines whether the function is copyable or not
///
/// \tparam Capacity Defines the internal capacity of the function
/// for small functor optimization.
/// The size of the whole function object will be the capacity
/// plus the size of two pointers. If the capacity is zero,
/// the size will increase through one additional pointer
/// so the whole object has the size of 3 * sizeof(void*).
/// The type which is passed to the Capacity template parameter
/// shall provide a capacity and alignment member which
/// looks like the following example:
/// ```cpp
/// struct my_capacity {
/// static constexpr std::size_t capacity = sizeof(my_type);
/// static constexpr std::size_t alignment = alignof(my_type);
/// };
/// ```
///
/// \tparam IsThrowing Defines whether the function throws an exception on
/// empty function call, `std::abort` is called otherwise.
///
/// \tparam HasStrongExceptGuarantee Defines whether all objects satisfy the
/// strong exception guarantees,
/// which means the function type will satisfy
/// the strong exception guarantees too.
///
/// \tparam Signatures Defines the signature of the callable wrapper
///
template <bool IsOwning, bool IsCopyable, typename Capacity, bool IsThrowing,
bool HasStrongExceptGuarantee, typename... Signatures>
using function_base = detail::function<
detail::config<IsOwning, IsCopyable, Capacity>,
detail::property<IsThrowing, HasStrongExceptGuarantee, Signatures...>>;
/// An owning copyable function wrapper for arbitrary callable types.
template <typename... Signatures>
using function = function_base<true, true, capacity_default, //
true, false, Signatures...>;
/// An owning non copyable function wrapper for arbitrary callable types.
template <typename... Signatures>
using unique_function = function_base<true, false, capacity_default, //
true, false, Signatures...>;
/// A non owning copyable function wrapper for arbitrary callable types.
template <typename... Signatures>
using function_view = function_base<false, true, capacity_default, //
true, false, Signatures...>;
#if !defined(FU2_HAS_DISABLED_EXCEPTIONS)
/// Exception type that is thrown when invoking empty function objects
/// and exception support isn't disabled.
///
/// Exception support is enabled if
/// the template parameter 'Throwing' is set to true (default).
///
/// This type will default to std::bad_function_call if the
/// functional header is used, otherwise the library provides its own type.
///
/// You may disable the inclusion of the functional header
/// through defining `FU2_WITH_NO_FUNCTIONAL_HEADER`.
///
using detail::type_erasure::invocation_table::bad_function_call;
#endif
/// Returns a callable object, which unifies all callable objects
/// that were passed to this function.
///
/// ```cpp
/// auto overloaded = fu2::overload([](std::true_type) { return true; },
/// [](std::false_type) { return false; });
/// ```
///
/// \param callables A pack of callable objects with arbitrary signatures.
///
/// \returns A callable object which exposes the
///
template <typename... T>
constexpr auto overload(T&&... callables) {
return detail::overloading::overload(std::forward<T>(callables)...);
}
} // namespace fu2
#undef FU2_DETAIL_EXPAND_QUALIFIERS
#undef FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT
#undef FU2_DETAIL_EXPAND_CV
#undef FU2_DETAIL_EXPAND_CV_NOEXCEPT
#undef FU2_DETAIL_UNREACHABLE_INTRINSIC
#undef FU2_DETAIL_UNREACHABLE_INTRINSIC
#undef FU2_DETAIL_TRAP
#endif // FU2_INCLUDED_FUNCTION2_HPP_
// #include <continuable/continuable-base.hpp>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-promise-base.hpp>
// #include <continuable/detail/other/erasure.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_ERASURE_HPP_INCLUDED
#define CONTINUABLE_DETAIL_ERASURE_HPP_INCLUDED
#include <type_traits>
#include <utility>
// #include <function2/function2.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace erasure {
template <typename... Args>
using callback_erasure_t =
fu2::function_base<true, false, fu2::capacity_none, true, false,
void(Args...)&&, void(exception_arg_t, exception_t) &&>;
#ifdef CONTINUABLE_HAS_IMMEDIATE_TYPES
template <typename... Args>
using callback = callback_erasure_t<Args...>;
#else
template <typename... Args>
class callback;
template <typename T>
struct is_callback : std::false_type {};
template <typename... Args>
struct is_callback<callback<Args...>> : std::true_type {};
template <typename... Args>
class callback : public callback_erasure_t<Args...> {
public:
using erasure_t = callback_erasure_t<Args...>;
erasure_t erasure_;
callback() = default;
~callback() = default;
callback(callback const&) = delete;
callback(callback&&) = default;
callback& operator=(callback const&) = delete;
callback& operator=(callback&&) = default;
template <
typename T,
std::enable_if_t<std::is_convertible<T, erasure_t>::value>* = nullptr,
std::enable_if_t<!is_callback<traits::unrefcv_t<T>>::value>* = nullptr>
/* implicit */ callback(T&& callable) : erasure_(std::forward<T>(callable)) {
}
template <
typename T,
std::enable_if_t<std::is_assignable<erasure_t, T>::value>* = nullptr,
std::enable_if_t<!is_callback<traits::unrefcv_t<T>>::value>* = nullptr>
callback& operator=(T&& callable) {
erasure_ = std::forward<T>(callable);
return *this;
}
void operator()(Args... args) && noexcept {
std::move(erasure_)(std::move(args)...);
}
void operator()(exception_arg_t exception_arg, exception_t exception) &&
noexcept {
std::move(erasure_)(exception_arg, std::move(exception));
}
explicit operator bool() const noexcept {
return bool(erasure_);
}
};
#endif
using work_erasure_t =
fu2::function_base<true, false, fu2::capacity_fixed<32UL>, true, false,
void()&&, void(exception_arg_t, exception_t) &&>;
#ifdef CONTINUABLE_HAS_IMMEDIATE_TYPES
using work = work_erasure_t;
#else
class work;
template <typename T>
struct is_work : std::false_type {};
template <>
struct is_work<work> : std::true_type {};
class work {
using erasure_t = work_erasure_t;
erasure_t erasure_;
public:
work() = default;
~work() = default;
work(work const&) = delete;
work(work&&) = default;
work& operator=(work const&) = delete;
work& operator=(work&&) = default;
template <
typename T,
std::enable_if_t<std::is_convertible<T, erasure_t>::value>* = nullptr,
std::enable_if_t<!is_work<traits::unrefcv_t<T>>::value>* = nullptr>
/* implicit */ work(T&& callable) : erasure_(std::forward<T>(callable)) {
}
template <
typename T,
std::enable_if_t<std::is_assignable<erasure_t, T>::value>* = nullptr,
std::enable_if_t<!is_work<traits::unrefcv_t<T>>::value>* = nullptr>
work& operator=(T&& callable) {
erasure_ = std::forward<T>(callable);
return *this;
}
void operator()() && noexcept {
std::move(erasure_)();
}
void operator()(exception_arg_t, exception_t exception) && noexcept {
std::move(erasure_)(exception_arg_t{}, std::move(exception));
}
explicit operator bool() const noexcept {
return bool(erasure_);
}
};
#endif
template <typename... Args>
struct continuation_capacity {
using type = union {
void* pointer_;
base::ready_continuation<Args...> continuation_;
};
static constexpr std::size_t capacity = sizeof(type);
static constexpr std::size_t alignment = alignof(type);
};
template <typename... Args>
using continuation_erasure_t = fu2::function_base<
true, false, continuation_capacity<Args...>, true, false,
void(promise_base<callback<Args...>, signature_arg_t<Args...>>),
bool(is_ready_arg_t) const, result<Args...>(unpack_arg_t)>;
#ifdef CONTINUABLE_HAS_IMMEDIATE_TYPES
template <typename... Args>
using continuation = continuation_erasure_t<Args...>;
#else
template <typename... Args>
class continuation;
template <typename T>
struct is_continuation : std::false_type {};
template <typename... Args>
struct is_continuation<continuation<Args...>> : std::true_type {};
template <typename... Args>
class continuation {
using erasure_t = continuation_erasure_t<Args...>;
erasure_t erasure_;
public:
continuation() = default;
~continuation() = default;
continuation(continuation const&) = delete;
continuation(continuation&&) = default;
continuation& operator=(continuation const&) = delete;
continuation& operator=(continuation&&) = default;
template <
typename T,
std::enable_if_t<std::is_convertible<T, erasure_t>::value>* = nullptr,
std::enable_if_t<!is_continuation<traits::unrefcv_t<T>>::value>* =
nullptr>
/* implicit */ continuation(T&& callable)
: erasure_(std::forward<T>(callable)) {
}
template <
typename T,
std::enable_if_t<std::is_assignable<erasure_t, T>::value>* = nullptr,
std::enable_if_t<!is_continuation<traits::unrefcv_t<T>>::value>* =
nullptr>
continuation& operator=(T&& callable) {
erasure_ = std::forward<T>(callable);
return *this;
}
void operator()(promise_base<callback<Args...>, //
signature_arg_t<Args...>>
promise) {
erasure_(std::move(promise));
}
bool operator()(is_ready_arg_t is_ready_arg) const {
return erasure_(is_ready_arg);
}
result<Args...> operator()(unpack_arg_t query_arg) {
return erasure_(query_arg);
}
};
#endif
} // namespace erasure
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_ERASURE_HPP_INCLUDED
namespace cti {
/// \defgroup Types Types
/// provides the \link cti::continuable continuable\endlink and \link
/// cti::promise promise\endlink facility for type erasure.
/// \{
/// Deduces to the preferred continuation capacity for a possible
/// small functor optimization. The given capacity size is always enough to
/// to avoid any allocation when storing a ready continuable_base.
///
/// \since 4.0.0
template <typename... Args>
using continuation_capacity = detail::erasure::continuation_capacity<Args...>;
/// Defines a non-copyable continuation type which uses the
/// function2 backend for type erasure.
///
/// Usable like: `continuable<int, float>`
///
/// \note You can always define your own continuable with a type erasure of
/// choice, the type erasure wrapper just needs to accept a
/// callable object with a continuation signature as specified
/// in the Primitives section.
///
/// \since 1.0.0
template <typename... Args>
using continuable = continuable_base<detail::erasure::continuation<Args...>, //
signature_arg_t<Args...>>;
/// Defines a non-copyable promise type which is using the
/// function2 backend for type erasure.
///
/// Usable like: `promise<int, float>`
///
/// \note You can always define your own promise with a type erasure of
/// choice, the type erasure wrapper just needs to accept a
/// callable object with a callback signature as specified
/// in the Primitives section.
///
/// \since 1.0.0
template <typename... Args>
using promise = promise_base<detail::erasure::callback<Args...>, //
signature_arg_t<Args...>>;
/// Defines a non-copyable type erasure which is capable of carrying
/// callable objects passed to executors.
///
/// The work behaves like a `promise<>` but the work type erasure uses extra
/// stack space for small object optimization.
/// Additionally the outstanding work can be resolved through an exception.
///
/// \note You can always define your own cancelable_work with a type erasure of
/// choice, the type erasure wrapper just needs to accept a
/// callable object which is callable with a `void()` and
/// `void(exception_arg_t, exception_t)` signature.
///
/// \since 4.0.0
using work = promise_base<detail::erasure::work, //
signature_arg_t<>>;
/// \}
} // namespace cti
#endif // CONTINUABLE_TYPES_HPP_INCLUDED
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
# include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
// # include <continuable/detail/other/coroutines.hpp>
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
#if defined(CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE)
namespace cti {
# if defined(CONTINUABLE_HAS_EXCEPTIONS)
/// Is thrown from co_await expressions if the awaited continuable is canceled
///
/// Default constructed exception types that are returned by a cancelled
/// continuable are converted automatically to await_canceled_exception when
/// being returned by a co_await expression.
///
/// The await_canceled_exception gets converted again to a default constructed
/// exception type if it becomes unhandled inside a coroutine which
/// returns a continuable_base.
/// ```cpp
/// continuable<> cancelled_coroutine() {
/// co_await make_cancelling_continuable<void>();
///
/// co_return;
/// }
///
/// // ...
///
/// cancelled_coroutine().fail([](exception_t e) {
/// assert(bool(e) == false);
/// });
/// ```
///
/// \since 4.1.0
using await_canceled_exception = detail::awaiting::await_canceled_exception;
# endif // CONTINUABLE_HAS_EXCEPTIONS
} // namespace cti
/// \cond false
// As far as I know there is no other way to implement this specialization...
// NOLINTNEXTLINE(cert-dcl58-cpp)
namespace std {
namespace experimental {
template <typename Data, typename... Args, typename... FunctionArgs>
struct coroutine_traits<
cti::continuable_base<Data, cti::detail::identity<Args...>>,
FunctionArgs...> {
using promise_type = cti::detail::awaiting::promise_type<
cti::continuable<Args...>, cti::promise<Args...>, Args...>;
};
} // namespace experimental
} // namespace std
/// \endcond
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
#endif // CONTINUABLE_COROUTINE_HPP_INCLUDED
// #include <continuable/continuable-operations.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_OPERATIONS_HPP_INCLUDED
#define CONTINUABLE_OPERATIONS_HPP_INCLUDED
/// \defgroup Operations Operations
/// provides functions to work with asynchronous control flows.
// #include <continuable/operations/async.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_OPERATIONS_ASYNC_HPP_INCLUDED
#define CONTINUABLE_OPERATIONS_ASYNC_HPP_INCLUDED
#include <utility>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/operations/async.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_OPERATIONS_ASYNC_HPP_INCLUDED
#define CONTINUABLE_DETAIL_OPERATIONS_ASYNC_HPP_INCLUDED
// #include <continuable/continuable-base.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/utility/identity.hpp>
namespace cti {
namespace detail {
namespace operations {
template <typename Callable, typename Executor, typename... Args>
auto async(Callable&& callable, Executor&& executor, Args&&... args) {
using result_t =
decltype(util::invoke(std::forward<decltype(callable)>(callable),
std::forward<decltype(args)>(args)...));
constexpr auto hint =
decltype(base::decoration::invoker_of(identity<result_t>{}))::hint();
auto continuation = [callable = std::forward<decltype(callable)>(callable),
executor = std::forward<decltype(executor)>(executor),
args = std::make_tuple(std::forward<decltype(args)>(
args)...)](auto&& promise) mutable {
auto invoker = base::decoration::invoker_of(identity<result_t>{});
using promise_t = decltype(promise);
// Invoke the callback
traits::unpack(
[&](auto&&... args) mutable {
// Invoke the promise through the dedicated invoker
// and through the given executor
base::on_executor(std::move(executor), std::move(invoker),
std::move(callable),
std::forward<promise_t>(promise),
std::forward<decltype(args)>(args)...);
},
std::move(args));
};
return base::attorney::create_from(std::move(continuation), //
hint, util::ownership{});
}
} // namespace operations
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_OPERATIONS_ASYNC_HPP_INCLUDED
namespace cti {
/// \ingroup Operations
/// \{
/// Wraps the given callable inside a continuable_base such that it is
/// invoked when the asynchronous result is requested to return the result.
///
/// The async function shall be seen as an equivalent to std::async.
///
/// The behaviour will be equal as when using make_ready_continuable together
/// with continuable_base::then, but async is implemented in
/// a more efficient way:
/// ```cpp
/// auto do_sth() {
/// return async([] {
/// do_sth_more();
/// return 0;
/// });
/// }
/// ```
///
/// \param callable The callable type which is invoked on request.
///
/// \param args The arguments which are passed to the callable upon invocation.
///
/// \returns A continuable_base which asynchronous result type will
/// be computed with the same rules as continuable_base::then .
///
/// \since 4.0.0
///
template <typename Callable, typename... Args>
auto async(Callable&& callable, Args&&... args) {
return detail::operations::async(std::forward<Callable>(callable),
detail::types::this_thread_executor_tag{},
std::forward<Args>(args)...);
}
/// Wraps the given callable inside a continuable_base such that it is
/// invoked through the given executor when the asynchronous result
/// is requested to return the result.
///
/// The behaviour will be equal as when using make_ready_continuable together
/// with continuable_base::then and the given executor but async_on
/// is implemented in a more efficient way:
/// ```cpp
/// auto do_sth() {
/// auto executor = [](auto&& work) {
/// // Do something with the work here
/// std::forward<decltype(work)>(work);
/// };
///
/// return async_on([] {
/// do_sth_more();
/// return 0;
/// }, my_executor);
/// }
/// ```
///
/// \param callable The callable type which is invoked on request.
///
/// \param executor The executor that is used to dispatch the given callable.
///
/// \param args The arguments which are passed to the callable upon invocation.
///
/// \returns A continuable_base which asynchronous result type will
/// be computed with the same rules as continuable_base::then .
///
/// \since 4.0.0
///
template <typename Callable, typename Executor, typename... Args>
auto async_on(Callable&& callable, Executor&& executor, Args&&... args) {
return detail::operations::async(std::forward<Callable>(callable),
std::forward<Executor>(executor),
std::forward<Args>(args)...);
}
/// \}
} // namespace cti
#endif // CONTINUABLE_OPERATIONS_ASYNC_HPP_INCLUDED
// #include <continuable/operations/loop.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_OPERATIONS_LOOP_HPP_INCLUDED
#define CONTINUABLE_OPERATIONS_LOOP_HPP_INCLUDED
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-result.hpp>
// #include <continuable/detail/operations/loop.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_OPERATIONS_LOOP_HPP_INCLUDED
#define CONTINUABLE_DETAIL_OPERATIONS_LOOP_HPP_INCLUDED
#include <cassert>
#include <memory>
#include <tuple>
#include <type_traits>
// #include <continuable/continuable-base.hpp>
// #include <continuable/continuable-result.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
#include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS
namespace cti {
namespace detail {
template <typename T>
struct loop_trait {
static_assert(!std::is_same<T, T>::value,
"The callable passed to cti::loop must always return a "
"cti::continuable_base which resolves to a cti::result.");
};
template <typename... Args>
struct loop_trait<identity<result<Args...>>> {
template <typename Callable>
static auto make(Callable&& callable) {
return make_continuable<Args...>(std::forward<Callable>(callable));
}
};
template <>
struct loop_trait<identity<result<>>> {
template <typename Callable>
static auto make(Callable&& callable) {
return make_continuable<void>(std::forward<Callable>(callable));
}
};
namespace operations {
template <typename Promise, typename Callable, typename ArgsTuple>
class loop_frame : public std::enable_shared_from_this<
loop_frame<Promise, Callable, ArgsTuple>> {
Promise promise_;
Callable callable_;
ArgsTuple args_;
public:
explicit loop_frame(Promise promise, Callable callable, ArgsTuple args)
: promise_(std::move(promise)), callable_(std::move(callable)),
args_(std::move(args)) {
}
void loop() {
// MSVC can't evaluate this inside the lambda capture
auto me = this->shared_from_this();
traits::unpack(
[&](auto&&... args) mutable {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
try {
#endif // CONTINUABLE_HAS_EXCEPTIONS
util::invoke(callable_, std::forward<decltype(args)>(args)...)
.next([me = std::move(me)](auto&&... args) {
me->resolve(std::forward<decltype(args)>(args)...);
});
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
} catch (...) {
me->resolve(exception_arg_t{}, std::current_exception());
}
#endif // CONTINUABLE_HAS_EXCEPTIONS
},
args_);
}
template <typename Result>
void resolve(Result&& result) {
if (result.is_empty()) {
loop();
} else if (result.is_value()) {
traits::unpack(std::move(promise_), std::forward<Result>(result));
} else {
assert(result.is_exception());
std::move(promise_).set_exception(
std::forward<Result>(result).get_exception());
}
}
void resolve(exception_arg_t, exception_t exception) {
promise_.set_exception(std::move(exception));
}
};
template <typename Promise, typename Callable, typename ArgsTuple>
auto make_loop_frame(Promise&& promise, Callable&& callable,
ArgsTuple&& args_tuple) {
using frame_t =
loop_frame<traits::unrefcv_t<Promise>, traits::unrefcv_t<Callable>,
traits::unrefcv_t<ArgsTuple>>;
return std::make_shared<frame_t>(std::forward<Promise>(promise),
std::forward<Callable>(callable),
std::forward<ArgsTuple>(args_tuple));
}
template <typename Callable, typename... Args>
auto loop(Callable&& callable, Args&&... args) {
using invocation_result_t =
decltype(util::invoke(callable, args...).finish());
auto constexpr hint = base::annotation_of(identify<invocation_result_t>{});
using trait_t = loop_trait<std::remove_const_t<decltype(hint)>>;
return trait_t::make([callable = std::forward<decltype(callable)>(callable),
args = std::make_tuple(std::forward<decltype(args)>(
args)...)](auto&& promise) mutable {
// Do the actual looping
auto frame = make_loop_frame(std::forward<decltype(promise)>(promise),
std::move(callable), std::move(args));
frame->loop();
});
}
template <typename Callable, typename Begin, typename End>
auto make_range_looper(Callable&& callable, Begin&& begin, End&& end) {
return [callable = std::forward<Callable>(callable),
begin = std::forward<Begin>(begin),
end = std::forward<End>(end)]() mutable {
return util::invoke(callable, begin)
.then([&begin, &end]() mutable -> plain_t<result<>> {
// begin and end stays valid over the `then` here
if (++begin != end) {
return make_plain(result<>::empty());
} else {
return make_plain(make_result());
}
});
};
}
} // namespace operations
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_OPERATIONS_LOOP_HPP_INCLUDED
namespace cti {
/// \ingroup Operations
/// \{
/// Can be used to create an asynchronous loop.
///
/// The callable will be called repeatedly until it returns a
/// cti::continuable_base which then resolves to a present cti::result.
///
/// For better readability cti::loop_result, cti::loop_break and
/// cti::loop_continue are provided which can be used as following:
/// ```cpp
/// auto while_answer_not_yes() {
/// return loop([] {
/// return ask_something().then([](std::string answer) -> loop_result<> {
/// if (answer == "yes") {
/// return loop_break();
/// } else {
/// return loop_continue();
/// }
/// });
/// });
/// }
/// ```
///
/// \param callable The callable type which must return a cti::continuable_base
/// which then resolves to a cti::result of arbitrary values.
///
/// \param args The arguments that are passed to the callable upon
/// each invocation.
///
/// \since 4.0.0
///
template <typename Callable, typename... Args>
auto loop(Callable&& callable, Args&&... args) {
return detail::operations::loop(std::forward<Callable>(callable),
std::forward<Args>(args)...);
}
/// Can be used to indicate a specific result inside an asynchronous loop.
///
/// See cti::loop for details.
///
/// \since 4.0.0
template <typename... T>
using loop_result = plain_t<result<T...>>;
/// Can be used to create a loop_result which causes the loop to be
/// cancelled and resolved with the given arguments.
///
/// See cti::loop for details.
///
/// \since 4.0.0
template <typename... T>
auto loop_break(T&&... args) {
return make_plain(make_result(std::forward<T>(args)...));
}
/// Can be used to create a loop_result which causes the loop to be repeated.
///
/// See cti::loop for details.
///
/// \since 4.0.0
inline auto loop_continue() noexcept {
return empty_result{};
}
/// Can be used to create an asynchronous loop over a specific range.
///
/// The callable will be called repeatedly with each with begin increased
/// until end is reached.
///
/// ```cpp
/// auto iterate_some() {
/// // Iterate step from 0 to 9
/// return range_loop([] (int step) {
/// return do_something(i).then([] {
/// // You don't have to return a result here
/// });
/// }, 0, 10);
/// }
/// ```
///
/// \param callable The callable type which must return a cti::continuable_base
/// which then resolves to a cti::result of arbitrary values.
///
/// \param begin The iterator to iterate over
///
/// \param end The iterator to iterate until
///
/// \since 4.0.0
///
template <typename Callable, typename Iterator>
auto range_loop(Callable&& callable, Iterator begin, Iterator end) {
return detail::operations::loop( //
detail::operations::make_range_looper(std::forward<Callable>(callable),
begin, end));
}
/// \}
} // namespace cti
#endif // CONTINUABLE_OPERATIONS_LOOP_HPP_INCLUDED
// #include <continuable/operations/split.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_OPERATIONS_SPLIT_HPP_INCLUDED
#define CONTINUABLE_OPERATIONS_SPLIT_HPP_INCLUDED
#include <utility>
// #include <continuable/detail/operations/split.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_OPERATIONS_SPLIT_HPP_INCLUDED
#define CONTINUABLE_DETAIL_OPERATIONS_SPLIT_HPP_INCLUDED
#include <tuple>
#include <utility>
// #include <continuable/continuable-base.hpp>
// #include <continuable/continuable-traverse.hpp>
// #include <continuable/continuable-types.hpp>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace operations {
template <typename T, bool Else, typename = void>
struct operator_bool_or {
template <typename O>
static bool get(O&& /*obj*/) noexcept {
return Else;
}
};
template <typename T, bool Else>
struct operator_bool_or<T, Else,
traits::void_t<decltype(bool(std::declval<T&>()))>> {
template <typename O>
static bool get(O&& obj) noexcept {
return bool(obj);
}
};
template <typename First, typename... Promises>
class split_promise {
First first_;
std::tuple<Promises...> promises_;
public:
explicit split_promise(First first, Promises... promises)
: first_(std::move(first)), promises_(std::move(promises)...) {
}
template <typename... Args>
void operator()(Args&&... args) && {
traverse_pack(
[&](auto&& promise) mutable -> void {
using accessor =
operator_bool_or<traits::unrefcv_t<decltype(promise)>, true>;
if (accessor::get(promise)) {
std::forward<decltype(promise)>(promise)(args...);
}
},
std::move(promises_));
if (operator_bool_or<First, true>::get(first_)) {
std::move(first_)(std::forward<Args>(args)...);
}
}
template <typename... Args>
void set_value(Args... args) noexcept {
std::move (*this)(std::move(args)...);
}
void set_exception(exception_t error) noexcept {
std::move (*this)(exception_arg_t{}, std::move(error));
}
void set_canceled() noexcept {
std::move (*this)(exception_arg_t{}, exception_t{});
}
explicit operator bool() const noexcept {
bool is_valid = operator_bool_or<First, true>::get(first_);
traverse_pack(
[&](auto&& promise) mutable -> void {
using accessor =
operator_bool_or<traits::unrefcv_t<decltype(promise)>, true>;
if (!is_valid && accessor::get(promise)) {
is_valid = true;
}
},
promises_);
return is_valid;
}
};
} // namespace operations
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_OPERATIONS_SPLIT_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
/// \ingroup Operations
/// \{
/// Splits the asynchronous control flow and merges multiple promises/callbacks
/// together, which take the same types of arguments, into one.
///
/// The invocation order of all promises is undefined.
///
/// The split function is the opposite of the connection functions
/// like `when_all` because is can merge multiple waiters together rather than
/// joining those.
///
/// The split function can be used to resolve multiple waiters when resolving
/// a single promise.
/// ```cpp
/// class my_class {
/// cti::promise<> promise_;
///
/// public:
/// cti::continuable<> wait_for_sth() {
/// return [this](auto&& promise) mutable {
/// // Make sure accessing promise_ is done in a thread safe way!
/// promise_ = cti::split(std::move(promise_),
/// std::forward<decltype(promise)>(promise));
/// };
/// }
///
/// void resolve_all() {
/// // Resolves all waiting promises
/// promise_.set_value();
/// }
/// };
/// ```
///
/// \note The split function only works if all asynchronous arguments are
/// copyable. All asynchronous arguments and exceptions will be passed
/// to all split promises.
///
/// \param promises The promises to split the control flow into,
/// can be single promises or heterogeneous or homogeneous
/// containers of promises (see traverse_pack for a description
/// of supported nested arguments).
///
/// \returns A new promise with the same asynchronous result types as
/// the given promises.
///
/// \since 4.0.0
///
template <typename... Promises>
auto split(Promises&&... promises) {
return detail::operations::split_promise<
detail::traits::unrefcv_t<Promises>...>(
std::forward<Promises>(promises)...);
}
/// \}
} // namespace cti
#endif // CONTINUABLE_OPERATIONS_SPLIT_HPP_INCLUDED
#endif // CONTINUABLE_OPERATIONS_HPP_INCLUDED
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-promise-base.hpp>
// #include <continuable/continuable-promisify.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_PROMISIFY_HPP_INCLUDED
#define CONTINUABLE_PROMISIFY_HPP_INCLUDED
#include <type_traits>
#include <utility>
// #include <continuable/detail/other/promisify.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_PROMISIFY_HPP_INCLUDED
#define CONTINUABLE_DETAIL_PROMISIFY_HPP_INCLUDED
#include <type_traits>
// #include <continuable/continuable-base.hpp>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
#include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS
namespace cti {
namespace detail {
namespace convert {
/// A resolver for promisifying asio and js style callbacks.
inline auto default_resolver() {
return [](auto&& promise, auto&& e, auto&&... args) {
static_assert(
std::is_convertible<std::decay_t<decltype(e)>, exception_t>::value,
"The given error type must be convertible to the error type used! "
"Specify a custom resolver in order to apply a conversion to the "
"used error type.");
if (e) {
promise.set_exception(std::forward<decltype(e)>(e));
} else {
promise.set_value(std::forward<decltype(args)>(args)...);
}
};
}
template <typename... Result>
struct promisify_helper {
template <typename Resolver, typename Callable, typename... Args>
static auto from(Resolver&& resolver, Callable&& callable, Args&&... args) {
return make_continuable<Result...>(
[resolver = std::forward<Resolver>(resolver),
args = traits::make_flat_tuple(std::forward<Callable>(callable),
std::forward<Args>(args)...)](
auto&& promise) mutable {
traits::unpack(
[promise = std::forward<decltype(promise)>(promise),
&resolver](auto&&... args) mutable {
// Call the resolver from with the promise and result
auto callback =
[resolver = std::move(resolver),
promise = std::move(promise)](auto&&... args) mutable {
resolver(std::move(promise),
std::forward<decltype(args)>(args)...);
};
// Invoke the callback taking function
util::invoke(std::forward<decltype(args)>(args)...,
std::move(callback));
},
std::move(args));
});
}
};
} // namespace convert
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_PROMISIFY_HPP_INCLUDED
namespace cti {
/// \defgroup Promisify Promisify
/// provides helper methods to convert various callback styles to
/// \link continuable_base continuable_bases\endlink.
/// \{
/// Helper class for converting callback taking callable types into a
/// a continuable. Various styles are supported.
/// - `from`: Converts callback taking callable types into continuables
/// which pass an error code as first parameter and the rest of
/// the result afterwards.
///
/// \tparam Result The result of the converted continuable, this should align
/// with the arguments that are passed to the callback.
///
/// \since 3.0.0
template <typename... Result>
class promisify {
using helper = detail::convert::promisify_helper<Result...>;
public:
/// Converts callback taking callable types into a continuable.
/// This applies to calls which pass an error code as first parameter
/// and the rest of the asynchronous result afterwards.
///
/// See an example of how to promisify boost asio's async_resolve below:
/// ```cpp
/// auto async_resolve(std::string host, std::string service) {
/// return cti::promisify<asio::ip::udp::resolver::iterator>::from(
/// [&](auto&&... args) {
/// resolver_.async_resolve(std::forward<decltype(args)>(args)...);
/// },
/// std::move(host), std::move(service));
/// }
/// ```
///
/// A given error variable is converted to the used error type.
/// If this isn't possible you need to create a custom resolver callable
/// object \see with for details.
///
/// \since 3.0.0
template <typename Callable, typename... Args>
static auto from(Callable&& callable, Args&&... args) {
return helper::template from(detail::convert::default_resolver(),
std::forward<Callable>(callable),
std::forward<Args>(args)...);
}
/// \copybrief from
///
/// This modification of \ref from additionally takes a resolver callable
/// object which is used to resolve the promise from the given result.
///
/// See an example of how to promisify boost asio's async_resolve below:
/// ```cpp
/// auto async_resolve(std::string host, std::string service) {
/// return cti::promisify<asio::ip::udp::resolver::iterator>::with(
/// [](auto&& promise, auto&& e, auto&&... args) {
/// if (e) {
/// promise.set_exception(std::forward<decltype(e)>(e));
/// } else {
/// promise.set_value(std::forward<decltype(args)>(args)...);
/// }
/// },
/// [&](auto&&... args) {
/// resolver_.async_resolve(std::forward<decltype(args)>(args)...);
/// },
/// std::move(host), std::move(service));
/// }
/// ```
///
/// \since 4.0.0
template <typename Resolver, typename Callable, typename... Args>
static auto with(Resolver&& resolver, Callable&& callable, Args&&... args) {
return helper::template from(std::forward<Resolver>(resolver),
std::forward<Callable>(callable),
std::forward<Args>(args)...);
}
};
/// \}
} // namespace cti
#endif // CONTINUABLE_PROMISIFY_HPP_INCLUDED
// #include <continuable/continuable-result.hpp>
// #include <continuable/continuable-transforms.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TRANSFORMS_HPP_INCLUDED
#define CONTINUABLE_TRANSFORMS_HPP_INCLUDED
// #include <continuable/transforms/wait.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TRANSFORMS_WAIT_HPP_INCLUDED
#define CONTINUABLE_TRANSFORMS_WAIT_HPP_INCLUDED
#include <chrono>
#include <condition_variable>
#include <utility>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/transforms/wait.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TRANSFORMS_WAIT_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TRANSFORMS_WAIT_HPP_INCLUDED
#include <atomic>
#include <cassert>
#include <condition_variable>
#include <memory>
#include <mutex>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-result.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
# include <exception>
#endif
namespace cti {
namespace detail {
namespace transforms {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
class wait_transform_canceled_exception : public std::exception {
public:
wait_transform_canceled_exception() noexcept = default;
char const* what() const noexcept override {
return "cti::transforms::wait canceled due to cancellation of the "
"continuation";
}
};
#endif // CONTINUABLE_HAS_EXCEPTIONS
template <typename Hint>
struct sync_trait;
template <typename... Args>
struct sync_trait<identity<Args...>> {
using result_t = result<Args...>;
};
using lock_t = std::unique_lock<std::mutex>;
using condition_variable_t = std::condition_variable;
template <typename Data, typename Annotation,
typename Result = typename sync_trait<Annotation>::result_t>
Result wait_relaxed(continuable_base<Data, Annotation>&& continuable) {
// Do an immediate unpack if the continuable is ready
if (continuable.is_ready()) {
return std::move(continuable).unpack();
}
condition_variable_t cv;
std::mutex cv_mutex;
bool ready{false};
Result sync_result;
std::move(continuable)
.next([&](auto&&... args) {
sync_result = Result::from(std::forward<decltype(args)>(args)...);
lock_t lock(cv_mutex);
ready = true;
cv.notify_all();
})
.done();
lock_t lock(cv_mutex);
if (!ready) {
cv.wait(lock, [&] {
return ready;
});
}
return sync_result;
}
/// Transforms the continuation to sync execution and unpacks the result the if
/// possible
template <typename Data, typename Annotation>
auto wait_and_unpack(continuable_base<Data, Annotation>&& continuable) {
auto sync_result = wait_relaxed(std::move(continuable));
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
if (sync_result.is_value()) {
return std::move(sync_result).get_value();
} else {
assert(sync_result.is_exception());
if (exception_t e = sync_result.get_exception()) {
std::rethrow_exception(e);
} else {
throw wait_transform_canceled_exception();
}
}
#else
return sync_result;
#endif // CONTINUABLE_HAS_EXCEPTIONS
}
template <typename Result>
struct wait_frame {
std::mutex cv_mutex;
std::mutex rw_mutex;
condition_variable_t cv;
std::atomic_bool ready{false};
Result sync_result;
};
template <typename Data, typename Annotation, typename Waiter,
typename Result = typename sync_trait<Annotation>::result_t>
Result wait_unsafe(continuable_base<Data, Annotation>&& continuable,
Waiter&& waiter) {
// Do an immediate unpack if the continuable is ready
if (continuable.is_ready()) {
return std::move(continuable).unpack();
}
using frame_t = wait_frame<Result>;
auto frame = std::make_shared<frame_t>();
std::move(continuable)
.next([frame = std::weak_ptr<frame_t>(frame)](auto&&... args) {
if (auto locked = frame.lock()) {
{
std::lock_guard<std::mutex> rw_lock(locked->rw_mutex);
locked->sync_result = Result::from(
std::forward<decltype(args)>(args)...);
}
locked->ready.store(true, std::memory_order_release);
locked->cv.notify_all();
}
})
.done();
if (!frame->ready.load(std::memory_order_acquire)) {
lock_t lock(frame->cv_mutex);
std::forward<Waiter>(waiter)(frame->cv, lock, [&] {
return frame->ready.load(std::memory_order_acquire);
});
}
return ([&] {
std::lock_guard<std::mutex> rw_lock(frame->rw_mutex);
Result cached = std::move(frame->sync_result);
return cached;
})();
}
} // namespace transforms
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TRANSFORMS_WAIT_HPP_INCLUDED
namespace cti {
/// \ingroup Transforms
/// \{
namespace transforms {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
/// Is thrown from wait if the awaited continuable_base was cancelled,
/// which was signaled through resolving with a default
/// constructed exception type.
///
/// \since 4.1.0
using wait_transform_canceled_exception = detail::transforms::wait_transform_canceled_exception;
#endif // CONTINUABLE_HAS_EXCEPTIONS
/// Returns a transform that if applied to a continuable,
/// it will start the continuation chain and returns the result synchronously.
/// The current thread is blocked until the continuation chain is finished.
///
/// \returns Returns a value that is available immediately.
/// The signature of the future depends on the result type:
/// | Continuation type | Return type |
/// | : ------------------------------- | : -------------------------------- |
/// | `continuable_base with <>` | `void` |
/// | `continuable_base with <Arg>` | `Arg` |
/// | `continuable_base with <Args...>` | `std::tuple<Args...>` |
///
/// \throws wait_transform_canceled_exception if the awaited continuable_base
/// is cancelled, and thus was resolved with a default
/// constructed exception type.
///
/// \attention If exceptions are used, exceptions that are thrown, are rethrown
/// synchronously.
///
/// \since 4.0.0
inline auto wait() {
return [](auto&& continuable) {
return detail::transforms::wait_and_unpack(
std::forward<decltype(continuable)>(continuable));
};
}
/// \copybrief wait
///
/// \returns Returns a result that is available immediately.
/// The signature of the future depends on the result type:
/// | Continuation type | Return type |
/// | : ------------------------------- | : -------------------------------- |
/// | `continuable_base with <>` | `result<>` |
/// | `continuable_base with <Arg>` | `result<Arg>` |
/// | `continuable_base with <Args...>` | `result<Args...>` |
///
/// \attention Thrown exceptions returned through the result, also
/// make sure to check for a valid result value in case the
/// underlying time constraint timed out.
///
/// \since 4.0.0
template <typename Rep, typename Period>
auto wait_for(std::chrono::duration<Rep, Period> duration) {
return [duration](auto&& continuable) {
return detail::transforms::wait_unsafe(
std::forward<decltype(continuable)>(continuable),
[duration](detail::transforms::condition_variable_t& cv,
detail::transforms::lock_t& lock, auto&& predicate) {
cv.wait_for(lock, duration,
std::forward<decltype(predicate)>(predicate));
});
};
}
/// \copydoc wait_for
template <typename Clock, typename Duration>
auto wait_until(std::chrono::time_point<Clock, Duration> time_point) {
return [time_point](auto&& continuable) {
return detail::transforms::wait_unsafe(
std::forward<decltype(continuable)>(continuable),
[time_point](detail::transforms::condition_variable_t& cv,
detail::transforms::lock_t& lock, auto&& predicate) {
cv.wait_until(lock, time_point,
std::forward<decltype(predicate)>(predicate));
});
};
}
} // namespace transforms
/// \}
} // namespace cti
#endif // CONTINUABLE_TRANSFORMS_WAIT_HPP_INCLUDED
// #include <continuable/transforms/future.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TRANSFORMS_FUTURE_HPP_INCLUDED
#define CONTINUABLE_TRANSFORMS_FUTURE_HPP_INCLUDED
#include <utility>
// #include <continuable/detail/transforms/future.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.1.0
Copyright(c) 2015 - 2020 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_TRANSFORMS_FUTURE_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TRANSFORMS_FUTURE_HPP_INCLUDED
#include <future>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/util.hpp>
namespace cti {
namespace detail {
/// Provides helper functions to transform continuations to other types
namespace transforms {
/// Provides helper functions and typedefs for converting callback arguments
/// to their types a promise can accept.
template <typename... Args>
struct future_trait {
/// The promise type used to create the future
using promise_t = std::promise<std::tuple<Args...>>;
/// Boxes the argument pack into a tuple
static void resolve(promise_t& promise, Args... args) {
promise.set_value(std::make_tuple(std::move(args)...));
}
};
template <>
struct future_trait<> {
/// The promise type used to create the future
using promise_t = std::promise<void>;
/// Boxes the argument pack into void
static void resolve(promise_t& promise) {
promise.set_value();
}
};
template <typename First>
struct future_trait<First> {
/// The promise type used to create the future
using promise_t = std::promise<First>;
/// Boxes the argument pack into nothing
static void resolve(promise_t& promise, First first) {
promise.set_value(std::move(first));
}
};
template <typename Hint>
class promise_callback;
template <typename... Args>
class promise_callback<identity<Args...>> : public future_trait<Args...> {
typename future_trait<Args...>::promise_t promise_;
public:
constexpr promise_callback() = default;
promise_callback(promise_callback const&) = delete;
constexpr promise_callback(promise_callback&&) = default;
promise_callback& operator=(promise_callback const&) = delete;
promise_callback& operator=(promise_callback&&) = delete;
/// Resolves the promise
void operator()(Args... args) {
this->resolve(promise_, std::move(args)...);
}
/// Resolves the promise through the exception
void operator()(exception_arg_t, exception_t error) {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
promise_.set_exception(error);
#else
(void)error;
// Can't forward a std::error_condition or custom error type
// to a std::promise. Handle the error first in order
// to prevent this trap!
CTI_DETAIL_TRAP();
#endif // CONTINUABLE_HAS_EXCEPTIONS
}
/// Returns the future from the promise
auto get_future() {
return promise_.get_future();
}
};
/// Transforms the continuation to a future
template <typename Data, typename Annotation>
auto to_future(continuable_base<Data, Annotation>&& continuable) {
// Create the promise which is able to supply the current arguments
constexpr auto const hint =
base::annotation_of(identify<decltype(continuable)>{});
promise_callback<std::decay_t<decltype(hint)>> callback;
(void)hint;
// Receive the future
auto future = callback.get_future();
// Dispatch the continuation with the promise resolving callback
std::move(continuable).next(std::move(callback)).done();
return future;
}
} // namespace transforms
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TRANSFORMS_FUTURE_HPP_INCLUDED
namespace cti {
/// \ingroup Transforms
/// \{
namespace transforms {
/// Returns a transform that if applied to a continuable,
/// it will start the continuation chain and returns the asynchronous
/// result as `std::future<...>`.
///
/// \returns Returns a `std::future<...>` which becomes ready as soon
/// as the the continuation chain has finished.
/// The signature of the future depends on the result type:
/// | Continuation type | Return type |
/// | : ------------------------------- | : -------------------------------- |
/// | `continuable_base with <>` | `std::future<void>` |
/// | `continuable_base with <Arg>` | `std::future<Arg>` |
/// | `continuable_base with <Args...>` | `std::future<std::tuple<Args...>>` |
///
/// \attention If exceptions are used, exceptions that are thrown, are forwarded
/// to the returned future. If there are no exceptions supported,
/// you shall not pass any errors to the end of the asynchronous
/// call chain!
/// Otherwise this will yield a trap that causes application exit.
///
/// \since 2.0.0
inline auto to_future() {
return [](auto&& continuable) {
return detail::transforms::to_future(
std::forward<decltype(continuable)>(continuable));
};
}
} // namespace transforms
/// \}
} // namespace cti
#endif // CONTINUABLE_OPERATIONS_SPLIT_HPP_INCLUDED
namespace cti {
/// \defgroup Transforms Transforms
/// provides utilities to convert
/// \link continuable_base continuable_bases\endlink to other
/// types such as (`std::future`).
/// \{
/// The namespace transforms declares callable objects that transform
/// any continuable_base to an object or to a continuable_base itself.
///
/// Transforms can be applied to continuables through using
/// the cti::continuable_base::apply method accordingly.
namespace transforms {}
} // namespace cti
#endif // CONTINUABLE_TRANSFORMS_HPP_INCLUDED
// #include <continuable/continuable-traverse-async.hpp>
// #include <continuable/continuable-traverse.hpp>
// #include <continuable/continuable-types.hpp>
#endif // CONTINUABLE_HPP_INCLUDED
/// This is an automatic generated amalgamation of:
/// continuable version 4.0.0 (735697026b72a8f415d3443834cceeda9623780d)
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_HPP_INCLUDED
#define CONTINUABLE_HPP_INCLUDED
/// Declares the continuable library namespace.
///
/// The most important class is cti::continuable_base, that provides the
/// whole functionality for continuation chaining.
///
/// The class cti::continuable_base is created through the
/// cti::make_continuable() function which accepts a callback taking function.
///
/// Also there are following support functions available:
/// - cti::when_all() - connects cti::continuable_base's to an `all` connection.
/// - cti::when_any() - connects cti::continuable_base's to an `any` connection.
/// - cti::when_seq() - connects cti::continuable_base's to a sequence.
namespace cti {}
// #include <continuable/continuable-base.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_BASE_HPP_INCLUDED
#define CONTINUABLE_BASE_HPP_INCLUDED
#include <cassert>
#include <cstddef>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_PRIMITIVES_HPP_INCLUDED
#define CONTINUABLE_PRIMITIVES_HPP_INCLUDED
// #include <continuable/detail/core/types.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TYPES_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TYPES_HPP_INCLUDED
#include <type_traits>
#include <utility>
// #include <continuable/detail/features.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_FEATURES_HPP_INCLUDED
#define CONTINUABLE_DETAIL_FEATURES_HPP_INCLUDED
// Defines CONTINUABLE_WITH_NO_EXCEPTIONS when exception support is disabled
#ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
# if defined(_MSC_VER)
# if !defined(_HAS_EXCEPTIONS) || (_HAS_EXCEPTIONS == 0)
# define CONTINUABLE_WITH_NO_EXCEPTIONS
# endif
# elif defined(__clang__)
# if !(__EXCEPTIONS && __has_feature(cxx_exceptions))
# define CONTINUABLE_WITH_NO_EXCEPTIONS
# endif
# elif defined(__GNUC__)
# if !__EXCEPTIONS
# define CONTINUABLE_WITH_NO_EXCEPTIONS
# endif
# endif
#endif // CONTINUABLE_WITH_NO_EXCEPTIONS
// clang-format off
// Detect if the whole standard is available
#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || \
(__cplusplus >= 201703L)
#define CONTINUABLE_HAS_CXX17_CONSTEXPR_IF
#define CONTINUABLE_HAS_CXX17_DISJUNCTION
#define CONTINUABLE_HAS_CXX17_CONJUNCTION
#define CONTINUABLE_HAS_CXX17_VOID_T
#else
// Generic feature detection based on __has_feature
// and other preprocessor definitions based on:
// http://en.cppreference.com/w/User:D41D8CD98F/feature_testing_macros
#if defined(__has_feature)
#if !defined(CONTINUABLE_HAS_CXX17_CONSTEXPR_IF) && \
__has_feature(cxx_if_constexpr)
#define CONTINUABLE_HAS_CXX17_CONSTEXPR_IF
#endif
#endif
#if !defined(CONTINUABLE_HAS_CXX17_DISJUNCTION) && \
defined(__cpp_lib_experimental_logical_traits) && \
(__cpp_lib_experimental_logical_traits >= 201511)
#define CONTINUABLE_HAS_CXX17_DISJUNCTION
#endif
#if !defined(CONTINUABLE_HAS_CXX17_CONJUNCTION) && \
defined(__cpp_lib_experimental_logical_traits) && \
(__cpp_lib_experimental_logical_traits >= 201511)
#define CONTINUABLE_HAS_CXX17_CONJUNCTION
#endif
#if !defined(CONTINUABLE_HAS_CXX17_VOID_T) && \
defined(__cpp_lib_void_t) && \
(__cpp_lib_void_t >= 201411)
#define CONTINUABLE_HAS_CXX17_VOID_T
#endif
#endif
// Automatically detects support for coroutines.
// Parts of this detection mechanism were adapted from boost::asio,
// with support added for experimental coroutines.
#if !defined(CONTINUABLE_HAS_DISABLED_COROUTINE) \
&& !defined(CONTINUABLE_HAS_COROUTINE)
/// Define CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE when
/// CONTINUABLE_WITH_EXPERIMENTAL_COROUTINE is defined.
#if defined(CONTINUABLE_WITH_EXPERIMENTAL_COROUTINE)
#define CONTINUABLE_HAS_COROUTINE 1
#elif defined(CONTINUABLE_WITH_COROUTINE)
#define CONTINUABLE_HAS_COROUTINE 1
#define CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE 1
#elif defined(_MSC_VER) // Visual Studio
#if (_MSC_VER >= 1928) && (_MSVC_LANG >= 201705)
#define CONTINUABLE_HAS_COROUTINE 1
#elif _MSC_FULL_VER >= 190023506
#if defined(_RESUMABLE_FUNCTIONS_SUPPORTED)
#define CONTINUABLE_HAS_COROUTINE 1
#define CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE 1
#endif // defined(_RESUMABLE_FUNCTIONS_SUPPORTED)
#endif // _MSC_FULL_VER >= 190023506
#elif defined(__clang__) // Clang
#if defined(__cpp_coroutines) && (__cpp_coroutines >= 201703L)
#define CONTINUABLE_HAS_COROUTINE 1
#if defined(_LIBCPP_EXPERIMENTAL_COROUTINE)
#define CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE 1
#endif
#endif // defined(__cpp_coroutines) && (__cpp_coroutines >= 201703L)
#elif defined(__GNUC__) // GCC
#if (__cplusplus >= 201709) && (__cpp_impl_coroutine >= 201902)
#if __has_include(<coroutine>)
#define CONTINUABLE_HAS_COROUTINE 1
#endif // __has_include(<coroutine>)
#endif // (__cplusplus >= 201709) && (__cpp_impl_coroutine >= 201902)
#endif
#endif
/// Define CONTINUABLE_HAS_EXCEPTIONS when exceptions are used
#if !defined(CONTINUABLE_WITH_CUSTOM_ERROR_TYPE) && \
!defined(CONTINUABLE_WITH_NO_EXCEPTIONS)
#define CONTINUABLE_HAS_EXCEPTIONS 1
#else
#undef CONTINUABLE_HAS_EXCEPTIONS
#endif
/// Define CONTINUABLE_HAS_IMMEDIATE_TYPES when either
/// - CONTINUABLE_WITH_IMMEDIATE_TYPES is defined
/// - Building in release mode (NDEBUG is defined)
///
/// Build error messages will become more readable in debug mode while
/// we don't suffer any runtime penalty in release.
#if defined(CONTINUABLE_WITH_IMMEDIATE_TYPES) || defined(NDEBUG)
#define CONTINUABLE_HAS_IMMEDIATE_TYPES 1
#else
#undef CONTINUABLE_HAS_IMMEDIATE_TYPES
#endif
// clang-format on
#endif // CONTINUABLE_DETAIL_FEATURES_HPP_INCLUDED
// #include <continuable/detail/utility/identity.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_IDENTITY_HPP_INCLUDED
#define CONTINUABLE_DETAIL_IDENTITY_HPP_INCLUDED
#include <type_traits>
// #include <continuable/detail/features.hpp>
namespace cti {
namespace detail {
/// A tagging type for wrapping other types
template <typename... T>
struct identity {};
template <typename>
struct is_identity : std::false_type {};
template <typename... Args>
struct is_identity<identity<Args...>> : std::true_type {};
template <typename T>
using identify = std::conditional_t<is_identity<std::decay_t<T>>::value, T,
identity<std::decay_t<T>>>;
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_IDENTITY_HPP_INCLUDED
#ifndef CONTINUABLE_WITH_CUSTOM_ERROR_TYPE
#ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
#include <exception>
#else // CONTINUABLE_WITH_NO_EXCEPTIONS
#include <system_error>
#endif // CONTINUABLE_WITH_NO_EXCEPTIONS
#endif // CONTINUABLE_WITH_CUSTOM_ERROR_TYPE
namespace cti {
template <typename Data, typename Annotation>
class continuable_base;
namespace detail {
/// Contains types used globally across the library
namespace types {
#ifdef CONTINUABLE_WITH_CUSTOM_ERROR_TYPE
using exception_t = CONTINUABLE_WITH_CUSTOM_ERROR_TYPE;
#else // CONTINUABLE_WITH_CUSTOM_ERROR_TYPE
#ifndef CONTINUABLE_WITH_NO_EXCEPTIONS
/// Represents the exception type when exceptions are enabled
using exception_t = std::exception_ptr;
#else // CONTINUABLE_WITH_NO_EXCEPTIONS
/// Represents the error type when exceptions are disabled
using exception_t = std::error_condition;
#endif // CONTINUABLE_WITH_NO_EXCEPTIONS
#endif // CONTINUABLE_WITH_CUSTOM_ERROR_TYPE
/// A tag which is used to execute the continuation inside the current thread
struct this_thread_executor_tag {};
/// Marks a given callable object as transformation
template <typename T>
class plain_tag {
T value_;
public:
template <typename O, std::enable_if_t<std::is_constructible<
T, std::decay_t<O>>::value>* = nullptr>
/* implicit */ plain_tag(O&& value) : value_(std::forward<O>(value)) {
}
explicit plain_tag(T value) : value_(std::move(value)) {
}
T&& consume() && {
return std::move(value_);
}
};
} // namespace types
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TYPES_HPP_INCLUDED
// #include <continuable/detail/utility/identity.hpp>
namespace cti {
/// \defgroup Primitives Primitives
/// provides basic tag types for creating a customized callbacks
/// and continuations.
///
/// For the callback and the continuation `Args...` represents the
/// asynchronous result:
/// ```cpp
/// template<typename... Args>
/// struct continuation {
/// void operator() (callback<Args...>);
/// bool operator() (cti::is_ready_arg_t) const;
/// result<Args...> operator() (cti::unpack_arg_t);
/// };
/// ```
/// ```cpp
/// template<typename... Args>
/// struct callback {
/// void operator() (Args...) &&;
/// void operator() (cti::exception_arg_t, cti::exception_t) &&;
/// };
/// ```
/// \{
/// Represents the tag type that is used to specify the signature hint
/// of a continuable_base or promise_base.
///
/// \since 4.0.0
template <typename... Args>
using signature_arg_t = detail::identity<Args...>;
/// Represents the tag type that is used to query the continuation
/// for whether it resolves the callback instantly with its arguments
/// without having side effects.
///
/// \since 4.0.0
struct is_ready_arg_t {};
/// Represents the tag type that is used to unpack the result of a continuation.
///
/// \attention It's required that the query of is_ready_arg_t returns true,
/// otherwise the behaviour when unpacking is unspecified.
///
/// \since 4.0.0
struct unpack_arg_t {};
/// \copydoc unpack_arg_t
///
/// \deprecated The query_arg_t was deprecated because of
/// its new naming unpack_arg_t.
///
[[deprecated("The dispatch_error_tag was replaced by unpack_arg_t and will "
"be removed in a later major version!")]] //
typedef unpack_arg_t query_arg_t;
/// Represents the tag type that is used to disambiguate the
/// callback operator() in order to take the exception asynchronous chain.
///
/// \note see continuable::next for details.
///
/// \since 4.0.0
struct exception_arg_t {};
/// \copydoc exception_arg_t
///
/// \deprecated The dispatch_error_tag was deprecated in order to move closer
/// to the types specified in the "A Unified Future" proposal
/// especially regarding naming types similar.
///
[[deprecated("The dispatch_error_tag was replaced by exception_arg_t and will "
"be removed in a later major version!")]] //
typedef exception_arg_t dispatch_error_tag;
/// Represents the type that is used as exception type
///
/// By default this type deduces to `std::exception_ptr`.
/// If `CONTINUABLE_WITH_NO_EXCEPTIONS` is defined the type
/// will be a `std::error_condition`.
/// A custom error type may be set through
/// defining `CONTINUABLE_WITH_CUSTOM_ERROR_TYPE`.
///
/// \since 4.0.0
using exception_t = detail::types::exception_t;
/// \copydoc exception_t
///
/// \deprecated The error_type was deprecated in order to move closer
/// to the types specified in the "A Unified Future" proposal
/// especially regarding naming types similar.
///
[[deprecated("The error_type was replaced by exception_t and will "
"be removed in a later major version!")]] //
typedef exception_t error_type;
/// Represents the type that is used to disable the special meaning of types
/// which are returned by a asynchronous result handler.
/// See cti::plain for details.
///
/// \since 4.0.0
template <typename T>
using plain_t = detail::types::plain_tag<T>;
/// \}
} // namespace cti
#endif // CONTINUABLE_PRIMITIVES_HPP_INCLUDED
// #include <continuable/continuable-result.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_RESULT_HPP_INCLUDED
#define CONTINUABLE_RESULT_HPP_INCLUDED
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/detail/utility/result-trait.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_RESULT_TRAIT_HPP_INCLUDED
#define CONTINUABLE_DETAIL_RESULT_TRAIT_HPP_INCLUDED
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/core/annotation.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_ANNOTATION_HPP_INCLUDED
#define CONTINUABLE_DETAIL_ANNOTATION_HPP_INCLUDED
#include <type_traits>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/utility/traits.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TRAITS_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TRAITS_HPP_INCLUDED
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/identity.hpp>
namespace cti {
namespace detail {
namespace traits {
/// Removes all references and qualifiers from the given type T,
/// since std::decay has too much overhead through checking for
/// function pointers and arrays also.
template <typename T>
using unrefcv_t = std::remove_cv_t<std::remove_reference_t<T>>;
namespace detail {
template <typename T, typename... Args>
struct index_of_impl;
template <typename T, typename... Args>
struct index_of_impl<T, T, Args...> : std::integral_constant<std::size_t, 0U> {
};
template <typename T, typename U, typename... Args>
struct index_of_impl<T, U, Args...>
: std::integral_constant<std::size_t,
1 + index_of_impl<T, Args...>::value> {};
} // namespace detail
/// Evaluates to the index of T in the given pack
template <typename T, typename... Args>
using index_of_t = detail::index_of_impl<T, Args...>;
/// Creates a tuple in which r-values gets copied and
/// l-values keep their l-value.
template <typename... T>
auto make_flat_tuple(T&&... args) {
return std::tuple<T...>{std::forward<T>(args)...};
}
#if defined(CONTINUABLE_HAS_CXX17_VOID_T)
using std::void_t;
#else
namespace detail {
// Equivalent to C++17's std::void_t which targets a bug in GCC,
// that prevents correct SFINAE behavior.
// See http://stackoverflow.com/questions/35753920 for details.
template <typename...>
struct deduce_to_void : std::common_type<void> {};
} // namespace detail
/// C++17 like void_t type
template <typename... T>
using void_t = typename detail::deduce_to_void<T...>::type;
#endif // CONTINUABLE_HAS_CXX17_VOID_T
namespace detail_unpack {
using std::get;
/// Calls the given unpacker with the content of the given sequenceable
template <typename U, typename F, std::size_t... I>
constexpr auto unpack_impl(U&& unpacker, F&& first_sequenceable,
std::integer_sequence<std::size_t, I...>)
-> decltype(std::forward<U>(unpacker)(
get<I>(std::forward<F>(first_sequenceable))...)) {
(void)first_sequenceable;
return std::forward<U>(unpacker)(
get<I>(std::forward<F>(first_sequenceable))...);
}
} // namespace detail_unpack
/// Calls the given callable object with the content of the given sequenceable
///
/// \note We can't use std::apply here since this implementation is SFINAE
/// aware and the std version not! This would lead to compilation errors.
template <typename Callable, typename TupleLike,
typename Sequence = std::make_index_sequence<
std::tuple_size<std::decay_t<TupleLike>>::value>>
constexpr auto unpack(Callable&& obj, TupleLike&& tuple_like)
-> decltype(detail_unpack::unpack_impl(std::forward<Callable>(obj),
std::forward<TupleLike>(tuple_like),
Sequence{})) {
return detail_unpack::unpack_impl(std::forward<Callable>(obj),
std::forward<TupleLike>(tuple_like),
Sequence{});
}
namespace detail {
template <typename T, typename Args, typename = traits::void_t<>>
struct is_invokable_impl : std::common_type<std::false_type> {};
template <typename T, typename... Args>
struct is_invokable_impl<
T, std::tuple<Args...>,
void_t<decltype(std::declval<T>()(std::declval<Args>()...))>>
: std::common_type<std::true_type> {};
} // namespace detail
/// Deduces to a std::true_type if the given type is callable with the arguments
/// inside the given tuple.
/// The main reason for implementing it with the detection idiom instead of
/// hana like detection is that MSVC has issues with capturing raw template
/// arguments inside lambda closures.
///
/// ```cpp
/// traits::is_invocable<object, std::tuple<Args...>>
/// ```
template <typename T, typename Args>
using is_invocable_from_tuple =
typename detail::is_invokable_impl<T, Args>::type;
// Checks whether the given callable object is invocable with the given
// arguments. This doesn't take member functions into account!
template <typename T, typename... Args>
using is_invocable = is_invocable_from_tuple<T, std::tuple<Args...>>;
/// Deduces to a std::false_type
template <typename T>
using fail = std::integral_constant<bool, !std::is_same<T, T>::value>;
#ifdef CONTINUABLE_HAS_CXX17_DISJUNCTION
using std::disjunction;
#else
namespace detail {
/// Declares a C++14 polyfill for C++17 std::disjunction.
template <typename Args, typename = void_t<>>
struct disjunction_impl : std::common_type<std::true_type> {};
template <typename... Args>
struct disjunction_impl<identity<Args...>,
void_t<std::enable_if_t<!bool(Args::value)>...>>
: std::common_type<std::false_type> {};
} // namespace detail
template <typename... Args>
using disjunction = typename detail::disjunction_impl<identity<Args...>>::type;
#endif // CONTINUABLE_HAS_CXX17_DISJUNCTION
#ifdef CONTINUABLE_HAS_CXX17_CONJUNCTION
using std::conjunction;
#else
namespace detail {
/// Declares a C++14 polyfill for C++17 std::conjunction.
template <typename Args, typename = void_t<>>
struct conjunction_impl : std::common_type<std::false_type> {};
template <typename... Args>
struct conjunction_impl<identity<Args...>,
void_t<std::enable_if_t<bool(Args::value)>...>>
: std::common_type<std::true_type> {};
} // namespace detail
template <typename... Args>
using conjunction = typename detail::conjunction_impl<identity<Args...>>::type;
#endif // CONTINUABLE_HAS_CXX17_CONJUNCTION
} // namespace traits
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TRAITS_HPP_INCLUDED
namespace cti {
namespace detail {
namespace hints {
/// Extracts the signature we pass to the internal continuable
/// from an argument pack as specified by make_continuable.
///
/// This is the overload taking an arbitrary amount of args
template <typename... HintArgs>
struct from_args : std::common_type<signature_arg_t<HintArgs...>> {};
template <>
struct from_args<void> : std::common_type<signature_arg_t<>> {};
} // namespace hints
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_ANNOTATION_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_UTIL_HPP_INCLUDED
#define CONTINUABLE_DETAIL_UTIL_HPP_INCLUDED
#include <cassert>
#include <cstdlib>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
/// Hint for the compiler that this point should be unreachable
#if defined(_MSC_VER)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE_INTRINSIC() __assume(false)
#elif defined(__GNUC__)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable()
#elif defined(__has_builtin) && __has_builtin(__builtin_unreachable)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE_INTRINSIC() abort()
#endif
/// Causes the application to exit abnormally
#if defined(_MSC_VER)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_TRAP() __debugbreak()
#elif defined(__GNUC__)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_TRAP() __builtin_trap()
#elif defined(__has_builtin) && __has_builtin(__builtin_trap)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_TRAP() __builtin_trap()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_TRAP() *(volatile int*)0x11 = 0
#endif
#ifndef NDEBUG
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE() ::cti::detail::util::unreachable_debug()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CTI_DETAIL_UNREACHABLE() CTI_DETAIL_UNREACHABLE_INTRINSIC()
#endif
namespace cti {
namespace detail {
/// Utility namespace which provides useful meta-programming support
namespace util {
#ifndef NDEBUG
[[noreturn]] inline void unreachable_debug() {
CTI_DETAIL_TRAP();
std::abort();
}
#endif
/// Helper to trick compilers about that a parameter pack is used
template <typename... T>
constexpr void unused(T&&...) noexcept {
}
namespace detail {
template <typename T, std::size_t... I>
auto forward_except_last_impl(T&& tuple,
std::integer_sequence<std::size_t, I...>) {
(void)tuple;
return std::forward_as_tuple(std::get<I>(std::forward<T>(tuple))...);
}
template <std::size_t Size>
constexpr auto make_decreased_index_sequence(
std::integral_constant<std::size_t, Size>) noexcept {
return std::make_index_sequence<Size - 1>();
}
inline void make_decreased_index_sequence(
std::integral_constant<std::size_t, 0U>) noexcept {
// This function is only instantiated on a compiler error and
// should not be included in valid code.
// See https://github.com/Naios/continuable/issues/21 for details.
CTI_DETAIL_UNREACHABLE();
}
/// Forwards every element in the tuple except the last one
template <typename T>
auto forward_except_last(T&& sequenceable) {
static_assert(
std::tuple_size<std::decay_t<T>>::value > 0U,
"Attempt to remove a parameter from an empty tuple like type! If you see "
"this your compiler could run into possible infinite recursion! Open a "
"ticket at https://github.com/Naios/continuable/issues with a small "
"reproducible example if your compiler doesn't stop!");
constexpr auto size = std::tuple_size<std::decay_t<T>>::value;
constexpr auto sequence = make_decreased_index_sequence(
std::integral_constant<std::size_t, size>{});
return forward_except_last_impl(std::forward<T>(sequenceable), sequence);
}
template <std::size_t Keep>
struct invocation_env {
/// We are able to call the callable with the arguments given in the tuple
template <typename T, typename... Args>
static auto partial_invoke_impl(std::true_type, T&& callable,
std::tuple<Args...> args) {
return traits::unpack(std::forward<T>(callable), std::move(args));
}
/// We were unable to call the callable with the arguments in the tuple.
/// Remove the last argument from the tuple and try it again.
template <typename T, typename... Args>
static auto partial_invoke_impl(std::false_type, T&& callable,
std::tuple<Args...> args) {
// If you are encountering this assertion you tried to attach a callback
// which can't accept the arguments of the continuation.
//
// ```cpp
// continuable<int, int> c;
// std::move(c).then([](std::vector<int> v) { /*...*/ })
// ```
static_assert(
sizeof...(Args) > Keep,
"There is no way to call the given object with these arguments!");
// Remove the last argument from the tuple
auto next = forward_except_last(std::move(args));
// Test whether we are able to call the function with the given tuple
constexpr std::integral_constant<
bool, traits::is_invocable_from_tuple<decltype(callable),
decltype(next)>::value ||
(sizeof...(Args) <= Keep)>
is_callable;
return partial_invoke_impl(is_callable, std::forward<T>(callable),
std::move(next));
}
/// Shortcut - we can call the callable directly
template <typename T, typename... Args>
static auto partial_invoke_impl_shortcut(std::true_type, T&& callable,
Args&&... args) {
return std::forward<T>(callable)(std::forward<Args>(args)...);
}
/// Failed shortcut - we were unable to invoke the callable with the
/// original arguments.
template <typename T, typename... Args>
static auto partial_invoke_impl_shortcut(std::false_type failed, T&& callable,
Args&&... args) {
// Our shortcut failed, convert the arguments into a forwarding tuple
return partial_invoke_impl(
failed, std::forward<T>(callable),
std::forward_as_tuple(std::forward<Args>(args)...));
}
};
} // namespace detail
/// Partially invokes the given callable with the given arguments.
///
/// \note This function will assert statically if there is no way to call the
/// given object with less arguments.
template <std::size_t KeepArgs, typename T, typename... Args>
/*keep this inline*/ inline auto
partial_invoke(std::integral_constant<std::size_t, KeepArgs>, T&& callable,
Args&&... args) {
// Test whether we are able to call the function with the given arguments.
constexpr traits::is_invocable_from_tuple<decltype(callable),
std::tuple<Args...>>
is_invocable;
// The implementation is done in a shortcut way so there are less
// type instantiations needed to call the callable with its full signature.
using env = detail::invocation_env<KeepArgs>;
return env::partial_invoke_impl_shortcut(
is_invocable, std::forward<T>(callable), std::forward<Args>(args)...);
}
/// Invokes the given callable object with the given arguments
template <typename Callable, typename... Args>
constexpr auto invoke(Callable&& callable, Args&&... args) noexcept(
noexcept(std::forward<Callable>(callable)(std::forward<Args>(args)...)))
-> decltype(std::forward<Callable>(callable)(std::forward<Args>(args)...)) {
return std::forward<Callable>(callable)(std::forward<Args>(args)...);
}
/// Invokes the given member function pointer by reference
template <typename T, typename Type, typename Self, typename... Args>
constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
noexcept((std::forward<Self>(self).*member)(std::forward<Args>(args)...)))
-> decltype((std::forward<Self>(self).*
member)(std::forward<Args>(args)...)) {
return (std::forward<Self>(self).*member)(std::forward<Args>(args)...);
}
/// Invokes the given member function pointer by pointer
template <typename T, typename Type, typename Self, typename... Args>
constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
noexcept((std::forward<Self>(self)->*member)(std::forward<Args>(args)...)))
-> decltype(
(std::forward<Self>(self)->*member)(std::forward<Args>(args)...)) {
return (std::forward<Self>(self)->*member)(std::forward<Args>(args)...);
}
/// Returns a constant view on the object
template <typename T>
constexpr std::add_const_t<T>& as_const(T& object) noexcept {
return object;
}
// Class for making child classes non copyable
struct non_copyable {
constexpr non_copyable() = default;
non_copyable(non_copyable const&) = delete;
constexpr non_copyable(non_copyable&&) = default;
non_copyable& operator=(non_copyable const&) = delete;
non_copyable& operator=(non_copyable&&) = default;
};
// Class for making child classes non copyable and movable
struct non_movable {
constexpr non_movable() = default;
non_movable(non_movable const&) = delete;
constexpr non_movable(non_movable&&) = delete;
non_movable& operator=(non_movable const&) = delete;
non_movable& operator=(non_movable&&) = delete;
};
/// This class is responsible for holding an abstract copy- and
/// move-able ownership that is invalidated when the object
/// is moved to another instance.
class ownership {
explicit constexpr ownership(bool acquired, bool frozen)
: acquired_(acquired), frozen_(frozen) {
}
public:
constexpr ownership() : acquired_(true), frozen_(false) {
}
constexpr ownership(ownership const&) = default;
ownership(ownership&& right) noexcept
: acquired_(right.consume()), frozen_(right.is_frozen()) {
}
ownership& operator=(ownership const&) = default;
ownership& operator=(ownership&& right) noexcept {
acquired_ = right.consume();
frozen_ = right.is_frozen();
return *this;
}
// Merges both ownerships together
ownership operator|(ownership const& right) const noexcept {
return ownership(is_acquired() && right.is_acquired(),
is_frozen() || right.is_frozen());
}
constexpr bool is_acquired() const noexcept {
return acquired_;
}
constexpr bool is_frozen() const noexcept {
return frozen_;
}
void release() noexcept {
assert(is_acquired() && "Tried to release the ownership twice!");
acquired_ = false;
}
void freeze(bool enabled = true) noexcept {
assert(is_acquired() && "Tried to freeze a released object!");
frozen_ = enabled;
}
private:
bool consume() noexcept {
if (is_acquired()) {
release();
return true;
}
return false;
}
/// Is true when the object is in a valid state
bool acquired_ : 1;
/// Is true when the automatic invocation on destruction is disabled
bool frozen_ : 1;
};
} // namespace util
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_UTIL_HPP_INCLUDED
namespace cti {
namespace detail {
struct void_arg_t { };
template <typename... T>
struct result_trait;
template <>
struct result_trait<> {
using value_t = void;
using surrogate_t = void_arg_t;
static constexpr surrogate_t wrap() noexcept {
return {};
}
static constexpr void unwrap(surrogate_t) {
}
};
template <typename T>
struct result_trait<T> {
using value_t = T;
using surrogate_t = value_t;
static surrogate_t wrap(T arg) {
return std::move(arg);
}
template <typename R>
static decltype(auto) unwrap(R&& unwrap) {
return std::forward<R>(unwrap);
}
template <std::size_t I, typename Result>
static decltype(auto) get(Result&& result) {
return std::forward<Result>(result).get_value();
}
};
template <typename First, typename Second, typename... Rest>
struct result_trait<First, Second, Rest...> {
using value_t = std::tuple<First, Second, Rest...>;
using surrogate_t = value_t;
static surrogate_t wrap(First first, Second second, Rest... rest) {
return std::make_tuple(std::move(first), std::move(second),
std::move(rest)...);
}
template <typename R>
static decltype(auto) unwrap(R&& unwrap) {
return std::forward<R>(unwrap);
}
template <std::size_t I, typename Result>
static decltype(auto) get(Result&& result) {
return std::get<I>(std::forward<Result>(result).get_value());
}
};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_RESULT_TRAIT_HPP_INCLUDED
// #include <continuable/detail/utility/result-variant.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_RESULT_VARIANT_HPP_INCLUDED
#define CONTINUABLE_DETAIL_RESULT_VARIANT_HPP_INCLUDED
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <memory>
#include <type_traits>
#include <utility>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace container {
enum class result_slot_t : std::uint8_t {
slot_empty,
slot_value,
slot_exception,
};
} // namespace container
struct init_empty_arg_t {};
struct init_result_arg_t {};
struct init_exception_arg_t {};
template <typename T>
class result_variant {
static constexpr bool is_nothrow_destructible = //
std::is_nothrow_destructible<T>::value &&
std::is_nothrow_destructible<exception_t>::value;
static constexpr bool is_nothrow_move_constructible = //
std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_constructible<exception_t>::value;
public:
result_variant() = default;
~result_variant() noexcept(is_nothrow_destructible) {
destroy();
}
explicit result_variant(init_empty_arg_t) noexcept
: slot_(container::result_slot_t::slot_empty) {}
explicit result_variant(init_result_arg_t, T value) noexcept(
std::is_nothrow_destructible<T>::value&&
std::is_nothrow_move_constructible<T>::value)
: slot_(container::result_slot_t::slot_value) {
new (value_ptr()) T(std::move(value));
}
explicit result_variant(init_exception_arg_t, exception_t exception) noexcept(
std::is_nothrow_destructible<exception_t>::value&&
std::is_nothrow_move_constructible<exception_t>::value)
: slot_(container::result_slot_t::slot_exception) {
new (exception_ptr()) exception_t(std::move(exception));
}
result_variant(result_variant const&) = delete;
result_variant& operator=(result_variant const&) = delete;
result_variant(result_variant&& other) noexcept(
is_nothrow_destructible&& is_nothrow_move_constructible)
: slot_(other.slot_) {
switch (other.slot_) {
case container::result_slot_t::slot_value: {
new (value_ptr()) T(std::move(*other.value_ptr()));
break;
}
case container::result_slot_t::slot_exception: {
new (exception_ptr()) exception_t(std::move(*other.exception_ptr()));
break;
}
default: {
break;
}
}
other.destroy();
other.slot_ = container::result_slot_t::slot_empty;
}
result_variant& operator=(result_variant&& other) noexcept(
is_nothrow_destructible&& is_nothrow_move_constructible) {
destroy();
slot_ = other.slot_;
switch (other.slot_) {
case container::result_slot_t::slot_value: {
new (value_ptr()) T(std::move(*other.value_ptr()));
break;
}
case container::result_slot_t::slot_exception: {
new (exception_ptr()) exception_t(std::move(*other.exception_ptr()));
break;
}
default: {
break;
}
}
other.destroy();
other.slot_ = container::result_slot_t::slot_empty;
return *this;
}
void set_empty() {
destroy();
slot_ = container::result_slot_t::slot_empty;
}
void set_value(T value) {
destroy();
new (value_ptr()) T(std::move(value));
slot_ = container::result_slot_t::slot_value;
}
void set_exception(exception_t exception) {
destroy();
new (exception_ptr()) exception_t(std::move(exception));
slot_ = container::result_slot_t::slot_exception;
}
container::result_slot_t slot() const noexcept {
return slot_;
}
bool is_empty() const noexcept {
return slot_ == container::result_slot_t::slot_empty;
}
bool is_value() const noexcept {
return slot_ == container::result_slot_t::slot_value;
}
bool is_exception() const noexcept {
return slot_ == container::result_slot_t::slot_exception;
}
T& get_value() noexcept {
assert(is_value());
return *reinterpret_cast<T*>(&storage_);
}
T const& get_value() const noexcept {
assert(is_value());
return *reinterpret_cast<T const*>(&storage_);
}
exception_t& get_exception() noexcept {
assert(is_exception());
return *reinterpret_cast<exception_t*>(&storage_);
}
exception_t const& get_exception() const noexcept {
assert(is_exception());
return *reinterpret_cast<exception_t const*>(&storage_);
}
private:
constexpr T* value_ptr() noexcept {
return reinterpret_cast<T*>(&storage_);
}
constexpr exception_t* exception_ptr() noexcept {
return reinterpret_cast<exception_t*>(&storage_);
}
void destroy() noexcept(is_nothrow_destructible) {
switch (slot_) {
case container::result_slot_t::slot_value: {
value_ptr()->~T();
break;
}
case container::result_slot_t::slot_exception: {
exception_ptr()->~exception_t();
break;
}
default: {
break;
}
}
}
container::result_slot_t slot_{container::result_slot_t::slot_empty};
std::aligned_storage_t<
(sizeof(T) > sizeof(exception_t) ? sizeof(T) : sizeof(exception_t)),
(alignof(T) > alignof(exception_t) ? alignof(T) : alignof(exception_t))>
storage_;
};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_RESULT_VARIANT_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
namespace cti {
/// \defgroup Result Result
/// provides the \ref result class and corresponding utility functions to work
/// with the result of an asynchronous operation which can possibly yield:
/// - *no result*: If the operation didn't finish
/// - *a value*: If the operation finished successfully
/// - *an exception*: If the operation finished with an exception
/// or was cancelled.
/// \{
/// A tag which represents present void values in result.
///
/// \since 4.0.0
using void_arg_t = detail::void_arg_t;
/// A class which is convertible to any \ref result and that definitely holds no
/// value so the real result gets invalidated when this object is passed to it.
///
/// \since 4.0.0
///
struct empty_result {};
/// A class which is convertible to any \ref result and that definitely holds
/// a default constructed exception which signals the cancellation of the
/// asynchronous control flow.
///
/// \since 4.0.0
///
struct cancellation_result {};
/// A class which is convertible to any result and that holds
/// an exception which is then passed to the converted result object.
///
/// \since 4.0.0
///
class exceptional_result {
exception_t exception_;
public:
exceptional_result() = delete;
exceptional_result(exceptional_result const&) = default;
exceptional_result(exceptional_result&&) = default;
exceptional_result& operator=(exceptional_result const&) = default;
exceptional_result& operator=(exceptional_result&&) = default;
~exceptional_result() = default;
explicit exceptional_result(exception_t exception)
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
: exception_(std::move(exception)) {}
exceptional_result& operator=(exception_t exception) {
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
exception_ = std::move(exception);
return *this;
}
/// Sets an exception
void set_exception(exception_t exception) {
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
exception_ = std::move(exception);
}
/// Returns the contained exception
exception_t& get_exception() & noexcept {
return exception_;
}
/// \copydoc get_exception
exception_t const& get_exception() const& noexcept {
return exception_;
}
/// \copydoc get_exception
exception_t&& get_exception() && noexcept {
return std::move(exception_);
}
};
/// The result class can carry the three kinds of results an asynchronous
/// operation possibly can return, it's implemented in a variant like
/// data structure which is also specialized to hold arbitrary arguments.
///
/// The result can be in the following three states:
/// - *no result*: If the operation didn't finish
/// - *a value*: If the operation finished successfully
/// - *an exception*: If the operation finished with an exception
/// or was cancelled.
///
/// The interface of the result object is similar to the one proposed in
/// the `std::expected` proposal:
/// ```cpp
/// result<std::string> result = make_result("Hello World!");
/// bool(result);
/// result.is_value();
/// result.is_exception();
/// *result; // Same as result.get_value()
/// result.get_value();
/// result.get_exception();
/// ```
///
/// \since 4.0.0
///
template <typename... T>
class result {
using trait_t = detail::result_trait<T...>;
template <typename... Args>
explicit result(detail::init_result_arg_t arg, Args&&... values)
: variant_(arg, trait_t::wrap(std::forward<Args>(values)...)) {}
explicit result(detail::init_exception_arg_t arg, exception_t exception)
: variant_(arg, std::move(exception)) {}
public:
using value_t = typename trait_t::value_t;
using value_placeholder_t = typename trait_t::surrogate_t;
template <typename FirstArg, typename... Args>
explicit result(FirstArg&& first, Args&&... values)
: variant_(detail::init_result_arg_t{},
trait_t::wrap(std::forward<FirstArg>(first),
std::forward<Args>(values)...)) {}
result() = default;
result(result const&) = delete;
result(result&&) = default;
result& operator=(result const&) = delete;
result& operator=(result&&) = default;
~result() = default;
explicit result(exception_t exception)
: variant_(detail::init_exception_arg_t{}, std::move(exception)) {}
/* implicit */ result(empty_result) {}
/* implicit */ result(exceptional_result exceptional_result)
: variant_(detail::init_exception_arg_t{},
std::move(exceptional_result.get_exception())) {}
/* implicit */ result(cancellation_result)
: variant_(detail::init_exception_arg_t{}, exception_t{}) {}
result& operator=(empty_result) {
variant_.set_empty();
return *this;
}
result& operator=(value_placeholder_t value) {
variant_.set_value(std::move(value));
return *this;
}
result& operator=(exceptional_result exception) {
variant_.set_exception(std::move(exception.get_exception()));
return *this;
}
result& operator=(cancellation_result) {
variant_.set_exception({});
return *this;
}
/// Set the result to an empty state
void set_empty() {
variant_.set_empty();
}
/// Set the result to a the state which holds the corresponding value
void set_value(T... values) {
variant_.set_value(trait_t::wrap(std::move(values)...));
}
/// Set the result into a state which holds the corresponding exception
void set_exception(exception_t exception) {
variant_.set_exception(std::move(exception));
}
/// Set the result into a state which holds the cancellation token
void set_canceled() {
variant_.set_exception(exception_t{});
}
/// Returns true if the state of the result is empty
bool is_empty() const noexcept {
return variant_.is_empty();
}
/// Returns true if the state of the result holds the result
bool is_value() const noexcept {
return variant_.is_value();
}
/// Returns true if the state of the result holds a present exception
bool is_exception() const noexcept {
return variant_.is_exception();
}
/// \copydoc is_value
explicit constexpr operator bool() const noexcept {
return is_value();
}
/// Returns the values of the result, if the result doesn't hold the value
/// the behaviour is undefined but will assert in debug mode.
decltype(auto) get_value() & noexcept {
return trait_t::unwrap(variant_.get_value());
}
///\copydoc get_value
decltype(auto) get_value() const& noexcept {
return trait_t::unwrap(variant_.get_value());
}
///\copydoc get_value
decltype(auto) get_value() && noexcept {
return trait_t::unwrap(std::move(variant_.get_value()));
}
///\copydoc get_value
decltype(auto) operator*() & noexcept {
return get_value();
}
///\copydoc get_value
decltype(auto) operator*() const& noexcept {
return get_value();
}
///\copydoc get_value
decltype(auto) operator*() && noexcept {
return std::move(variant_.get_value());
}
/// Returns the exception of the result, if the result doesn't hold an
/// exception the behaviour is undefined but will assert in debug mode.
exception_t& get_exception() & noexcept {
return variant_.get_exception();
}
/// \copydoc get_exception
exception_t const& get_exception() const& noexcept {
return variant_.get_exception();
}
/// \copydoc get_exception
exception_t&& get_exception() && noexcept {
return std::move(variant_.get_exception());
}
/// Creates a present result from the given values
static result from(T... values) {
return result{detail::init_result_arg_t{}, std::move(values)...};
}
/// Creates a present result from the given exception
static result from(exception_arg_t, exception_t exception) {
return result{detail::init_exception_arg_t{}, std::move(exception)};
}
/// Creates an empty result
static result empty() {
return result{empty_result{}};
}
private:
detail::result_variant<value_placeholder_t> variant_;
};
/// Returns the value at position I of the given result
template <std::size_t I, typename... T>
decltype(auto) get(result<T...>& result) {
return detail::result_trait<T...>::template get<I>(result);
}
/// \copydoc get
template <std::size_t I, typename... T>
decltype(auto) get(result<T...> const& result) {
return detail::result_trait<T...>::template get<I>(result);
}
/// \copydoc get
template <std::size_t I, typename... T>
decltype(auto) get(result<T...>&& result) {
return detail::result_trait<T...>::template get<I>(std::move(result));
}
/// Creates a present result from the given values.
///
/// This could be used to pass the result of the next handler to the same
/// asynchronous path it came from as shown below:
/// ```cpp
/// make_ready_continuable().next([&](auto&&... args) {
/// result<> captured = make_result(std::forward<decltype(args)>(args)...);
/// return shutdown().then([captured = std::move(captured)]() mutable {
/// return std::move(captured);
/// });
/// });
/// ```
///
/// \since 4.0.0
template <typename... T,
typename Result = result<detail::traits::unrefcv_t<T>...>>
Result make_result(T&&... values) {
return Result::from(std::forward<T>(values)...);
}
/// Creates an exceptional_result from the given exception.
///
/// \copydetails make_result
///
/// \since 4.0.0
inline exceptional_result make_result(exception_arg_t, exception_t exception) {
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
return exceptional_result{std::move(exception)};
}
/// \}
} // namespace cti
namespace std {
// The GCC standard library defines tuple_size as class and struct which
// triggers a warning here.
#if defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmismatched-tags"
#endif
template <typename... Args>
struct tuple_size<cti::result<Args...>>
: std::integral_constant<size_t, sizeof...(Args)> {};
template <std::size_t I, typename... Args>
struct tuple_element<I, cti::result<Args...>>
: tuple_element<I, tuple<Args...>> {};
#if defined(__clang__)
# pragma GCC diagnostic pop
#endif
} // namespace std
#endif // CONTINUABLE_RESULT_HPP_INCLUDED
// #include <continuable/detail/connection/connection-all.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_CONNECTION_ALL_HPP_INCLUDED
#define CONTINUABLE_DETAIL_CONNECTION_ALL_HPP_INCLUDED
#include <atomic>
#include <memory>
#include <mutex>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/detail/connection/connection-aggregated.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_CONNECTION_REMAPPING_HPP_INCLUDED
#define CONTINUABLE_DETAIL_CONNECTION_REMAPPING_HPP_INCLUDED
#include <cassert>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-result.hpp>
// #include <continuable/continuable-traverse.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TRAVERSE_HPP_INCLUDED
#define CONTINUABLE_TRAVERSE_HPP_INCLUDED
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/traversal/traverse.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TRAVERSE_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TRAVERSE_HPP_INCLUDED
#include <cstddef>
#include <iterator>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/traversal/container-category.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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 <tuple>
#include <type_traits>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace traversal {
/// Deduces to a true type if the given parameter T
/// has a begin() and end() method.
// TODO Find out whether we should use std::begin and std::end instead, which
// could cause issues with plain arrays.
template <typename T, typename = void>
struct is_range : std::false_type {};
template <typename T>
struct is_range<T, traits::void_t<decltype(std::declval<T>().begin() ==
std::declval<T>().end())>>
: std::true_type {};
/// Deduces to a true type if the given parameter T
/// is accessible through std::tuple_size.
template <typename T, typename = void>
struct is_tuple_like : std::false_type {};
template <typename T>
struct is_tuple_like<T, traits::void_t<decltype(std::tuple_size<T>::value)>>
: std::true_type {};
/// A tag for dispatching based on the tuple like
/// or container properties of a type.
///
/// This type deduces to a true_type if it has any category.
template <bool IsContainer, bool IsTupleLike>
struct container_category_tag
: std::integral_constant<bool, IsContainer || IsTupleLike> {};
/// Deduces to the container_category_tag of the given type T.
template <typename T>
using container_category_of_t =
container_category_tag<is_range<T>::value, is_tuple_like<T>::value>;
} // namespace traversal
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONTAINER_CATEGORY_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace traversal {
/// Exposes useful facilities for dealing with 1:n mappings
namespace spreading {
/// \cond false
/// 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<std::decay_t<First>, 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(
traits::unpack(std::forward<C>(callable),
std::tuple_cat(undecorate(std::forward<T>(args))...))) {
return traits::unpack(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)
-> decltype(std::forward<C>(callable)(std::forward<T>(args)...)) {
return 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 = traits::disjunction<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 decltype(auto) tupelize_or_void(T&&... args) {
return voidify_empty_tuple(tupelize(std::forward<T>(args)...));
}
/// \endcond
} // 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>;
// TODO find out whether the linear compile-time instantiation is faster:
// template <typename Mapper, typename... T>
// struct is_effective_any_of_t
// : traits::disjunction<is_effective_t<Mapper, T>...> {};
// template <typename Mapper>
// struct is_effective_any_of_t<Mapper> : std::false_type {};
/// 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,
traits::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,
std::decay_t<T>, 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<decltype(
std::declval<Mapping>()(std::declval<element_of_t<Container>>()))>>;
/// 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<
std::decay_t<decltype(std::declval<M>()(std::declval<element_of_t<T>>()))>>;
/// 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>;
/// Deduces to a true type if the given parameter T supports a `reserve` method
template <typename From, typename To, typename = void>
struct is_reservable_from : std::false_type {};
template <typename From, typename To>
struct is_reservable_from<From, To,
traits::void_t<decltype(std::declval<To>().reserve(
std::declval<From>().size()))>> : std::true_type {
};
template <typename Dest, typename Source>
void reserve_if(std::true_type, Dest&& dest, Source&& source) {
// Reserve the mapped size
dest.reserve(source.size());
}
template <typename Dest, typename Source>
void reserve_if(std::false_type, Dest&&, Source&&) noexcept {
// We do nothing here, since the container doesn't support reserving
}
/// 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<std::decay_t<T>, 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 inside the destination container.
reserve_if(
is_reservable_from<std::decay_t<T>, std::decay_t<decltype(remapped)>>{},
remapped, container);
// 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) -> std::decay_t<T> {
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();
}
/// \cond false
/// 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));
}
/// \endcond
/// 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 : container_accessor_of(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) -> traits::void_t<
decltype(std::declval<M>()(std::declval<OldArgs>()))...> {
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)
-> decltype((std::declval<M>()(std::declval<OldArg>()))()) {
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(traits::unpack(
std::declval<
tuple_like_remapper<Strategy, std::decay_t<M>, std::decay_t<T>>>(),
std::forward<T>(container))) {
return traits::unpack(
tuple_like_remapper<Strategy, std::decay_t<M>, std::decay_t<T>>{
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 -> std::decay_t<T> {
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<std::decay_t<T>>>(),
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<std::decay_t<T>>>(),
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<std::decay_t<T>>{},
std::forward<T>(element));
}
public:
explicit mapping_helper(M mapper) : mapper_(std::move(mapper)) {
}
/// \copybrief try_traverse
template <typename T>
decltype(auto) init_traverse(strategy_remap_tag, T&& element) {
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>
decltype(auto) init_traverse(strategy_remap_tag strategy, First&& first,
Second&& second, 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>
decltype(auto) transform(Strategy strategy, Mapper&& mapper, T&&... pack) {
mapping_helper<Strategy, std::decay_t<Mapper>> helper(
std::forward<Mapper>(mapper));
return helper.init_traverse(strategy, std::forward<T>(pack)...);
}
} // namespace traversal
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TRAVERSE_HPP_INCLUDED
namespace cti {
/// \defgroup Traversal Traversal
/// provides functions to traverse and remap nested packs.
/// \{
/// Maps the pack with the given mapper.
///
/// This function tries to visit all plain elements which may be wrapped in:
/// - homogeneous containers (`std::vector`, `std::list`)
/// - heterogenous containers `(std::tuple`, `std::pair`, `std::array`)
/// and re-assembles the pack with the result of the mapper.
/// Mapping from one type to a different one is supported.
///
/// Elements that aren't accepted by the mapper are routed through
/// and preserved through the hierarchy.
///
/// ```cpp
/// // Maps all integers to floats
/// map_pack([](int value) {
/// return float(value);
/// },
/// 1, std::make_tuple(2, std::vector<int>{3, 4}), 5);
/// ```
///
/// \throws std::exception like objects which are thrown by an
/// invocation to the mapper.
///
/// \param mapper A callable object, which accept an arbitrary type
/// and maps it to another type or the same one.
///
/// \param pack An arbitrary variadic pack which may contain any type.
///
/// \returns The mapped element or in case the pack contains
/// multiple elements, the pack is wrapped into
/// a `std::tuple`.
///
/// \since 3.0.0
///
template <typename Mapper, typename... T>
/*keep this inline*/ inline decltype(auto) map_pack(Mapper&& mapper,
T&&... pack) {
return detail::traversal::transform(detail::traversal::strategy_remap_tag{},
std::forward<Mapper>(mapper),
std::forward<T>(pack)...);
}
/// Indicate that the result shall be spread across the parent container
/// if possible. This can be used to create a mapper function used
/// in map_pack that maps one element to an arbitrary count (1:n).
///
/// \since 3.0.0
template <typename... T>
constexpr detail::traversal::spreading::spread_box<std::decay_t<T>...>
spread_this(T&&... args) noexcept(
noexcept(std::make_tuple(std::forward<T>(args)...))) {
using type = detail::traversal::spreading::spread_box<std::decay_t<T>...>;
return type(std::make_tuple(std::forward<T>(args)...));
}
/// Traverses the pack with the given visitor.
///
/// This function works in the same way as `map_pack`,
/// however, the result of the mapper isn't preserved.
///
/// See `map_pack` for a detailed description.
///
/// \since 3.0.0
template <typename Mapper, typename... T>
void traverse_pack(Mapper&& mapper, T&&... pack) {
detail::traversal::transform(detail::traversal::strategy_traverse_tag{},
std::forward<Mapper>(mapper),
std::forward<T>(pack)...);
}
/// \}
} // namespace cti
#endif // CONTINUABLE_TRAVERSE_HPP_INCLUDED
// #include <continuable/detail/core/base.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_BASE_HPP_INCLUDED
#define CONTINUABLE_DETAIL_BASE_HPP_INCLUDED
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-result.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/result-trait.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
#include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS
namespace cti {
namespace detail {
/// The namespace `base` provides the low level API for working
/// with continuable types.
///
/// Important methods are:
/// - Creating a continuation from a callback taking functional
/// base::attorney::create_from(auto&& callback)
/// -> base::continuation<auto>
/// - Chaining a continuation together with a callback
/// base::chain_continuation(base::continuation<auto> continuation,
/// auto&& callback)
/// -> base::continuation<auto>
/// - Finally invoking the continuation chain
/// base::finalize_continuation(base::continuation<auto> continuation)
/// -> void
namespace base {
template <typename T>
struct is_continuable : std::false_type {};
template <typename Data, typename Annotation>
struct is_continuable<continuable_base<Data, Annotation>> : std::true_type {};
template <typename... Args>
struct ready_continuation {
explicit ready_continuation(result<Args...> result)
: result_(std::move(result)) {
}
ready_continuation() = delete;
~ready_continuation() = default;
ready_continuation(ready_continuation&&) = default;
ready_continuation(ready_continuation const&) = delete;
ready_continuation& operator=(ready_continuation&&) = default;
ready_continuation& operator=(ready_continuation const&) = delete;
template <typename Callback>
void operator()(Callback&& callback) {
if (result_.is_value()) {
traits::unpack(std::forward<Callback>(callback), std::move(result_));
} else if (result_.is_exception()) {
util::invoke(std::forward<Callback>(callback), exception_arg_t{},
result_.get_exception());
}
}
bool operator()(is_ready_arg_t) const noexcept {
return true;
}
result<Args...> operator()(unpack_arg_t) {
return std::move(result_);
}
private:
result<Args...> result_;
};
template <typename T>
struct ready_continuation_from_hint;
template <typename... Args>
struct ready_continuation_from_hint<identity<Args...>> {
using type = ready_continuation<Args...>;
};
template <typename T>
struct result_from_hint;
template <typename... Args>
struct result_from_hint<identity<Args...>> {
using type = result<Args...>;
};
template <typename Hint, typename Continuation>
struct proxy_continuable;
template <typename... Args, typename Continuation>
struct proxy_continuable<identity<Args...>, Continuation> : Continuation {
explicit proxy_continuable(Continuation continuation)
: Continuation(std::move(continuation)) {
}
~proxy_continuable() = default;
proxy_continuable(proxy_continuable&&) = default;
proxy_continuable(proxy_continuable const&) = delete;
proxy_continuable& operator=(proxy_continuable&&) = default;
proxy_continuable& operator=(proxy_continuable const&) = delete;
using Continuation::operator();
bool operator()(is_ready_arg_t) const noexcept {
return false;
}
result<Args...> operator()(unpack_arg_t) {
CTI_DETAIL_UNREACHABLE();
}
};
struct attorney {
/// Creates a continuable_base from the given continuation,
/// annotation and ownership.
template <typename T, typename Annotation>
static auto create_from_raw(T&& continuation, Annotation,
util::ownership ownership) {
using continuation_t = continuable_base<traits::unrefcv_t<T>, //
traits::unrefcv_t<Annotation>>;
return continuation_t{std::forward<T>(continuation), ownership};
}
/// Creates a continuable_base from the given continuation,
/// annotation and ownership.
/// This wraps the continuable to contain the is_ready and query method
/// implemented empty.
template <typename T, typename Hint>
static auto create_from(T&& continuation, Hint, util::ownership ownership) {
using hint_t = traits::unrefcv_t<Hint>;
using proxy_t = proxy_continuable<hint_t, traits::unrefcv_t<T>>;
return continuable_base<proxy_t, hint_t>{
proxy_t{std::forward<T>(continuation)}, ownership};
}
/// Returns the ownership of the given continuable_base
template <typename Continuable>
static util::ownership ownership_of(Continuable&& continuation) noexcept {
return continuation.ownership_;
}
template <typename Data, typename Annotation>
static Data&& consume(continuable_base<Data, Annotation>&& continuation) {
return std::move(continuation).consume();
}
template <typename Continuable>
static bool is_ready(Continuable&& continuation) noexcept {
return util::as_const(continuation.data_)(is_ready_arg_t{});
}
template <typename Data, typename Annotation>
static auto query(continuable_base<Data, Annotation>&& continuation) {
return std::move(continuation).consume()(unpack_arg_t{});
}
};
} // namespace base
template <typename Annotation>
struct annotation_trait;
/// Specialization for a present signature hint
template <typename... Args>
struct annotation_trait<identity<Args...>> {
template <typename Continuable>
static Continuable&& finish(Continuable&& continuable) {
return std::forward<Continuable>(continuable);
}
template <typename Continuable>
static bool is_ready(Continuable const& continuable) noexcept {
return base::attorney::is_ready(continuable);
}
};
namespace base {
/// Returns the signature hint of the given continuable
template <typename Data, typename... Args>
constexpr identity<Args...>
annotation_of(identity<continuable_base<Data, //
identity<Args...>>>) {
return {};
}
/// Invokes a continuation object in a reference correct way
template <typename Data, typename Annotation, typename Callback>
void invoke_continuation(continuable_base<Data, Annotation>&& continuation,
Callback&& callback) noexcept {
util::invoke(attorney::consume(std::move(continuation).finish()),
std::forward<Callback>(callback));
}
// Returns the invoker of a callback, the next callback
// and the arguments of the previous continuation.
//
// The return type of the invokerOf function matches a callable of:
// void(auto&& callback, auto&& next_callback, auto&&... args)
//
// The invoker decorates the result type in the following way
// - void -> next_callback()
// - ? -> next_callback(?)
// - std::pair<?, ?> -> next_callback(?, ?)
// - std::tuple<?...> -> next_callback(?...)
//
// When the result is a continuation itself pass the callback to it
// - continuation<?...> -> result(next_callback);
namespace decoration {
/// Helper class wrapping the underlaying unwrapping lambda
/// in order to extend it with a hint method.
template <typename T, typename Hint>
class invoker : public T {
public:
constexpr explicit invoker(T invoke) : T(std::move(invoke)) {
}
using T::operator();
/// Returns the underlaying signature hint
static constexpr Hint hint() noexcept {
return {};
}
};
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
#define CONTINUABLE_BLOCK_TRY_BEGIN try {
#define CONTINUABLE_BLOCK_TRY_END \
} \
catch (...) { \
std::forward<decltype(next_callback)>(next_callback)( \
exception_arg_t{}, std::current_exception()); \
}
#else // CONTINUABLE_HAS_EXCEPTIONS
#define CONTINUABLE_BLOCK_TRY_BEGIN {
#define CONTINUABLE_BLOCK_TRY_END }
#endif // CONTINUABLE_HAS_EXCEPTIONS
/// Invokes the callback partially, keeps the exception_arg_t such that
/// we don't jump accidentally from the exception path to the result path.
template <typename T, typename... Args>
constexpr auto invoke_callback(T&& callable, exception_arg_t exception_arg,
Args&&... args) {
return util::partial_invoke(std::integral_constant<std::size_t, 01>{},
std::forward<T>(callable), exception_arg,
std::forward<Args>(args)...);
}
template <typename T, typename... Args>
constexpr auto invoke_callback(T&& callable, Args&&... args) {
return util::partial_invoke(std::integral_constant<std::size_t, 0U>{},
std::forward<T>(callable),
std::forward<Args>(args)...);
}
/// Invokes the given callable object with the given arguments while
/// marking the operation as non exceptional.
template <typename T, typename... Args>
constexpr void invoke_no_except(T&& callable, Args&&... args) noexcept {
std::forward<T>(callable)(std::forward<Args>(args)...);
}
template <typename... Args, typename T>
void invoke_void_no_except(identity<exception_arg_t, Args...>,
T&& /*callable*/) noexcept {
// Don't invoke the next failure handler when being in an exception handler
}
template <typename... Args, typename T>
void invoke_void_no_except(identity<Args...>, T&& callable) noexcept {
std::forward<T>(callable)();
}
template <typename T, typename... Args>
constexpr auto make_invoker(T&& invoke, identity<Args...>) {
return invoker<std::decay_t<T>, identity<Args...>>(std::forward<T>(invoke));
}
/// - continuable<?...> -> result(next_callback);
template <typename Data, typename Annotation>
constexpr auto invoker_of(identity<continuable_base<Data, Annotation>>) {
/// Get the hint of the unwrapped returned continuable
using Type =
decltype(std::declval<continuable_base<Data, Annotation>>().finish());
auto constexpr const hint = base::annotation_of(identify<Type>{});
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
auto continuation_ =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_continuation(
std::move(continuation_),
std::forward<decltype(next_callback)>(next_callback));
CONTINUABLE_BLOCK_TRY_END
},
hint);
}
/// - ? -> next_callback(?)
template <typename T>
constexpr auto invoker_of(identity<T>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
auto result =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
std::move(result));
CONTINUABLE_BLOCK_TRY_END
},
identify<T>{});
}
/// - plain_tag<?> -> next_callback(?)
template <typename T>
constexpr auto invoker_of(identity<types::plain_tag<T>>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
types::plain_tag<T> result =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
std::move(result).consume());
CONTINUABLE_BLOCK_TRY_END
},
identify<T>{});
}
/// - void -> next_callback()
inline auto invoker_of(identity<void>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
invoke_void_no_except(
identity<traits::unrefcv_t<decltype(args)>...>{},
std::forward<decltype(next_callback)>(next_callback));
CONTINUABLE_BLOCK_TRY_END
},
identity<>{});
}
/// - empty_result -> <abort>
inline auto invoker_of(identity<empty_result>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
(void)next_callback;
CONTINUABLE_BLOCK_TRY_BEGIN
empty_result result =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Don't invoke anything here since returning an empty result
// aborts the asynchronous chain effectively.
(void)result;
CONTINUABLE_BLOCK_TRY_END
},
identity<>{});
}
/// - cancellation_result -> <cancel>
inline auto invoker_of(identity<cancellation_result>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
(void)next_callback;
CONTINUABLE_BLOCK_TRY_BEGIN
cancellation_result result = invoke_callback(
std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Forward the cancellation to the next available exception handler
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
exception_arg_t{}, exception_t{});
(void)result;
CONTINUABLE_BLOCK_TRY_END
},
identity<>{});
}
/// - exceptional_result -> <throw>
inline auto invoker_of(identity<exceptional_result>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
util::unused(callback, next_callback, args...);
CONTINUABLE_BLOCK_TRY_BEGIN
exceptional_result result =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Forward the exception to the next available exception handler
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
exception_arg_t{},
std::move(result).get_exception());
CONTINUABLE_BLOCK_TRY_END
},
identity<>{});
}
/// - result<?...> -> next_callback(?...)
template <typename... Args>
auto invoker_of(identity<result<Args...>>) {
return make_invoker(
[](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
result<Args...> result =
invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
if (result.is_value()) {
// Workaround for MSVC not capturing the reference
// correctly inside the lambda.
using Next = decltype(next_callback);
traits::unpack(
[&](auto&&... values) {
invoke_no_except(std::forward<Next>(next_callback),
std::forward<decltype(values)>(values)...);
},
std::move(result));
} else if (result.is_exception()) {
// Forward the exception to the next available handler
invoke_no_except(std::forward<decltype(next_callback)>(
next_callback),
exception_arg_t{},
std::move(result).get_exception());
} else {
// Aborts the continuation of the chain
assert(result.is_empty());
}
// Otherwise the result is empty and we are cancelling our
// asynchronous chain.
CONTINUABLE_BLOCK_TRY_END
},
identity<Args...>{});
}
/// Returns a sequenced invoker which is able to invoke
/// objects where std::get is applicable.
inline auto sequenced_unpack_invoker() {
return [](auto&& callback, auto&& next_callback, auto&&... args) {
CONTINUABLE_BLOCK_TRY_BEGIN
auto result = invoke_callback(std::forward<decltype(callback)>(callback),
std::forward<decltype(args)>(args)...);
// Workaround for MSVC not capturing the reference
// correctly inside the lambda.
using Next = decltype(next_callback);
traits::unpack(
[&](auto&&... values) {
invoke_no_except(std::forward<Next>(next_callback),
std::forward<decltype(values)>(values)...);
},
std::move(result));
CONTINUABLE_BLOCK_TRY_END
};
} // namespace decoration
// - std::pair<?, ?> -> next_callback(?, ?)
template <typename First, typename Second>
constexpr auto invoker_of(identity<std::pair<First, Second>>) {
return make_invoker(sequenced_unpack_invoker(), identity<First, Second>{});
}
// - std::tuple<?...> -> next_callback(?...)
template <typename... Args>
constexpr auto invoker_of(identity<std::tuple<Args...>>) {
return make_invoker(sequenced_unpack_invoker(), identity<Args...>{});
}
#undef CONTINUABLE_BLOCK_TRY_BEGIN
#undef CONTINUABLE_BLOCK_TRY_END
} // namespace decoration
/// Invoke the callback immediately
template <typename Invoker, typename Callback, typename NextCallback,
typename... Args>
void on_executor(types::this_thread_executor_tag, Invoker&& invoker,
Callback&& callback, NextCallback&& next_callback,
Args&&... args) {
// Invoke the callback with the decorated invoker immediately
std::forward<Invoker>(invoker)(std::forward<Callback>(callback),
std::forward<NextCallback>(next_callback),
std::forward<Args>(args)...);
}
template <typename Invoker, typename Callback, typename NextCallback,
typename... Args>
class work_proxy {
public:
work_proxy(Invoker&& invoker, Callback&& callback,
NextCallback&& next_callback, std::tuple<Args...>&& args)
: invoker_(std::move(invoker)), callback_(std::move(callback)),
next_callback_(std::move(next_callback)), args_(std::move(args)) {
}
~work_proxy() = default;
work_proxy(work_proxy&&) = default;
work_proxy(work_proxy const&) = delete;
work_proxy& operator=(work_proxy&&) = default;
work_proxy& operator=(work_proxy const&) = delete;
void set_value() noexcept {
traits::unpack(
[&](auto&&... captured_args) {
// Just use the packed dispatch method which dispatches the work_proxy
// on the current thread.
std::move(invoker_)(
std::move(callback_), std::move(next_callback_),
std::forward<decltype(captured_args)>(captured_args)...);
},
std::move(args_));
}
void operator()() && noexcept {
std::move(*this).set_value();
}
void operator()(exception_arg_t, exception_t exception) && noexcept {
std::move(next_callback_)(exception_arg_t{}, std::move(exception));
}
void set_exception(exception_t exception) noexcept {
std::move(next_callback_)(exception_arg_t{}, std::move(exception));
}
void set_canceled() noexcept {
std::move(next_callback_)(exception_arg_t{}, exception_t{});
}
explicit operator bool() const noexcept {
return true;
}
private:
Invoker invoker_;
Callback callback_;
NextCallback next_callback_;
std::tuple<Args...> args_;
};
/// Invoke the callback through the given executor
template <typename Executor, typename Invoker, typename Callback,
typename NextCallback, typename... Args>
void on_executor(Executor&& executor, Invoker&& invoker, Callback&& callback,
NextCallback&& next_callback, Args&&... args) {
// Create a work_proxy object which when invoked calls the callback with the
// the returned arguments and pass the work_proxy callable object to the
// executor
using work_proxy_t =
work_proxy<Invoker, std::decay_t<Callback>, std::decay_t<NextCallback>,
std::decay_t<Args>...>;
std::forward<Executor>(executor)(work_proxy_t(
std::forward<Invoker>(invoker), std::forward<Callback>(callback),
std::forward<NextCallback>(next_callback),
std::make_tuple(std::forward<Args>(args)...)));
}
/// Tells whether we potentially move the chain upwards and handle the result
enum class handle_results {
no, //< The result is forwarded to the next callable
yes //< The result is handled by the current callable
};
// Silences a doxygen bug, it tries to map forward to std::forward
/// \cond false
/// Tells whether we handle the error through the callback
enum class handle_errors {
no, //< The error is forwarded to the next callable
forward //< The error is forwarded to the callable while keeping its tag
};
/// \endcond
namespace callbacks {
namespace proto {
template <handle_results HandleResults, typename Base, typename Hint>
struct result_handler_base;
template <typename Base, typename... Args>
struct result_handler_base<handle_results::no, Base, identity<Args...>> {
void operator()(Args... args) && {
// Forward the arguments to the next callback
std::move(static_cast<Base*>(this)->next_callback_)(std::move(args)...);
}
};
template <typename Base, typename... Args>
struct result_handler_base<handle_results::yes, Base, identity<Args...>> {
/// The operator which is called when the result was provided
void operator()(Args... args) && {
// In order to retrieve the correct decorator we must know what the
// result type is.
constexpr auto result = identify<decltype(decoration::invoke_callback(
std::move(static_cast<Base*>(this)->callback_), std::move(args)...))>{};
// Pick the correct invoker that handles decorating of the result
auto invoker = decoration::invoker_of(result);
// Invoke the callback
on_executor(std::move(static_cast<Base*>(this)->executor_),
std::move(invoker),
std::move(static_cast<Base*>(this)->callback_),
std::move(static_cast<Base*>(this)->next_callback_),
std::move(args)...);
}
};
template <handle_errors HandleErrors, typename Base>
struct error_handler_base;
template <typename Base>
struct error_handler_base<handle_errors::no, Base> {
/// The operator which is called when an error occurred
void operator()(exception_arg_t tag, exception_t exception) && {
// Forward the error to the next callback
std::move(static_cast<Base*>(this)->next_callback_)(tag,
std::move(exception));
}
};
template <typename Base>
struct error_handler_base<handle_errors::forward, Base> {
/// The operator which is called when an error occurred
void operator()(exception_arg_t, exception_t exception) && {
constexpr auto result = identify<decltype(decoration::invoke_callback(
std::move(static_cast<Base*>(this)->callback_), exception_arg_t{},
std::move(exception)))>{};
auto invoker = decoration::invoker_of(result);
// Invoke the error handler
on_executor(std::move(static_cast<Base*>(this)->executor_),
std::move(invoker),
std::move(static_cast<Base*>(this)->callback_),
std::move(static_cast<Base*>(this)->next_callback_),
exception_arg_t{}, std::move(exception));
}
};
} // namespace proto
template <typename Hint, handle_results HandleResults,
handle_errors HandleErrors, typename Callback, typename Executor,
typename NextCallback>
struct callback_base;
template <typename... Args, handle_results HandleResults,
handle_errors HandleErrors, typename Callback, typename Executor,
typename NextCallback>
struct callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
Executor, NextCallback>
: proto::result_handler_base<
HandleResults,
callback_base<identity<Args...>, HandleResults, HandleErrors,
Callback, Executor, NextCallback>,
identity<Args...>>,
proto::error_handler_base<
HandleErrors,
callback_base<identity<Args...>, HandleResults, HandleErrors,
Callback, Executor, NextCallback>>,
util::non_copyable {
Callback callback_;
Executor executor_;
NextCallback next_callback_;
explicit callback_base(Callback callback, Executor executor,
NextCallback next_callback)
: callback_(std::move(callback)), executor_(std::move(executor)),
next_callback_(std::move(next_callback)) {
}
/// Pull the result handling operator() in
using proto::result_handler_base<
HandleResults,
callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
Executor, NextCallback>,
identity<Args...>>::operator();
/// Pull the error handling operator() in
using proto::error_handler_base<
HandleErrors,
callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
Executor, NextCallback>>::operator();
/// Resolves the continuation with the given values
void set_value(Args... args) noexcept {
std::move (*this)(std::move(args)...);
}
/// Resolves the continuation with the given exception.
void set_exception(exception_t exception) noexcept {
std::move (*this)(exception_arg_t{}, std::move(exception));
}
void set_canceled() noexcept {
std::move (*this)(exception_arg_t{}, exception_t{});
}
/// Returns true because this is a present continuation
explicit operator bool() const noexcept {
return true;
}
};
template <typename Hint, handle_results HandleResults,
handle_errors HandleErrors, typename Callback, typename Executor,
typename NextCallback>
auto make_callback(Callback&& callback, Executor&& executor,
NextCallback&& next_callback) {
return callback_base<Hint, HandleResults, HandleErrors,
std::decay_t<Callback>, std::decay_t<Executor>,
std::decay_t<NextCallback>>{
std::forward<Callback>(callback), std::forward<Executor>(executor),
std::forward<NextCallback>(next_callback)};
}
/// Represents the last callback in the asynchronous continuation chain
template <typename... Args>
struct final_callback : util::non_copyable {
void operator()(Args... /*args*/) && {
}
void operator()(exception_arg_t, exception_t exception) && {
// Only handle the exception when it is present, otherwise handle it as
// a cancellation of the control flow.
// This behaviour is intentionally correct for
// - `std::exception_ptr`
// - `std::error_code`
// - `std::error_condition`
// which allow to be default constructed and then return false
// by their corresponding `operator bool()`.
if (bool(exception)) {
#ifndef CONTINUABLE_WITH_UNHANDLED_EXCEPTIONS
// There were unhandled errors inside the asynchronous call chain!
// Define `CONTINUABLE_WITH_UNHANDLED_EXCEPTIONS` in order
// to ignore unhandled errors!"
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
try {
std::rethrow_exception(exception);
} catch (std::exception const& unhandled) {
char const* const what = unhandled.what();
(void)what;
CTI_DETAIL_TRAP();
} catch (...) {
CTI_DETAIL_TRAP();
}
#else
CTI_DETAIL_TRAP();
#endif
#endif // CONTINUABLE_WITH_UNHANDLED_EXCEPTIONS
}
}
void set_value(Args... args) noexcept {
std::move (*this)(std::forward<Args>(args)...);
}
void set_exception(exception_t exception) noexcept {
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
std::move (*this)(exception_arg_t{}, std::move(exception));
}
void set_canceled() noexcept {
std::move (*this)(exception_arg_t{}, exception_t{});
}
explicit operator bool() const noexcept {
return true;
}
};
} // namespace callbacks
/// Returns the next hint when the callback is invoked with the given hint
template <typename T, typename... Args>
constexpr auto
next_hint_of(std::integral_constant<handle_results, handle_results::yes>,
identity<T> /*callback*/, identity<Args...> /*current*/) {
// Partial Invoke the given callback
using Result = decltype(
decoration::invoke_callback(std::declval<T>(), std::declval<Args>()...));
// Return the hint of thr given invoker
return decltype(decoration::invoker_of(identify<Result>{}).hint()){};
}
/// Don't progress the hint when we don't continue
template <typename T, typename... Args>
constexpr auto
next_hint_of(std::integral_constant<handle_results, handle_results::no>,
identity<T> /*callback*/, identity<Args...> current) {
return current;
}
namespace detail {
template <typename Callable>
struct exception_stripper_proxy {
Callable callable_;
template <typename... Args>
auto operator()(exception_arg_t, Args&&... args)
-> decltype(util::invoke(std::declval<Callable>(), //
std::declval<Args>()...)) {
return util::invoke(std::move(callable_), //
std::forward<decltype(args)>(args)...);
}
};
} // namespace detail
/// Removes the exception_arg_t from the arguments passed to the given callable
template <typename Callable>
auto strip_exception_arg(Callable&& callable) {
using proxy = detail::exception_stripper_proxy<traits::unrefcv_t<Callable>>;
return proxy{std::forward<Callable>(callable)};
}
template <typename Hint, typename NextHint, handle_results HandleResults,
handle_errors HandleErrors, typename Continuation, typename Callback,
typename Executor>
struct chained_continuation;
template <typename... Args, typename... NextArgs, handle_results HandleResults,
handle_errors HandleErrors, typename Continuation, typename Callback,
typename Executor>
struct chained_continuation<identity<Args...>, identity<NextArgs...>,
HandleResults, HandleErrors, Continuation, Callback,
Executor> {
Continuation continuation_;
Callback callback_;
Executor executor_;
explicit chained_continuation(Continuation continuation, Callback callback,
Executor executor)
: continuation_(std::move(continuation)), callback_(std::move(callback)),
executor_(std::move(executor)) {
}
chained_continuation() = delete;
~chained_continuation() = default;
chained_continuation(chained_continuation const&) = delete;
chained_continuation(chained_continuation&&) = default;
chained_continuation& operator=(chained_continuation const&) = delete;
chained_continuation& operator=(chained_continuation&&) = default;
template <typename NextCallback>
void operator()(NextCallback&& next_callback) {
// Invokes a continuation with a given callback.
// Passes the next callback to the resulting continuable or
// invokes the next callback directly if possible.
//
// For example given:
// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
// - Callback: [](std::string) { }
// - NextCallback: []() { }
auto proxy = callbacks::make_callback<identity<Args...>, HandleResults,
HandleErrors>(
std::move(callback_), std::move(executor_),
std::forward<decltype(next_callback)>(next_callback));
// Check whether the continuation is ready
bool const is_ready = util::as_const(continuation_)(is_ready_arg_t{});
if (is_ready) {
// Invoke the proxy callback directly with the result to
// avoid a potential type erasure.
auto result = std::move(continuation_)(unpack_arg_t{});
if (result.is_value()) {
traits::unpack(std::move(proxy), std::move(result));
} else if (result.is_exception()) {
util::invoke(std::move(proxy), exception_arg_t{},
std::move(result.get_exception()));
} else {
assert(result.is_empty());
}
} else {
// Invoke the continuation with a proxy callback.
// The proxy callback is responsible for passing
// the result to the callback as well as decorating it.
util::invoke(std::move(continuation_), std::move(proxy));
}
}
bool operator()(is_ready_arg_t) const noexcept {
return false;
}
result<NextArgs...> operator()(unpack_arg_t) {
CTI_DETAIL_UNREACHABLE();
}
};
// Specialization to unpack ready continuables directly
template <typename... Args, typename... NextArgs, handle_results HandleResults,
handle_errors HandleErrors, typename Callback, typename Executor>
struct chained_continuation<identity<Args...>, identity<NextArgs...>,
HandleResults, HandleErrors,
ready_continuation<Args...>, Callback, Executor> {
ready_continuation<Args...> continuation_;
Callback callback_;
Executor executor_;
explicit chained_continuation(ready_continuation<Args...> continuation,
Callback callback, Executor executor)
: continuation_(std::move(continuation)), callback_(std::move(callback)),
executor_(std::move(executor)) {
}
chained_continuation() = delete;
~chained_continuation() = default;
chained_continuation(chained_continuation const&) = delete;
chained_continuation(chained_continuation&&) = default;
chained_continuation& operator=(chained_continuation const&) = delete;
chained_continuation& operator=(chained_continuation&&) = default;
template <typename NextCallback>
void operator()(NextCallback&& next_callback) {
auto proxy = callbacks::make_callback<identity<Args...>, HandleResults,
HandleErrors>(
std::move(callback_), std::move(executor_),
std::forward<decltype(next_callback)>(next_callback));
// Extract the result out of the ready continuable
auto result = std::move(continuation_)(unpack_arg_t{});
if (result.is_value()) {
traits::unpack(std::move(proxy), std::move(result));
} else if (result.is_exception()) {
util::invoke(std::move(proxy), exception_arg_t{},
std::move(result.get_exception()));
} else {
assert(result.is_empty());
}
}
bool operator()(is_ready_arg_t) const noexcept {
return false;
}
result<NextArgs...> operator()(unpack_arg_t) {
CTI_DETAIL_UNREACHABLE();
}
};
/// Chains a callback together with a continuation and returns a
/// continuation:
///
/// For example given:
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
/// - Callback: [](std::string) { }
///
/// This function returns a function accepting the next callback in the
/// chain:
/// - Result: continuation<[](auto&& callback) { /*...*/ }>
///
template <handle_results HandleResults, handle_errors HandleErrors,
typename Continuation, typename Callback, typename Executor>
auto chain_continuation(Continuation&& continuation, Callback&& callback,
Executor&& executor) {
static_assert(is_continuable<std::decay_t<Continuation>>{},
"Expected a continuation!");
using Hint = decltype(base::annotation_of(identify<Continuation>()));
constexpr auto next_hint =
next_hint_of(std::integral_constant<handle_results, HandleResults>{},
identify<decltype(callback)>{}, Hint{});
auto ownership = attorney::ownership_of(continuation);
auto data =
attorney::consume(std::forward<Continuation>(continuation).finish());
using continuation_t = chained_continuation<
Hint, traits::unrefcv_t<decltype(next_hint)>, HandleResults, HandleErrors,
decltype(data), traits::unrefcv_t<Callback>, traits::unrefcv_t<Executor>>;
return attorney::create_from_raw(
continuation_t(std::move(data), std::forward<Callback>(callback),
std::forward<Executor>(executor)),
next_hint, ownership);
}
/// Final invokes the given continuation chain:
///
/// For example given:
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
template <typename Data, typename... Args>
void finalize_continuation(
continuable_base<Data, identity<Args...>>&& continuation) noexcept {
#ifdef CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK
invoke_continuation(std::move(continuation),
CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK<Args...>{});
#else // CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK
invoke_continuation(std::move(continuation),
callbacks::final_callback<Args...>{});
#endif // CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK
}
/// Deduces to a true type if the given callable data can be wrapped
/// with the given hint and converted to the given Data.
template <typename Data, typename Annotation, typename Continuation>
struct can_accept_continuation : std::false_type {};
template <typename Data, typename... Args, typename Continuation>
struct can_accept_continuation<Data, identity<Args...>, Continuation>
: traits::conjunction<
traits::is_invocable<Continuation,
callbacks::final_callback<Args...>>,
std::is_convertible<
proxy_continuable<identity<Args...>, Continuation>, Data>> {};
/// Workaround for GCC bug:
/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095
template <typename T>
class supplier_callback {
T data_;
public:
explicit supplier_callback(T data) : data_(std::move(data)) {
}
template <typename... Args>
auto operator()(Args...) {
return std::move(data_);
}
};
/// Returns a continuable into a callable object returning the continuable
template <typename Continuation>
auto wrap_continuation(Continuation&& continuation) {
continuation.freeze();
return supplier_callback<std::decay_t<Continuation>>(
std::forward<Continuation>(continuation));
}
/// Callback which converts its input to the given set of arguments
template <typename... Args>
struct convert_to {
std::tuple<Args...> operator()(Args... args) {
return std::make_tuple(std::move(args)...);
}
};
template <typename T>
struct convert_to<T> {
T operator()(T arg) noexcept {
return std::move(arg);
}
};
template <>
struct convert_to<> {
void operator()() noexcept {
}
};
} // namespace base
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_BASE_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace connection {
/// This namespace provides utilities for performing compound
/// connections between deeply nested continuables and values.
///
/// We create the result pack from the provides values and
/// the async values if those are default constructible,
/// otherwise use a lazy initialization wrapper and unwrap
/// the whole pack when the connection is finished.
/// - value -> value
/// - single async value -> single value
/// - multiple async value -> tuple of async values.
namespace aggregated {
/// Guards a type to be default constructible,
/// and wraps it into an optional type if it isn't default constructible.
template <typename T>
using lazy_value_t = std::conditional_t<std::is_default_constructible<T>::value,
T, result<T>>;
template <typename T>
decltype(auto) unpack_lazy(std::true_type /*is_default_constructible*/,
T&& value) {
return std::forward<T>(value);
}
template <typename T>
T&& unpack_lazy(std::false_type /*is_default_constructible*/,
result<T>&& value) {
assert(value.is_value() &&
"The connection was finalized before all values were present!");
return std::move(value).get_value();
}
template <typename Continuable>
class continuable_box;
template <typename Data>
class continuable_box<continuable_base<Data, identity<>>> {
continuable_base<Data, identity<>> continuable_;
public:
explicit continuable_box(continuable_base<Data, identity<>>&& continuable)
: continuable_(std::move(continuable)) {}
auto const& peek() const {
return continuable_;
}
auto&& fetch() {
return std::move(continuable_);
}
void assign() {}
auto unbox() && {
return spread_this();
}
};
template <typename Data, typename First>
class continuable_box<continuable_base<Data, identity<First>>> {
continuable_base<Data, identity<First>> continuable_;
lazy_value_t<First> first_;
public:
explicit continuable_box(
continuable_base<Data, identity<First>>&& continuable)
: continuable_(std::move(continuable)) {}
auto const& peek() const {
return continuable_;
}
auto&& fetch() {
return std::move(continuable_);
}
void assign(First first) {
first_ = std::move(first);
}
auto unbox() && {
return unpack_lazy(std::is_default_constructible<First>{},
std::move(first_));
}
};
template <typename Data, typename First, typename Second, typename... Rest>
class continuable_box<
continuable_base<Data, identity<First, Second, Rest...>>> {
continuable_base<Data, identity<First, Second, Rest...>> continuable_;
lazy_value_t<std::tuple<First, Second, Rest...>> args_;
public:
explicit continuable_box(
continuable_base<Data, identity<First, Second, Rest...>>&& continuable)
: continuable_(std::move(continuable)) {}
auto const& peek() const {
return continuable_;
}
auto&& fetch() {
return std::move(continuable_);
}
void assign(First first, Second second, Rest... rest) {
args_ = std::make_tuple(std::move(first), std::move(second),
std::move(rest)...);
}
auto unbox() && {
return traits::unpack(
[](auto&&... args) {
return spread_this(std::forward<decltype(args)>(args)...);
},
unpack_lazy(
std::is_default_constructible<std::tuple<First, Second, Rest...>>{},
std::move(args_)));
}
};
template <typename T>
struct is_continuable_box : std::false_type {};
template <typename Continuable>
struct is_continuable_box<continuable_box<Continuable>> : std::true_type {};
namespace detail {
/// Maps a deeply nested pack of continuables to a continuable_box
struct continuable_box_packer {
template <
typename T,
std::enable_if_t<base::is_continuable<std::decay_t<T>>::value>* = nullptr>
auto operator()(T&& continuable) {
return continuable_box<std::decay_t<T>>{std::forward<T>(continuable)};
}
};
/// Maps a deeply nested pack of continuable_boxes to its result
struct continuable_box_unpacker {
template <
typename T,
std::enable_if_t<is_continuable_box<std::decay_t<T>>::value>* = nullptr>
auto operator()(T&& box) {
return std::forward<T>(box).unbox();
}
};
} // namespace detail
/// Returns the boxed pack of the given deeply nested pack.
/// This transforms all continuables into a continuable_box which is
/// capable of caching the result from the corresponding continuable.
template <typename... Args>
constexpr auto box_continuables(Args&&... args) {
return cti::map_pack(detail::continuable_box_packer{},
std::forward<Args>(args)...);
}
/// Returns the unboxed pack of the given deeply nested boxed pack.
/// This transforms all continuable_boxes into its result.
template <typename... Args>
constexpr auto unbox_continuables(Args&&... args) {
return cti::map_pack(detail::continuable_box_unpacker{},
std::forward<Args>(args)...);
}
namespace detail {
template <typename Callback, typename Data>
constexpr auto finalize_impl(identity<void>, Callback&& callback, Data&&) {
return std::forward<Callback>(callback)();
}
template <typename... Args, typename Callback, typename Data>
constexpr auto finalize_impl(identity<std::tuple<Args...>>, Callback&& callback,
Data&& data) {
// Call the final callback with the cleaned result
return traits::unpack(std::forward<Callback>(callback),
unbox_continuables(std::forward<Data>(data)));
}
struct hint_mapper {
template <typename... T>
constexpr auto operator()(T...) -> identity<T...> {
return {};
}
};
} // namespace detail
template <typename Callback, typename Data>
constexpr auto finalize_data(Callback&& callback, Data&& data) {
using result_t = decltype(unbox_continuables(std::forward<Data>(data)));
// Guard the final result against void
return detail::finalize_impl(identity<std::decay_t<result_t>>{},
std::forward<Callback>(callback),
std::forward<Data>(data));
}
template <typename Data>
constexpr auto hint_of_data() {
return decltype(finalize_data(detail::hint_mapper{}, std::declval<Data>())){};
}
} // namespace aggregated
} // namespace connection
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONNECTION_REMAPPING_HPP_INCLUDED
// #include <continuable/detail/connection/connection.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_CONNECTION_HPP_INCLUDED
#define CONTINUABLE_DETAIL_CONNECTION_HPP_INCLUDED
#include <cassert>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-traverse.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
namespace cti {
namespace detail {
/// The namespace `connection` offers methods to chain continuations together
/// with `all`, `any` or `seq` logic.
namespace connection {
template <typename T>
struct is_connection_strategy // ...
: std::false_type {};
/// Adds the given continuation tuple to the left connection
template <typename... LeftArgs, typename... RightArgs>
auto chain_connection(std::tuple<LeftArgs...> leftPack,
std::tuple<RightArgs...> rightPack) {
return std::tuple_cat(std::move(leftPack), std::move(rightPack));
}
/// Normalizes a continuation to a tuple holding an arbitrary count of
/// continuations matching the given strategy.
///
/// Basically we can encounter 3 cases:
/// - The continuable isn't in any strategy:
/// -> make a tuple containing the continuable as only element
template <
typename Strategy, typename Data, typename Annotation,
std::enable_if_t<!is_connection_strategy<Annotation>::value>* = nullptr>
auto normalize(Strategy /*strategy*/,
continuable_base<Data, Annotation>&& continuation) {
// If the continuation isn't a strategy initialize the strategy
return std::make_tuple(std::move(continuation));
}
/// - The continuable is in a different strategy then the current one:
/// -> materialize it
template <
typename Strategy, typename Data, typename Annotation,
std::enable_if_t<is_connection_strategy<Annotation>::value>* = nullptr>
auto normalize(Strategy /*strategy*/,
continuable_base<Data, Annotation>&& continuation) {
// If the right continuation is a different strategy materialize it
// in order to keep the precedence in cases where: `c1 && (c2 || c3)`.
return std::make_tuple(std::move(continuation).finish());
}
/// - The continuable is inside the current strategy state:
/// -> return the data of the tuple
template <typename Strategy, typename Data>
auto normalize(Strategy /*strategy*/,
continuable_base<Data, Strategy>&& continuation) {
// If we are in the given strategy we can just use the data of the continuable
return base::attorney::consume(std::move(continuation));
}
/// Entry function for connecting two continuables with a given strategy.
template <typename Strategy, typename LData, typename LAnnotation,
typename RData, typename RAnnotation>
auto connect(Strategy strategy, continuable_base<LData, LAnnotation>&& left,
continuable_base<RData, RAnnotation>&& right) {
auto ownership_ =
base::attorney::ownership_of(left) | base::attorney::ownership_of(right);
left.freeze();
right.freeze();
// Make the new data which consists of a tuple containing
// all connected continuables.
auto data = chain_connection(normalize(strategy, std::move(left)),
normalize(strategy, std::move(right)));
// Return a new continuable containing the tuple and holding
// the current strategy as annotation.
return base::attorney::create_from_raw(std::move(data), strategy, ownership_);
}
/// All strategies should specialize this class in order to provide:
/// - A finalize static method that creates the callable object which
/// is invoked with the callback to call when the connection is finished.
/// - A static method hint that returns the new signature hint.
template <typename Strategy>
struct connection_finalizer;
template <typename Strategy>
struct connection_annotation_trait {
/// Finalizes the connection logic of a given connection
template <typename Continuable>
static auto finish(Continuable&& continuable) {
using finalizer = connection_finalizer<Strategy>;
util::ownership ownership = base::attorney::ownership_of(continuable);
auto connection =
base::attorney::consume(std::forward<Continuable>(continuable));
// Return a new continuable which
return finalizer::finalize(std::move(connection), std::move(ownership));
}
template <typename Continuable>
static bool is_ready(Continuable const& /*continuable*/) noexcept {
return false;
}
};
class prepare_continuables {
util::ownership& ownership_;
public:
explicit constexpr prepare_continuables(util::ownership& ownership)
: ownership_(ownership) {
}
template <typename Continuable,
std::enable_if_t<base::is_continuable<
std::decay_t<Continuable>>::value>* = nullptr>
auto operator()(Continuable&& continuable) noexcept {
util::ownership current = base::attorney::ownership_of(continuable);
assert(current.is_acquired() &&
"Only valid continuables should be passed!");
// Propagate a frozen state to the new continuable
if (!ownership_.is_frozen() && current.is_frozen()) {
ownership_.freeze();
}
// Freeze the continuable since it is stored for later usage
continuable.freeze();
// Materialize every continuable
// TODO Actually we would just need to consume the data here
return std::forward<Continuable>(continuable).finish();
}
};
template <typename Strategy, typename... Args>
auto apply_connection(Strategy, Args&&... args) {
using finalizer = connection_finalizer<Strategy>;
// Freeze every continuable inside the given arguments,
// and freeze the ownership if one of the continuables
// is frozen already.
// Additionally test whether every continuable is acquired.
// Also materialize every continuable.
util::ownership ownership;
auto connection = map_pack(prepare_continuables{ownership},
std::make_tuple(std::forward<Args>(args)...));
return finalizer::finalize(std::move(connection), std::move(ownership));
}
} // namespace connection
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONNECTION_HPP_INCLUDED
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace connection {
namespace all {
/// Caches the partial results and invokes the callback when all results
/// are arrived. This class is thread safe.
template <typename Callback, typename Result>
class result_submitter
: public std::enable_shared_from_this<result_submitter<Callback, Result>>,
public util::non_movable {
Callback callback_;
Result result_;
std::atomic<std::size_t> left_;
std::once_flag flag_;
// Invokes the callback with the cached result
void invoke() {
assert((left_ == 0U) && "Expected that the submitter is finished!");
std::atomic_thread_fence(std::memory_order_acquire);
// Call the final callback with the cleaned result
std::call_once(flag_, [&] {
aggregated::finalize_data(std::move(callback_), std::move(result_));
});
}
// Completes one result
void complete_one() {
assert((left_ > 0U) && "Expected that the submitter isn't finished!");
auto const current = --left_;
if (!current) {
invoke();
}
}
template <typename Box>
struct partial_all_callback {
Box* box;
std::shared_ptr<result_submitter> me;
template <typename... Args>
void operator()(Args&&... args) && {
// Assign the result to the target
box->assign(std::forward<decltype(args)>(args)...);
// Complete one result
me->complete_one();
}
template <typename... PartialArgs>
void operator()(exception_arg_t tag, exception_t exception) && {
// We never complete the connection, but we forward the first error
// which was raised.
std::call_once(me->flag_, std::move(me->callback_), tag,
std::move(exception));
}
};
public:
explicit result_submitter(Callback callback, Result&& result)
: callback_(std::move(callback)), result_(std::move(result)), left_(1) {
}
/// Creates a submitter which submits it's result into the storage
template <typename Box>
auto create_callback(Box* box) {
left_.fetch_add(1, std::memory_order_seq_cst);
return partial_all_callback<std::decay_t<Box>>{box,
this->shared_from_this()};
}
/// Initially the counter is created with an initial count of 1 in order
/// to prevent that the connection is finished before all callbacks
/// were registered.
void accept() {
complete_one();
}
constexpr auto& head() noexcept {
return result_;
}
};
template <typename Submitter>
struct continuable_dispatcher {
std::shared_ptr<Submitter>& submitter;
template <typename Box, std::enable_if_t<aggregated::is_continuable_box<
std::decay_t<Box>>::value>* = nullptr>
void operator()(Box&& box) const {
// Retrieve a callback from the submitter and attach it to the continuable
box.fetch().next(submitter->create_callback(std::addressof(box))).done();
}
};
} // namespace all
struct connection_strategy_all_tag {};
template <>
struct is_connection_strategy<connection_strategy_all_tag> // ...
: std::true_type {};
/// Finalizes the all logic of a given connection
template <>
struct connection_finalizer<connection_strategy_all_tag> {
/// Finalizes the all logic of a given connection
template <typename Connection>
static auto finalize(Connection&& connection, util::ownership ownership) {
// Create the target result from the connection
auto res =
aggregated::box_continuables(std::forward<Connection>(connection));
auto signature = aggregated::hint_of_data<decltype(res)>();
return base::attorney::create_from(
[res = std::move(res)](auto&& callback) mutable {
using submitter_t =
all::result_submitter<std::decay_t<decltype(callback)>,
std::decay_t<decltype(res)>>;
// Create the shared state which holds the result
// and the final callback
auto state = std::make_shared<submitter_t>(
std::forward<decltype(callback)>(callback), std::move(res));
// Dispatch the continuables and store its partial result
// in the whole result
traverse_pack(all::continuable_dispatcher<submitter_t>{state},
state->head());
// Finalize the connection if all results arrived in-place
state->accept();
},
signature, std::move(ownership));
}
};
} // namespace connection
/// Specialization for a connection annotation
template <>
struct annotation_trait<connection::connection_strategy_all_tag>
: connection::connection_annotation_trait<
connection::connection_strategy_all_tag> {};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONNECTION_ALL_HPP_INCLUDED
// #include <continuable/detail/connection/connection-any.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_CONNECTION_ANY_HPP_INCLUDED
#define CONTINUABLE_DETAIL_CONNECTION_ANY_HPP_INCLUDED
#include <atomic>
#include <memory>
#include <mutex>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-promise-base.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_PROMISE_BASE_HPP_INCLUDED
#define CONTINUABLE_PROMISE_BASE_HPP_INCLUDED
#include <cassert>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
namespace cti {
/// \defgroup Base Base
/// provides classes and functions to create continuable_base objects.
/// \{
/// The promise_base makes it possible to resolve an asynchronous
/// continuable through it's result or through an error type.
///
/// Use the promise type defined in `continuable/continuable_types.hpp`,
/// in order to use this class.
///
/// If we want to resolve the promise_base trough the call operator,
/// and we want to resolve it through an exception, we must call it with a
/// exception_arg_t as first and the exception as second argument.
/// Additionally the promise is resolveable only through its call
/// operator when invoked as an r-value.
///
/// \since 2.0.0
// clang-format off
template <typename Data, typename Hint>
class promise_base
/// \cond false
;
template <typename Data, typename... Args>
class promise_base<Data, detail::identity<Args...>>
: detail::util::non_copyable
/// \endcond
{ // clang-format on
/// \cond false
// The callback type
Data data_;
/// \endcond
public:
/// Constructor for constructing an empty promise
explicit promise_base() = default;
/// Constructor accepting the data object
explicit promise_base(Data data) : data_(std::move(data)) {
}
/// \cond false
promise_base(promise_base&&) = default;
promise_base(promise_base const&) = delete;
promise_base& operator=(promise_base&&) = default;
promise_base& operator=(promise_base const&) = delete;
/// \endcond
/// Constructor accepting any object convertible to the data object
template <typename OData,
std::enable_if_t<std::is_convertible<
detail::traits::unrefcv_t<OData>, Data>::value>* = nullptr>
/* implicit */ promise_base(OData&& data) : data_(std::forward<OData>(data)) {
}
/// Assignment operator accepting any object convertible to the data object
template <typename OData,
std::enable_if_t<std::is_convertible<
detail::traits::unrefcv_t<OData>, Data>::value>* = nullptr>
promise_base& operator=(OData&& data) {
data_ = std::forward<OData>(data);
return *this;
}
/// Resolves the continuation with the given values.
///
/// \throws This method never throws an exception.
///
/// \attention This method may only be called once,
/// when the promise is valid operator bool() returns true.
/// Calling this method will invalidate the promise such that
/// subsequent calls to operator bool() will return false.
/// This behaviour is only consistent in promise_base and
/// non type erased promises may behave differently.
/// Invoking an invalid promise_base is undefined!
///
/// \since 2.0.0
void operator()(Args... args) && noexcept {
assert(data_);
std::move(data_)(std::move(args)...);
data_ = nullptr;
}
/// Resolves the continuation with the given exception.
///
/// \throws This method never throws an exception.
///
/// \attention This method may only be called once,
/// when the promise is valid operator bool() returns true.
/// Calling this method will invalidate the promise such that
/// subsequent calls to operator bool() will return false.
/// This behaviour is only consistent in promise_base and
/// non type erased promises may behave differently.
/// Invoking an invalid promise_base is undefined!
///
/// \since 2.0.0
void operator()(exception_arg_t tag, exception_t exception) && noexcept {
assert(data_);
std::move(data_)(tag, std::move(exception));
data_ = nullptr;
}
/// Resolves the continuation with the given values.
///
/// \throws This method never throws an exception.
///
/// \attention This method may only be called once,
/// when the promise is valid operator bool() returns true.
/// Calling this method will invalidate the promise such that
/// subsequent calls to operator bool() will return false.
/// This behaviour is only consistent in promise_base and
/// non type erased promises may behave differently.
/// Invoking an invalid promise_base is undefined!
///
/// \since 2.0.0
void set_value(Args... args) noexcept {
// assert(data_);
std::move(data_)(std::move(args)...);
data_ = nullptr;
}
/// Resolves the continuation with the given exception.
///
/// \throws This method never throws an exception.
///
/// \attention This method may only be called once,
/// when the promise is valid operator bool() returns true.
/// Calling this method will invalidate the promise such that
/// subsequent calls to operator bool() will return false.
/// This behaviour is only consistent in promise_base and
/// non type erased promises may behave differently.
/// Invoking an invalid promise_base is undefined!
///
/// \since 2.0.0
void set_exception(exception_t exception) noexcept {
assert(data_);
std::move(data_)(exception_arg_t{}, std::move(exception));
data_ = nullptr;
}
/// Resolves the continuation with the cancellation token which is represented
/// by a default constructed exception_t.
///
/// \throws This method never throws an exception.
///
/// \attention This method may only be called once,
/// when the promise is valid operator bool() returns true.
/// Calling this method will invalidate the promise such that
/// subsequent calls to operator bool() will return false.
/// This behaviour is only consistent in promise_base and
/// non type erased promises may behave differently.
/// Invoking an invalid promise_base is undefined!
///
/// \since 4.0.0
void set_canceled() noexcept {
assert(data_);
std::move(data_)(exception_arg_t{}, exception_t{});
data_ = nullptr;
}
/// Returns true if the continuation is valid (non empty).
///
/// \throws This method never throws an exception.
///
/// \since 4.0.0
explicit operator bool() const noexcept {
return bool(data_);
}
};
/// \}
} // namespace cti
#endif // CONTINUABLE_PROMISE_BASE_HPP_INCLUDED
// #include <continuable/continuable-traverse.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/traversal/container-category.hpp>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace connection {
namespace any {
/// Invokes the callback with the first arriving result
template <typename T>
class any_result_submitter
: public std::enable_shared_from_this<any_result_submitter<T>>,
public util::non_movable {
T callback_;
std::once_flag flag_;
struct any_callback {
std::shared_ptr<any_result_submitter> me_;
template <typename... PartialArgs>
void operator()(PartialArgs&&... args) && {
me_->invoke(std::forward<decltype(args)>(args)...);
}
};
public:
explicit any_result_submitter(T callback) : callback_(std::move(callback)) {
}
/// Creates a submitter which submits it's result to the callback
auto create_callback() {
return any_callback{this->shared_from_this()};
}
private:
// Invokes the callback with the given arguments
template <typename... ActualArgs>
void invoke(ActualArgs&&... args) {
std::call_once(flag_, std::move(callback_),
std::forward<ActualArgs>(args)...);
}
};
struct result_deducer {
template <typename T>
static auto deduce_one(std::false_type, identity<T>) {
static_assert(traits::fail<T>::value,
"Non continuable types except tuple like and homogeneous "
"containers aren't allowed inside an any expression!");
}
template <typename T>
static auto deduce_one(std::true_type, identity<T> id) {
return base::annotation_of(id);
}
template <typename T>
static auto deduce(traversal::container_category_tag<false, false>,
identity<T> id) {
return deduce_one<T>(base::is_continuable<T>{}, id);
}
/// Deduce a homogeneous container
template <bool IsTupleLike, typename T>
static auto deduce(traversal::container_category_tag<true, IsTupleLike>,
identity<T>) {
// Deduce the containing type
using element_t = std::decay_t<decltype(*std::declval<T>().begin())>;
return deduce(traversal::container_category_of_t<element_t>{},
identity<element_t>{});
}
template <typename First, typename... T>
static auto deduce_same_hints(First first, T...) {
static_assert(traits::conjunction<std::is_same<First, T>...>::value,
"The continuables inside the given pack must have the "
"same signature hint!");
return first;
}
template <std::size_t... I, typename T>
static auto deduce_tuple_like(std::integer_sequence<std::size_t, I...>,
identity<T>) {
return deduce_same_hints(deduce(
traversal::container_category_of_t<
std::decay_t<decltype(std::get<I>(std::declval<T>()))>>{},
identity<std::decay_t<decltype(std::get<I>(std::declval<T>()))>>{})...);
}
/// Traverse tuple like container
template <typename T>
static auto deduce(traversal::container_category_tag<false, true>,
identity<T> id) {
constexpr auto const size = std::tuple_size<T>::value;
return deduce_tuple_like(std::make_index_sequence<size>{}, id);
}
};
template <typename Submitter>
struct continuable_dispatcher {
std::shared_ptr<Submitter>& submitter;
template <typename Continuable,
std::enable_if_t<base::is_continuable<
std::decay_t<Continuable>>::value>* = nullptr>
void operator()(Continuable&& continuable) {
// Retrieve a callback from the submitter and attach it to the continuable
std::forward<Continuable>(continuable)
.next(submitter->create_callback())
.done();
}
};
} // namespace any
struct connection_strategy_any_tag {};
template <>
struct is_connection_strategy<connection_strategy_any_tag> // ...
: std::true_type {};
/// Finalizes the any logic of a given connection
template <>
struct connection_finalizer<connection_strategy_any_tag> {
template <typename Connection>
static auto finalize(Connection&& connection, util::ownership ownership) {
constexpr auto const signature = decltype(any::result_deducer::deduce(
traversal::container_category_of_t<std::decay_t<Connection>>{},
identity<std::decay_t<Connection>>{})){};
return base::attorney::create_from(
[connection =
std::forward<Connection>(connection)](auto&& callback) mutable {
using submitter_t =
any::any_result_submitter<std::decay_t<decltype(callback)>>;
// Create the submitter which calls the given callback once at the
// first callback invocation.
auto submitter = std::make_shared<submitter_t>(
std::forward<decltype(callback)>(callback));
traverse_pack(any::continuable_dispatcher<submitter_t>{submitter},
std::move(connection));
},
signature, std::move(ownership));
}
};
} // namespace connection
/// Specialization for a connection annotation
template <>
struct annotation_trait<connection::connection_strategy_any_tag>
: connection::connection_annotation_trait<
connection::connection_strategy_any_tag> {};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONNECTION_ANY_HPP_INCLUDED
// #include <continuable/detail/connection/connection-seq.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_CONNECTION_SEQ_HPP_INCLUDED
#define CONTINUABLE_DETAIL_CONNECTION_SEQ_HPP_INCLUDED
#include <cassert>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-traverse-async.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TRAVERSE_ASYNC_HPP_INCLUDED
#define CONTINUABLE_TRAVERSE_ASYNC_HPP_INCLUDED
#include <utility>
// #include <continuable/detail/traversal/traverse-async.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TRAVERSE_ASYNC_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TRAVERSE_ASYNC_HPP_INCLUDED
#include <atomic>
#include <cassert>
#include <cstddef>
#include <iterator>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
// #include <continuable/detail/traversal/container-category.hpp>
// #include <continuable/detail/utility/traits.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,
std::integer_sequence<std::size_t, Sequence...>>
: std::common_type<
std::integer_sequence<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,
std::make_index_sequence<End - Begin>>::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<std::decay_t<Frame>, std::decay_t<State>> {
return resume_traversal_callable<std::decay_t<Frame>, std::decay_t<State>>(
std::forward<Frame>(frame), std::forward<State>(state));
}
template <typename T, typename = void>
struct has_head : std::false_type {};
template <typename T>
struct has_head<T, traits::void_t<decltype(std::declval<T>().head())>>
: std::true_type {};
template <typename Visitor, typename... Args>
class async_traversal_frame_data : public Visitor {
std::tuple<Args...> args_;
public:
explicit async_traversal_frame_data(Visitor visitor, Args... args)
: Visitor(std::move(visitor)),
args_(std::make_tuple(std::move(args)...)) {
}
template <typename MapperArg>
explicit async_traversal_frame_data(async_traverse_in_place_tag<Visitor>,
MapperArg&& mapper_arg, Args... args)
: Visitor(std::forward<MapperArg>(mapper_arg)),
args_(std::make_tuple(std::move(args)...)) {
}
/// Returns the arguments of the frame
std::tuple<Args...>& head() noexcept {
return args_;
}
};
template <typename Visitor>
class async_traversal_frame_no_data : public Visitor {
public:
explicit async_traversal_frame_no_data(Visitor visitor)
: Visitor(std::move(visitor)) {
}
template <typename MapperArg>
explicit async_traversal_frame_no_data(async_traverse_in_place_tag<Visitor>,
MapperArg&& mapper_arg)
: Visitor(std::forward<MapperArg>(mapper_arg)) {
}
};
template <typename Visitor, typename... Args>
using data_layout_t =
std::conditional_t<has_head<Visitor>::value,
async_traversal_frame_no_data<Visitor>,
async_traversal_frame_data<Visitor, Args...>>;
/// Stores the visitor and the arguments to traverse
template <typename Visitor, typename... Args>
class async_traversal_frame : public data_layout_t<Visitor, Args...> {
#ifndef NDEBUG
std::atomic<bool> finished_;
#endif // NDEBUG
Visitor& visitor() noexcept {
return *static_cast<Visitor*>(this);
}
Visitor const& visitor() const noexcept {
return *static_cast<Visitor const*>(this);
}
public:
template <typename... T>
explicit async_traversal_frame(T&&... args)
: data_layout_t<Visitor, Args...>(std::forward<T>(args)...)
#ifndef NDEBUG
,
finished_(false)
#endif // NDEBUG
{
}
/// We require a virtual base
virtual ~async_traversal_frame() override = default;
/// Calls the visitor with the given element
template <typename T>
auto traverse(T&& value) -> decltype(visitor()(async_traverse_visit_tag{},
std::forward<T>(value))) {
return 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) {
// Cast the frame up
auto frame = std::static_pointer_cast<async_traversal_frame>(
this->shared_from_this());
// Create a callable object which resumes the current
// traversal when it's called.
auto resumable = make_resume_traversal_callable(
std::move(frame), std::forward<Hierarchy>(hierarchy));
// Invoke the visitor with the current value and the
// callable object to resume the control flow.
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() {
#ifndef NDEBUG
{
bool expected = false;
assert(finished_.compare_exchange_strong(expected, true));
}
#endif // NDEBUG
visitor()(async_traverse_complete_tag{}, std::move(this->head()));
}
};
template <typename Target, std::size_t Begin, std::size_t End>
struct static_async_range {
Target* target_;
constexpr decltype(auto) operator*() const noexcept {
return std::get<Begin>(*target_);
}
template <std::size_t Position>
constexpr auto relocate(std::integral_constant<std::size_t, Position>) const
noexcept {
return static_async_range<Target, Position, End>{target_};
}
constexpr auto 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>
auto make_static_range(T&& element) {
using range_t = static_async_range<std::decay_t<T>, 0U,
std::tuple_size<std::decay_t<T>>::value>;
return range_t{std::addressof(element)};
}
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<std::decay_t<decltype(std::begin(std::declval<T>()))>,
std::decay_t<decltype(std::end(std::declval<T>()))>>;
/// Returns a dynamic range for the given type
template <typename T>
auto make_dynamic_async_range(T&& element) {
using range_t = dynamic_async_range_of_t<T>;
return range_t{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_;
std::tuple<Hierarchy...> hierarchy_;
bool& detached_;
public:
explicit async_traversal_point(Frame frame,
std::tuple<Hierarchy...> hierarchy,
bool& detached)
: frame_(std::move(frame)), hierarchy_(std::move(hierarchy)),
detached_(detached) {
}
// Abort the current control flow
void detach() noexcept {
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, std::decay_t<Parent>, Hierarchy...> {
// Create a new hierarchy which contains the
// the parent (the last traversed element).
auto hierarchy = std::tuple_cat(
std::make_tuple(std::forward<Parent>(parent)), hierarchy_);
return async_traversal_point<Frame, std::decay_t<Parent>, 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
-> traits::void_t<decltype(std::declval<Frame>()->traverse(*current))> {
if (!frame_->traverse(*current)) {
// Store the current call hierarchy into a tuple for
// later re-entrance.
auto hierarchy =
std::tuple_cat(std::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 = std::decay_t<decltype(*current)>;
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(
std::integer_sequence<std::size_t, Sequence...>, Current&& current) {
int dummy[] = {0, (async_traverse_one_checked(current.relocate(
std::integral_constant<std::size_t, Sequence>{})),
0)...};
(void)dummy;
(void)current;
}
/// 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<std::decay_t<Frame>, std::decay_t<Hierarchy>...>;
/// 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, std::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_);
traits::unpack(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_t =
async_traversal_frame<std::decay_t<Visitor>, std::decay_t<Args>...>;
/// The type of the demoted visitor type
using visitor_t = 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>
auto apply_pack_transform_async(Visitor&& visitor, Args&&... args) {
// Provide the frame and visitor type
using types = async_traversal_types<Visitor, Args...>;
using frame_t = typename types::frame_t;
using visitor_t = typename types::visitor_t;
// Check whether the visitor inherits enable_shared_from_this
static_assert(std::is_base_of<std::enable_shared_from_this<visitor_t>,
visitor_t>::value,
"The visitor must inherit std::enable_shared_from_this!");
// Check whether the visitor is virtual destructible
static_assert(std::has_virtual_destructor<visitor_t>::value,
"The visitor must have a virtual destructor!");
// Create the frame on the heap which stores the arguments
// to traverse asynchronous. It persists until the
// traversal frame isn't referenced anymore.
auto frame = std::make_shared<frame_t>(std::forward<Visitor>(visitor),
std::forward<Args>(args)...);
// Create a static range for the top level tuple
auto range = std::make_tuple(make_static_range(frame->head()));
// Create a resumer to start the asynchronous traversal
auto resumer = make_resume_traversal_callable(frame, std::move(range));
// Start the asynchronous traversal
resumer();
// Cast the shared_ptr down to the given visitor type
// for implementation invisibility
return std::static_pointer_cast<visitor_t>(std::move(frame));
}
} // namespace traversal
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TRAVERSE_ASYNC_HPP_INCLUDED
namespace cti {
/// \defgroup Traversal Traversal
/// provides functions to traverse and remap nested packs.
/// \{
/// A tag which is passed to the `operator()` of the visitor
/// if an element is visited synchronously through \ref traverse_pack_async.
///
/// \since 3.0.0
using async_traverse_visit_tag = detail::traversal::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 through \ref traverse_pack_async.
///
/// \since 3.0.0
using async_traverse_detach_tag = detail::traversal::async_traverse_detach_tag;
/// A tag which is passed to the `operator()` of the visitor if the
/// asynchronous pack traversal was finished through \ref traverse_pack_async.
///
/// \since 3.0.0
using async_traverse_complete_tag =
detail::traversal::async_traverse_complete_tag;
/// A tag to identify that a mapper shall be constructed in-place
/// from the first argument passed to \ref traverse_pack_async.
///
/// \since 3.0.0
template <typename T>
using async_traverse_in_place_tag =
detail::traversal::async_traverse_in_place_tag<T>;
/// Traverses the pack with the given visitor in an asynchronous way.
///
/// This function works in the same way as `traverse_pack`,
/// however, we are able to suspend and continue the traversal at
/// later time.
/// Thus we require a visitor callable object which provides three
/// `operator()` overloads as depicted by the code sample below:
/// ```cpp
/// struct my_async_visitor {
/// /// The synchronous overload is called for each object,
/// /// it may return false to suspend the current control flow.
/// /// In that case the overload below is called.
/// template <typename T>
/// bool operator()(async_traverse_visit_tag, T&& element) {
/// return true;
/// }
///
/// /// The asynchronous overload this is called when the
/// /// synchronous overload returned false.
/// /// In addition to the current visited element the overload is
/// /// called with a contnuation callable object which resumes the
/// /// traversal when it's called later.
/// /// The continuation next may be stored and called later or
/// /// dropped completely to abort the traversal early.
/// template <typename T, typename N>
/// void operator()(async_traverse_detach_tag, T&& element, N&& next) {
/// }
///
/// /// The overload is called when the traversal was finished.
/// /// As argument the whole pack is passed over which we
/// /// traversed asynchrnously.
/// template <typename T>
/// void operator()(async_traverse_complete_tag, T&& pack) {
/// }
/// };
/// ```
///
/// \param visitor A visitor object which provides the three `operator()`
/// overloads that were described above.
/// Additionally the visitor must be compatible
/// for referencing it from a `boost::intrusive_ptr`.
/// The visitor should must have a virtual destructor!
///
/// \param pack The arbitrary parameter pack which is traversed
/// asynchronously. Nested objects inside containers and
/// tuple like types are traversed recursively.
///
/// \returns A std::shared_ptr that references an instance of
/// the given visitor object.
///
/// \since 3.0.0
///
/// See `traverse_pack` for a detailed description about the
/// traversal behaviour and capabilities.
///
template <typename Visitor, typename... T>
auto traverse_pack_async(Visitor&& visitor, T&&... pack) {
return detail::traversal::apply_pack_transform_async(
std::forward<Visitor>(visitor), std::forward<T>(pack)...);
}
/// \}
} // namespace cti
#endif // CONTINUABLE_TRAVERSE_ASYNC_HPP_INCLUDED
// #include <continuable/detail/connection/connection-aggregated.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
namespace cti {
namespace detail {
namespace connection {
namespace seq {
/// Connects the left and the right continuable to a sequence
///
/// \note This is implemented in an eager way because we would not gain
/// any profit from chaining sequences lazily.
template <typename Left, typename Right>
auto sequential_connect(Left&& left, Right&& right) {
left.freeze(right.is_frozen());
right.freeze();
return std::forward<Left>(left).then(
[right = std::forward<Right>(right)](auto&&... args) mutable {
return std::move(right).then(
[previous = std::make_tuple(std::forward<decltype(args)>(args)...)](
auto&&... args) mutable {
return std::tuple_cat(
std::move(previous),
std::make_tuple(std::forward<decltype(args)>(args)...));
});
});
}
template <typename Callback, typename Box>
struct sequential_dispatch_data {
Callback callback;
Box box;
};
template <typename Data>
class sequential_dispatch_visitor
: public std::enable_shared_from_this<sequential_dispatch_visitor<Data>>,
public util::non_movable {
Data data_;
public:
explicit sequential_dispatch_visitor(Data&& data) : data_(std::move(data)) {
}
virtual ~sequential_dispatch_visitor() = default;
/// Returns the pack that should be traversed
auto& head() {
return data_.box;
}
template <typename Box, std::enable_if_t<aggregated::is_continuable_box<
std::decay_t<Box>>::value>* = nullptr>
bool operator()(async_traverse_visit_tag, Box&& box) {
if (base::attorney::is_ready(box.peek())) {
// The result can be resolved directly
traits::unpack(
[&](auto&&... args) mutable {
box.assign(std::forward<decltype(args)>(args)...);
},
base::attorney::query(box.fetch()));
return true;
} else {
return false;
}
}
template <typename Box, typename N>
void operator()(async_traverse_detach_tag, Box&& box, N&& next) {
box.fetch()
.then([box = std::addressof(box),
next = std::forward<N>(next)](auto&&... args) mutable {
// Assign the result to the target
box->assign(std::forward<decltype(args)>(args)...);
// Continue the asynchronous sequential traversal
next();
})
.fail([me = this->shared_from_this()](exception_t exception) {
// Abort the traversal when an error occurred
std::move(me->data_.callback)(exception_arg_t{},
std::move(exception));
})
.done();
}
template <typename T>
void operator()(async_traverse_complete_tag, T&& /*pack*/) {
return aggregated::finalize_data(std::move(data_.callback),
std::move(data_.box));
}
};
} // namespace seq
struct connection_strategy_seq_tag {};
template <>
struct is_connection_strategy<connection_strategy_seq_tag> // ...
: std::true_type {};
/// Finalizes the seq logic of a given connection
template <>
struct connection_finalizer<connection_strategy_seq_tag> {
/// Finalizes the all logic of a given connection
template <typename Connection>
static auto finalize(Connection&& connection, util::ownership ownership) {
auto res =
aggregated::box_continuables(std::forward<Connection>(connection));
auto signature = aggregated::hint_of_data<decltype(res)>();
return base::attorney::create_from(
[res = std::move(res)](auto&& callback) mutable {
// The data from which the visitor is constructed in-place
using data_t =
seq::sequential_dispatch_data<std::decay_t<decltype(callback)>,
std::decay_t<decltype(res)>>;
// The visitor type
using visitor_t = seq::sequential_dispatch_visitor<data_t>;
traverse_pack_async(async_traverse_in_place_tag<visitor_t>{},
data_t{std::forward<decltype(callback)>(callback),
std::move(res)});
},
signature, std::move(ownership));
}
};
} // namespace connection
/// Specialization for a connection annotation
template <>
struct annotation_trait<connection::connection_strategy_seq_tag>
: connection::connection_annotation_trait<
connection::connection_strategy_seq_tag> {};
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_CONNECTION_SEQ_HPP_INCLUDED
// #include <continuable/detail/connection/connection.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
#if defined(CONTINUABLE_HAS_COROUTINE)
// # include <continuable/detail/other/coroutines.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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.
**/
// Exclude this header when coroutines are not available
#ifndef CONTINUABLE_DETAIL_AWAITING_HPP_INCLUDED
#define CONTINUABLE_DETAIL_AWAITING_HPP_INCLUDED
#include <cassert>
#include <type_traits>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-result.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
# include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS
#if defined(CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE)
# include <experimental/coroutine>
#elif defined(CONTINUABLE_HAS_COROUTINE)
# include <coroutine>
#endif
#if defined(CONTINUABLE_HAS_COROUTINE)
namespace cti {
namespace detail {
namespace awaiting {
/// We import the coroutine handle in our namespace
# if defined(CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE)
using std::experimental::coroutine_handle;
using std::experimental::suspend_never;
# else
using std::coroutine_handle;
using std::suspend_never;
# endif
# if defined(CONTINUABLE_HAS_EXCEPTIONS)
class await_canceled_exception : public std::exception {
public:
await_canceled_exception() noexcept = default;
char const* what() const noexcept override {
return "co_await canceled due to cancellation of the continuation";
}
};
# endif // CONTINUABLE_HAS_EXCEPTIONS
template <typename T>
struct result_from_identity;
template <typename... T>
struct result_from_identity<identity<T...>> {
using result_t = result<T...>;
};
/// An object which provides the internal buffer and helper methods
/// for waiting on a continuable in a stackless coroutine.
template <typename Continuable>
class awaitable {
using hint_t = decltype(base::annotation_of(identify<Continuable>{}));
using result_t = typename result_from_identity<hint_t>::result_t;
/// The continuable which is invoked upon suspension
Continuable continuable_;
/// A cache which is used to pass the result of the continuation
/// to the coroutine.
result_t result_;
public:
explicit constexpr awaitable(Continuable&& continuable)
: continuable_(std::move(continuable)) {
// If the continuable is ready resolve the result from the
// continuable immediately.
if (base::attorney::is_ready(continuable_)) {
assert(result_.is_empty());
result_ = base::attorney::query(std::move(continuable_));
}
}
/// Return whether the continuable can provide its result instantly,
/// which also means its execution is side-effect free.
bool await_ready() const noexcept {
return !result_.is_empty();
}
/// Suspend the current context
// TODO Convert this to an r-value function once possible
void await_suspend(coroutine_handle<> h) {
assert(result_.is_empty());
// Forward every result to the current awaitable
std::move(continuable_)
.next([h, this](auto&&... args) mutable {
assert(result_.is_empty());
result_ = result_t::from(std::forward<decltype(args)>(args)...);
h.resume();
})
.done();
}
/// Resume the coroutine represented by the handle
typename result_t::value_t await_resume() noexcept(false) {
if (result_.is_value()) {
// When the result was resolved return it
return std::move(result_).get_value();
}
assert(result_.is_exception());
# if defined(CONTINUABLE_HAS_EXCEPTIONS)
if (exception_t e = result_.get_exception()) {
std::rethrow_exception(std::move(e));
} else {
throw await_canceled_exception();
}
# else // CONTINUABLE_HAS_EXCEPTIONS
// Returning error types from co_await isn't supported!
CTI_DETAIL_TRAP();
# endif // CONTINUABLE_HAS_EXCEPTIONS
}
};
/// Converts a continuable into an awaitable object as described by
/// the C++ coroutine TS.
template <typename T>
constexpr auto create_awaiter(T&& continuable) {
return awaitable<std::decay_t<T>>(std::forward<T>(continuable));
}
/// This makes it possible to take the coroutine_handle over on suspension
struct handle_takeover {
coroutine_handle<>& handle_;
bool await_ready() noexcept {
return false;
}
void await_suspend(coroutine_handle<> handle) noexcept {
handle_ = handle;
}
void await_resume() noexcept {}
};
/// The type which is passed to the compiler that describes the properties
/// of a continuable_base used as coroutine promise type.
template <typename Continuable, typename Promise, typename... Args>
struct promise_type;
/// Implements the resolving method return_void and return_value accordingly
template <typename Base>
struct promise_resolver_base;
template <typename Continuable, typename Promise>
struct promise_resolver_base<promise_type<Continuable, Promise>> {
void return_void() {
auto me = static_cast<promise_type<Continuable, Promise>*>(this);
me->promise_.set_value();
}
};
template <typename Continuable, typename Promise, typename T>
struct promise_resolver_base<promise_type<Continuable, Promise, T>> {
void return_value(T value) {
auto me = static_cast<promise_type<Continuable, Promise, T>*>(this);
me->promise_.set_value(std::move(value));
}
};
template <typename Continuable, typename Promise, typename... Args>
struct promise_resolver_base<promise_type<Continuable, Promise, Args...>> {
template <typename T>
void return_value(T&& tuple_like) {
auto me = static_cast<promise_type<Continuable, Promise, Args...>*>(this);
traits::unpack(std::move(me->promise_), std::forward<T>(tuple_like));
}
};
template <typename Continuable, typename Promise, typename... Args>
struct promise_type
: promise_resolver_base<promise_type<Continuable, Promise, Args...>> {
coroutine_handle<> handle_;
Promise promise_;
explicit promise_type() = default;
Continuable get_return_object() {
return [this](auto&& promise) {
promise_ = std::forward<decltype(promise)>(promise);
handle_.resume();
};
}
handle_takeover initial_suspend() {
return {handle_};
}
suspend_never final_suspend() noexcept {
return {};
}
void unhandled_exception() noexcept {
# if defined(CONTINUABLE_HAS_EXCEPTIONS)
try {
std::rethrow_exception(std::current_exception());
} catch (await_canceled_exception const&) {
promise_.set_canceled();
} catch (...) {
promise_.set_exception(std::current_exception());
}
# else // CONTINUABLE_HAS_EXCEPTIONS
// Returning exception types from a coroutine isn't supported
CTI_DETAIL_TRAP();
# endif // CONTINUABLE_HAS_EXCEPTIONS
}
};
} // namespace awaiting
} // namespace detail
} // namespace cti
#endif // defined(CONTINUABLE_HAS_COROUTINE)
#endif // CONTINUABLE_DETAIL_UTIL_HPP_INCLUDED
#endif // defined(CONTINUABLE_HAS_COROUTINE)
namespace cti {
/// \defgroup Base Base
/// provides classes and functions to create continuable_base objects.
/// \{
/// Deduces to a true_type if the given type is a continuable_base.
///
/// \since 3.0.0
template <typename T>
using is_continuable = detail::base::is_continuable<T>;
/// The main class of the continuable library, it provides the functionality
/// for chaining callbacks and continuations together to a unified hierarchy.
///
/// The most important method is the cti::continuable_base::then() method,
/// which allows to attach a callback to the continuable.
///
/// Use the continuable types defined in `continuable/continuable.hpp`,
/// in order to use this class.
///
/// \tparam Data The internal data which is used to store the current
/// continuation and intermediate lazy connection result.
///
/// \tparam Annotation The internal data used to store the current signature
/// hint or strategy used for combining lazy connections.
///
/// \note Nearly all methods of the cti::continuable_base are required to be
/// called as r-value. This is required because the continuable carries
/// variables which are consumed when the object is transformed as part
/// of a method call.
///
/// \attention The continuable_base objects aren't intended to be stored.
/// If you want to store a continuble_base you should always
/// call the continuable_base::freeze method for disabling the
/// invocation on destruction.
///
/// \since 1.0.0
template <typename Data, typename Annotation>
class continuable_base {
/// \cond false
using ownership = detail::util::ownership;
using annotation_trait = detail::annotation_trait<Annotation>;
template <typename, typename>
friend class continuable_base;
friend struct detail::base::attorney;
// The continuation type or intermediate result
Data data_;
// The transferable state which represents the validity of the object
ownership ownership_;
/// \endcond
/// Constructor accepting the data object while erasing the annotation
explicit continuable_base(Data data, ownership ownership)
: data_(std::move(data))
, ownership_(std::move(ownership)) {}
public:
/// Constructor accepting the data object while erasing the annotation
explicit continuable_base(Data data)
: data_(std::move(data)) {}
/// Constructor accepting any object convertible to the data object,
/// while erasing the annotation
template <typename OtherData,
std::enable_if_t<detail::base::can_accept_continuation<
Data, Annotation,
detail::traits::unrefcv_t<OtherData>>::value>* = nullptr>
/* implicit */ continuable_base(OtherData&& data)
: data_(
detail::base::proxy_continuable<Annotation,
detail::traits::unrefcv_t<OtherData>>(
std::forward<OtherData>(data))) {}
/// Constructor taking the data of other continuable_base objects
/// while erasing the hint.
///
/// This constructor makes it possible to replace the internal data object of
/// the continuable by any object which is useful for type-erasure.
template <typename OData,
std::enable_if_t<std::is_convertible<
detail::traits::unrefcv_t<OData>, Data>::value>* = nullptr>
/* implicit */ continuable_base(continuable_base<OData, Annotation>&& other)
: data_(std::move(other).consume()) {}
/// Constructor taking the data of other continuable_base objects
/// while erasing the hint.
///
/// This constructor makes it possible to replace the internal data object of
/// the continuable by any object which is useful for type-erasure.
template <typename OData, typename OAnnotation>
/* implicit */ continuable_base(continuable_base<OData, OAnnotation>&& other)
: continuable_base(std::move(other).finish().consume()) {}
/// \cond false
continuable_base(continuable_base&&) = default;
continuable_base(continuable_base const&) = delete;
continuable_base& operator=(continuable_base&&) = default;
continuable_base& operator=(continuable_base const&) = delete;
/// \endcond
/// The destructor automatically invokes the continuable_base
/// if it wasn't consumed yet.
///
/// In order to invoke the continuable early you may call the
/// continuable_base::done() method.
///
/// The continuable_base::freeze method disables the automatic
/// invocation on destruction without invalidating the object.
///
/// \since 1.0.0
~continuable_base() {
if (ownership_.is_acquired() && !ownership_.is_frozen()) {
std::move(*this).done();
}
assert((!ownership_.is_acquired() || ownership_.is_frozen()) &&
"Ownership should be released!");
}
/// Main method of the continuable_base to chain the current continuation
/// with a new callback.
///
/// \param callback The callback which is used to process the current
/// asynchronous result on arrival. The callback is required to accept
/// the current result at least partially (or nothing of the result).
/// ```cpp
/// (http_request("github.com") && http_request("atom.io"))
/// .then([](std::string github, std::string atom) {
/// // We use the whole result
/// });
///
/// (http_request("github.com") && http_request("atom.io"))
/// .then([](std::string github) {
/// // We only use the result partially
/// });
///
/// (http_request("github.com") && http_request("atom.io"))
/// .then([] {
/// // We discard the result
/// });
/// ```
///
/// \param executor The optional executor which is used to dispatch
/// the callback. The executor needs to accept callable objects
/// callable through an `operator()` through its operator() itself.
/// The executor can be move-only, but it's not required to.
/// The default executor which is used when omitting the argument
/// dispatches the callback on the current executing thread.
/// Consider the example shown below:
/// ```cpp
/// auto executor = [](auto&& work) {
/// // Dispatch the work here or forward it to an executor of
/// // your choice.
/// std::forward<decltype(work)>(work)();
/// };
///
/// http_request("github.com")
/// .then([](std::string github) {
/// // Do something...
/// }, executor);
/// ```
///
/// \returns Returns a continuable_base with an asynchronous return type
/// depending on the return value of the callback:
/// | Callback returns | Resulting type |
/// | : ---------------------- : | : --------------------------------------- |
/// | `void` | `continuable_base with <>` |
/// | `Arg` | `continuable_base with <Arg>` |
/// | `std::pair<First, Second>` | `continuable_base with <First, Second>` |
/// | `std::tuple<Args...>` | `continuable_base with <Args...>` |
/// | `cti::result<Args...>` | `continuable_base with <Args...>` |
/// | `continuable_base<Arg...>` | `continuable_base with <Args...>` |
/// Which means the result type of the continuable_base is equal to
/// the plain types the callback returns (`std::tuple` and
/// `std::pair` arguments are unwrapped).
/// A single continuable_base as argument is resolved and the result
/// type is equal to the resolved continuable_base.
/// A cti::result can be used to cancel the continuation or to
/// transition to the exception handler.
/// The special unwrapping of types can be disabled through wrapping
/// such objects through a call to cti::make_plain.
/// Consider the following examples:
/// ```cpp
/// http_request("github.com")
/// .then([](std::string github) { return; })
/// .then([] { }); // <void>
///
/// http_request("github.com")
/// .then([](std::string github) { return 0; })
/// .then([](int a) { }); // <int>
///
/// http_request("github.com")
/// .then([](std::string github) { return std::make_pair(1, 2); })
/// .then([](int a, int b) { }); // <int, int>
///
/// http_request("github.com")
/// .then([](std::string github) { return std::make_tuple(1, 2, 3); })
/// .then([](int a, int b, int c) { }); // <int, int, int>
///
/// http_request("github.com")
/// .then([](std::string github) { return http_request("atom.io"); })
/// .then([](std::string atom) { }); // <std::string>
///
/// http_request("example.com")
/// .then([](std::string content) -> result<std::string> {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// })
/// .fail([] -> result<std::string> {
/// return recover("Hello World!");
/// })
/// .then([](std::string content) -> result<std::string> {
/// return cancel();
/// })
/// ```
///
/// \since 1.0.0
template <typename T, typename E = detail::types::this_thread_executor_tag>
auto then(T&& callback,
E&& executor = detail::types::this_thread_executor_tag{}) && {
return detail::base::chain_continuation<detail::base::handle_results::yes,
detail::base::handle_errors::no>(
std::move(*this).finish(), std::forward<T>(callback),
std::forward<E>(executor));
}
/// Additional overload of the continuable_base::then() method
/// which is accepting a continuable_base itself.
///
/// \param continuation A continuable_base reflecting the continuation
/// which is used to continue the call hierarchy.
/// The result of the current continuable is discarded and the given
/// continuation is invoked as shown below.
/// ```cpp
/// http_request("github.com")
/// .then(http_request("atom.io"))
/// .then([](std::string atom) {
/// // ...
/// });
/// ```
///
/// \returns Returns a continuable_base representing the next asynchronous
/// result to continue within the asynchronous call hierarchy.
///
/// \since 1.0.0
template <typename OData, typename OAnnotation>
auto then(continuable_base<OData, OAnnotation>&& continuation) && {
return std::move(*this).then(
detail::base::wrap_continuation(std::move(continuation).finish()));
}
/// Main method of the continuable_base to catch exceptions and error codes
/// in case the asynchronous control flow failed and was resolved
/// through an error code or exception.
///
/// \param callback The callback which is used to process the current
/// asynchronous error result on arrival.
/// In case the continuable_base is using exceptions,
/// the usage is as shown below:
///
/// ```cpp
/// http_request("github.com")
/// .then([](std::string github) { })
/// .fail([](std::exception_ptr ep) {
/// // Check whether the exception_ptr is valid (not default constructed)
/// // if bool(ep) == false this means that the operation was cancelled
/// // by the user or application (promise.set_canceled() or
/// // make_cancelling_continuable()).
/// if (ep) {
/// // Handle the error here
/// try {
/// std::rethrow_exception(ep);
/// } catch (std::exception& e) {
/// e.what(); // Handle the exception
/// }
/// }
/// });
/// ```
/// In case exceptions are disabled, `std::error_condition` is
/// used as error result instead of `std::exception_ptr`.
/// ```cpp
/// http_request("github.com")
/// .then([](std::string github) { })
/// .fail([](std::error_condition error) {
/// error.message(); // Handle the error here
/// });
/// ```
///
/// \param executor The optional executor which is used to dispatch
/// the callback. See the description in `then` above.
///
/// \returns Returns a continuable_base with an asynchronous return type
/// depending on the previous result type.
///
/// \attention The given exception type exception_t can be passed to the
/// handler in a default constructed state <br>`bool(e) == false`.
/// This always means that the operation was cancelled by the user,
/// possibly through:
/// - \ref promise_base::set_canceled
/// - \ref make_cancelling_continuable
/// - \ref result::set_canceled
/// - \ref cancel<br>
/// In that case the exception can be ignored safely (but it is
/// recommended not to proceed, although it is possible to
/// recover from the cancellation).
///
/// \since 2.0.0
template <typename T, typename E = detail::types::this_thread_executor_tag>
auto fail(T&& callback,
E&& executor = detail::types::this_thread_executor_tag{}) && {
return detail::base::chain_continuation<
detail::base::handle_results::no, detail::base::handle_errors::forward>(
std::move(*this).finish(),
detail::base::strip_exception_arg(std::forward<T>(callback)),
std::forward<E>(executor));
}
/// Additional overload of the continuable_base::fail() method
/// which is accepting a continuable_base itself.
///
/// \param continuation A continuable_base reflecting the continuation
/// which is used to continue the call hierarchy on errors.
/// The result of the current continuable is discarded and the given
/// continuation is invoked as shown below.
/// ```cpp
/// http_request("github.com")
/// .fail(http_request("atom.io"))
/// ```
///
/// \returns Returns a continuable_base with an asynchronous return type
/// depending on the previous result type.
///
/// \since 2.0.0
template <typename OData, typename OAnnotation>
auto fail(continuable_base<OData, OAnnotation>&& continuation) && {
return std::move(*this) //
.fail([continuation = std::move(continuation).freeze()] //
(exception_t) mutable {
std::move(continuation).done(); //
});
}
/// A method which allows to use an overloaded callable for the error
/// as well as the valid result path.
///
/// \param callback The callback which is used to process the current
/// asynchronous result and error on arrival.
///
/// ```cpp
/// struct my_callable {
/// void operator() (std::string result) {
/// // ...
/// }
/// void operator() (cti::exception_arg_t, cti::exception_t) {
/// // ...
/// }
///
/// // Will receive errors and results
/// http_request("github.com")
/// .next(my_callable{});
/// ```
///
/// \param executor The optional executor which is used to dispatch
/// the callback. See the description in `then` above.
///
/// \returns Returns a continuable_base with an asynchronous return type
/// depending on the current result type.
///
/// \since 2.0.0
template <typename T, typename E = detail::types::this_thread_executor_tag>
auto next(T&& callback,
E&& executor = detail::types::this_thread_executor_tag{}) && {
return detail::base::chain_continuation<
detail::base::handle_results::yes,
detail::base::handle_errors::forward>(std::move(*this).finish(),
std::forward<T>(callback),
std::forward<E>(executor));
}
/// Returns a continuable_base which continues its invocation through the
/// given executor.
///
/// \returns Returns a continuable_base of the same type.
///
/// \since 4.2.0
template <typename E>
auto via(E&& executor) && {
return std::move(*this).next(
[](auto&&... args) {
return make_result(std::forward<decltype(args)>(args)...);
},
std::forward<E>(executor));
}
/// Returns a continuable_base which will have its signature converted
/// to the given Args.
///
/// A signature can only be converted if it can be partially applied
/// from the previous one as shown below:
/// ```cpp
/// continuable<long> c = make_ready_continuable(0, 1, 2).as<long>();
/// ```
///
/// \returns Returns a continuable_base with an asynchronous return type
/// matching the given Args.
///
/// \since 4.0.0
template <typename... Args>
auto as() && {
return std::move(*this).then(detail::base::convert_to<Args...>{});
}
/// A method which allows to apply a callable object to this continuable.
///
/// \param transform A callable objects that transforms a continuable
/// to a different object.
///
/// \returns Returns the result of the given transform when this
/// continuable is passed into it.
///
/// \since 4.0.0
template <typename T>
auto apply(T&& transform) && {
return std::forward<T>(transform)(std::move(*this).finish());
}
/// The pipe operator | is an alias for the continuable::then method.
///
/// \param right The argument on the right-hand side to connect.
///
/// \returns See the corresponding continuable_base::then method for the
/// explanation of the return type.
///
/// \since 2.0.0
template <typename T>
auto operator|(T&& right) && {
return std::move(*this).then(std::forward<T>(right));
}
/// Invokes both continuable_base objects parallel and calls the
/// callback with the result of both continuable_base objects.
///
/// \param right The continuable on the right-hand side to connect.
///
/// \returns Returns a continuable_base with a result type matching
/// the result of the left continuable_base combined with the
/// right continuable_base.
/// The returned continuable_base will be in an intermediate lazy
/// state, further calls to its continuable_base::operator &&
/// will add other continuable_base objects to the current
/// invocation chain.
/// ```cpp
/// (http_request("github.com") && http_request("atom.io"))
/// .then([](std::string github, std::string atom) {
/// // ...
/// });
///
/// auto request = http_request("github.com") && http_request("atom.io");
/// (std::move(request) && http_request("travis-ci.org"))
/// // All three requests are invoked in parallel although we added
/// // the request to "travis-ci.org" last.
/// .then([](std::string github, std::string atom, std::string travis) {
/// // ...
/// });
/// ```
///
/// \note The continuable_base objects are invoked all at onve,
/// because the `all` strategy tries to resolve
/// the continuations as fast as possible.
/// Sequential invocation is also supported through the
/// continuable_base::operator>> method.
///
/// \since 1.0.0
template <typename OData, typename OAnnotation>
auto operator&&(continuable_base<OData, OAnnotation>&& right) && {
return detail::connection::connect(
detail::connection::connection_strategy_all_tag{}, std::move(*this),
std::move(right));
}
/// Invokes both continuable_base objects parallel and calls the
/// callback once with the first result available.
///
/// \param right The continuable on the right-hand side to connect.
/// The right continuable is required to have the same
/// result as the left connected continuable_base.
///
/// \returns Returns a continuable_base with a result type matching
/// the combined result which of all connected
/// continuable_base objects.
/// The returned continuable_base will be in an intermediate lazy
/// state, further calls to its continuable_base::operator ||
/// will add other continuable_base objects to the current
/// invocation chain.
/// ```cpp
/// (http_request("github.com") || http_request("atom.io"))
/// .then([](std::string github_or_atom) {
/// // ...
/// });
///
/// (make_ready_continuable(10, 'A') || make_ready_continuable(29, 'B'))
/// .then([](int a, char b) {
/// // ...
/// });
/// ```
///
/// \note The continuable_base objects are invoked all at once,
/// however, the callback is only called once with
/// the first result or exception which becomes available.
///
/// \since 1.0.0
template <typename OData, typename OAnnotation>
auto operator||(continuable_base<OData, OAnnotation>&& right) && {
return detail::connection::connect(
detail::connection::connection_strategy_any_tag{}, std::move(*this),
std::move(right));
}
/// Invokes both continuable_base objects sequential and calls the
/// callback with the result of both continuable_base objects.
///
/// \param right The continuable on the right-hand side to connect.
///
/// \returns Returns a continuable_base with a result type matching
/// the result of the left continuable_base combined with the
/// right continuable_base.
/// ```cpp
/// (http_request("github.com") >> http_request("atom.io"))
/// .then([](std::string github, std::string atom) {
/// // The callback is called with the result of both requests,
/// // however, the request to atom was started after the request
/// // to github was finished.
/// });
/// ```
///
/// \note The continuable_base objects are invoked sequential one after
/// the previous one was finished. Parallel invocation is also
/// supported through the continuable_base::operator && method.
///
/// \since 1.0.0
template <typename OData, typename OAnnotation>
auto operator>>(continuable_base<OData, OAnnotation>&& right) && {
return detail::connection::seq::sequential_connect(std::move(*this),
std::move(right));
}
/// Invokes the continuation chain manually even before the
/// cti::continuable_base is destructed. This will release the object.
///
/// \see continuable_base::~continuable_base() for further details about
/// the continuation invocation on destruction.
///
/// \attention This method will trigger an assertion if the
/// continuable_base was released already.
///
/// \since 1.0.0
void done() && {
detail::base::finalize_continuation(std::move(*this).finish());
}
/// Materializes the continuation expression template and finishes
/// the current applied strategy such that the resulting continuable
/// will always be a concrete type and Continuable::is_concrete holds.
///
/// This can be used in the case where we are chaining continuations lazily
/// through a strategy, for instance when applying operators for
/// expressing connections and then want to return a materialized
/// continuable_base which uses the strategy respectively.
/// ```cpp
/// auto do_both() {
/// return (wait(10s) || wait_key_pressed(KEY_SPACE)).finish();
/// }
///
/// // Without a call to finish() this would lead to
/// // an unintended evaluation strategy:
/// do_both() || wait(5s);
/// ```
///
/// \note When using a type erased continuable_base such as
/// `continuable<...>` this method doesn't need to be called
/// since the continuable_base is materialized automatically
/// on conversion.
///
/// \since 4.0.0
auto finish() && {
return annotation_trait::finish(std::move(*this));
}
/// Returns true when the continuable can provide its result immediately,
/// and its lazy invocation would be side-effect free.
///
/// \since 4.0.0
bool is_ready() const noexcept {
return annotation_trait::is_ready(*this);
}
/// Invalidates the continuable and returns its immediate invocation result.
///
/// This method can be used to specialize the asynchronous control flow
/// based on whether the continuable_base is_ready at every time,
/// which is true for a continuable created through the following functions:
/// - make_ready_continuable
/// - make_exceptional_continuable
///
/// \returns A result<Args...> where Args... represent the current
/// asynchronous parameters or the currently stored exception.
///
/// \attention unpack requires that continuable_base::is_ready returned true
/// in a previous check, otherwise its behaviour is unspecified.
///
/// \since 4.0.0
auto unpack() && {
assert(ownership_.is_acquired());
assert(is_ready());
return detail::base::attorney::query(std::move(*this).finish());
}
/// Predicate to check whether the cti::continuable_base is frozen or not.
///
/// \returns Returns true when the continuable_base is frozen.
///
/// \see continuable_base::freeze for further details.
///
/// \attention This method will trigger an assertion if the
/// continuable_base was released already.
///
/// \since 1.0.0
bool is_frozen() const noexcept {
assert_acquired();
return ownership_.is_frozen();
}
/// Prevents the automatic invocation of the continuation chain
/// which happens on destruction of the continuable_base.
/// You may still invoke the chain through the continuable_base::done method.
///
/// This is useful for storing a continuable_base inside a continuation
/// chain while storing it for further usage.
///
/// \param enabled Indicates whether the freeze is enabled or disabled.
///
/// \see continuable_base::~continuable_base() for further details about
/// the continuation invocation on destruction.
///
/// \attention This method will trigger an assertion if the
/// continuable_base was released already.
///
/// \since 1.0.0
continuable_base& freeze(bool enabled = true) & noexcept {
ownership_.freeze(enabled);
return *this;
}
/// \copydoc continuable_base::freeze
continuable_base&& freeze(bool enabled = true) && noexcept {
ownership_.freeze(enabled);
return std::move(*this);
}
/// \cond false
#if defined(CONTINUABLE_HAS_COROUTINE)
/// \endcond
/// Implements the operator for awaiting on continuables using `co_await`.
///
/// The operator is only enabled if `CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE`
/// is defined and the toolchain supports experimental coroutines.
///
/// The return type of the `co_await` expression is specified as following:
/// | Continuation type | co_await returns |
/// | : ------------------------------- | : -------------------------------- |
/// | `continuable_base with <>` | `void` |
/// | `continuable_base with <Arg>` | `Arg` |
/// | `continuable_base with <Args...>` | `std::tuple<Args...>` |
///
/// When exceptions are used the usage is as intuitive as shown below:
/// ```cpp
/// // Handling the exception isn't required and
/// // the try catch clause may be omitted.
/// try {
/// std::string response = co_await http_request("github.com");
/// } (std::exception& e) {
/// e.what();
/// }
/// ```
///
/// In case the library is configured to use error codes or a custom
/// exception type the return type of the co_await expression is changed.
/// The result is returned through a cti::result<...>.
/// | Continuation type | co_await returns |
/// | : ------------------------------- | : -------------------------------- |
/// | `continuable_base with <>` | `result<void>` |
/// | `continuable_base with <Arg>` | `result<Arg>` |
/// | `continuable_base with <Args...>` | `result<Args...>` |
///
/// \note Using continuable_base as return type for coroutines
/// is supported. The coroutine is initially stopped and
/// resumed when the continuation is requested in order to
/// keep the lazy evaluation semantics of the continuable_base.
/// ```cpp
/// cti::continuable<> resolve_async_void() {
/// co_await http_request("github.com");
/// // ...
/// co_return;
/// }
///
/// cti::continuable<int> resolve_async() {
/// co_await http_request("github.com");
/// // ...
/// co_return 0;
/// }
/// ```
/// It's possible to return multiple return values from coroutines
/// by wrapping those in a tuple like type:
/// ```cpp
/// cti::continuable<int, int, int> resolve_async_multiple() {
/// co_await http_request("github.com");
/// // ...
/// co_return std::make_tuple(0, 1, 2);
/// }
/// ```
///
/// \since 2.0.0
auto operator co_await() && {
return detail::awaiting::create_awaiter(std::move(*this).finish());
}
/// \cond false
#endif // defined(CONTINUABLE_HAS_COROUTINE)
/// \endcond
private:
void release() noexcept {
ownership_.release();
}
Data&& consume() && {
assert_acquired();
release();
return std::move(data_);
}
void assert_acquired() const {
assert(ownership_.is_acquired() && "Tried to use a released continuable!");
}
};
/// Creates a continuable_base from a promise/callback taking function.
///
/// \tparam Args The types (signature hint) the given promise is resolved with.
/// * **Some arguments** indicate the types the promise will be invoked with.
/// ```cpp
/// auto ct = cti::make_continuable<int, std::string>([](auto&& promise) {
/// promise.set_value(200, "<html>...</html>");
/// });
/// ```
/// * `void` **as argument** indicates that the promise will be invoked
/// with no arguments:
/// ```cpp
/// auto ct = cti::make_continuable<void>([](auto&& promise) {
/// promise.set_value();
/// });
/// ```
/// * **No arguments** Since version 3.0.0 make_continuable always requires
/// to be given valid arguments!
/// You should always give the type hint a callback is called with because
/// it's required for intermediate actions like connecting continuables.
/// You may omit the signature hint if you are erasing the type of
/// the continuable right after creation.
/// ```cpp
/// // This won't work because the arguments are missing:
/// auto ct = cti::make_continuable([](auto&& promise) {
/// promise.set_value(0.f, 'c');
/// });
///
/// // However, you are allowed to do this:
/// cti::continuable<float, char> ct = [](auto&& promise) {
/// promise.set_value(callback)(0.f, 'c');
/// };
/// ```
///
/// \param continuation The continuation the continuable is created from.
/// The continuation must be a callable type accepting a callback parameter
/// which represents the object invokable with the asynchronous result of this
/// continuable.
/// ```cpp
/// auto ct = cti::make_continuable<std::string>([](auto&& promise) {
/// promise.set_value("result");
/// });
/// ```
/// The callback may be stored or moved.
/// In some cases the callback may be copied if supported by the underlying
/// callback chain, in order to invoke the call chain multiple times.
/// It's recommended to accept any callback instead of erasing it.
/// ```cpp
/// // Good practice:
/// auto ct = cti::make_continuable<std::string>([](auto&& promise) {
/// promise.set_value("result");
/// });
///
/// // Good practice using a callable object:
/// struct Continuation {
/// template<typename T>
/// void operator() (T&& continuation) && {
/// // ...
/// }
/// }
///
/// auto ct = cti::make_continuable<std::string>(Continuation{});
///
/// // Bad practice (because of unnecessary type erasure):
/// auto ct = cti::make_continuable<std::string>(
/// [](cti::promise<std::string> promise) {
/// promise.set_value("result");
/// });
/// ```
///
/// \returns A continuable_base with unspecified template parameters which
/// wraps the given continuation.
/// In order to convert the continuable_base to a known type
/// you need to apply type erasure through the
/// \link cti::continuable continuable\endlink or
/// \link cti::promise promise\endlink facilities.
///
/// \note You should always turn the callback/promise into a r-value if possible
/// (`std::move` or `std::forward`) for qualifier correct invokation.
/// Additionally it's important to know that all continuable promises
/// are callbacks and just expose their call operator nicely through
/// \link cti::promise_base::set_value set_value \endlink and
/// \link cti::promise_base::set_exception set_exception \endlink.
///
/// \since 1.0.0
template <typename... Args, typename Continuation>
constexpr auto make_continuable(Continuation&& continuation) {
static_assert(sizeof...(Args) > 0,
"Since version 3.0.0 make_continuable requires an exact "
"signature! If you did intend to create a void continuable "
"use make_continuable<void>(...). Continuables with an exact "
"signature may be created through make_continuable<Args...>.");
return detail::base::attorney::create_from(
std::forward<Continuation>(continuation),
typename detail::hints::from_args<Args...>::type{},
detail::util::ownership{});
}
/// Returns a continuable_base with no result which instantly resolves
/// the promise with no values.
///
/// \attention Usually using this function isn't needed at all since
/// the continuable library is capable of working with
/// plain values in most cases.
/// Try not to use it since it causes unnecessary recursive
/// function calls.
///
/// \since 3.0.0
template <typename... Args>
auto make_ready_continuable(Args&&... args) {
return detail::base::attorney::create_from_raw(
detail::base::ready_continuation<detail::traits::unrefcv_t<Args>...>(
result<detail::traits::unrefcv_t<Args>...>::from(
std::forward<Args>(args)...)),
detail::identity<detail::traits::unrefcv_t<Args>...>{},
detail::util::ownership{});
}
/// Returns a continuable_base with the parameterized result which instantly
/// resolves the promise with the given error type.
///
/// See an example below:
/// ```cpp
/// std::logic_error exception("Some issue!");
/// auto ptr = std::make_exception_ptr(exception);
/// auto ct = cti::make_exceptional_continuable<int>(ptr);
/// ```
///
/// \tparam Args The fake signature of the returned continuable.
///
/// \since 3.0.0
template <typename... Args, typename Exception>
constexpr auto make_exceptional_continuable(Exception&& exception) {
static_assert(sizeof...(Args) > 0,
"Requires at least one type for the fake signature!");
using hint_t = typename detail::hints::from_args<Args...>::type;
using ready_continuation_t = typename detail::base::
ready_continuation_from_hint<hint_t>::type;
using result_t = typename detail::base::result_from_hint<hint_t>::type;
return detail::base::attorney::create_from_raw(
ready_continuation_t(result_t::from(exception_arg_t{},
std::forward<Exception>(exception))),
hint_t{}, detail::util::ownership{});
}
/// Returns a continuable_base with the parameterized result which never
/// resolves its promise and thus cancels the asynchronous continuation chain
/// through throwing a default constructed exception_t.
///
/// This can be used to cancel an asynchronous continuation chain when
/// returning a continuable_base from a handler where other paths could
/// possibly continue the asynchronous chain. See an example below:
/// ```cpp
/// do_sth().then([weak = this->weak_from_this()]() -> continuable<> {
/// if (auto me = weak.lock()) {
/// return do_sth_more();
/// } else {
/// // Abort the asynchronous continuation chain since the
/// // weakly referenced object expired previously.
/// return make_cancelling_continuable<void>();
/// }
/// });
/// ```
/// The default unhandled exception handler ignores exception types
/// that don't evaluate to true when being converted to a bool.
/// This saves expensive construction of std::exception_ptr or similar types,
/// where only one exception type is used for signaling the cancellation.
///
/// \tparam Signature The fake signature of the returned continuable.
///
/// \since 4.0.0
template <typename... Signature>
auto make_cancelling_continuable() {
static_assert(sizeof...(Signature) > 0,
"Requires at least one type for the fake signature!");
return make_exceptional_continuable<Signature...>(exception_t{});
}
/// Can be used to disable the special meaning for a returned value in
/// asynchronous handler functions.
///
/// Several types have a special meaning when being returned from a callable
/// passed to asynchronous handler functions like:
/// - continuable_base::then
/// - continuable_base::fail
/// - continuable_base::next
///
/// For instance such types are std::tuple, std::pair and cti::result.
///
/// Wrapping such an object through a call to make_plain disables the special
/// meaning for such objects as shown below:
/// ```cpp
/// continuable<result<int, int> c = http_request("example.com")
/// .then([](std::string content) {
/// return make_plain(make_result(0, 1));
/// })
/// ```
///
/// \since 4.0.0
///
template <typename T>
auto make_plain(T&& value) {
return plain_t<detail::traits::unrefcv_t<T>>(std::forward<T>(value));
}
/// Can be used to recover to from a failure handler,
/// the result handler which comes after will be called with the
/// corresponding result.
///
/// The \ref exceptional_result returned by this function can be returned
/// from any result or failure handler in order to rethrow the exception.
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) {
/// return recover(1, 2);
/// })
/// .fail([](cti::exception_t exception) {
/// return recover(1, 2);
/// })
/// .then([](int a, int b) {
/// // Recovered from the failure
/// })
/// ```
/// A corresponding \ref result is returned by \ref recover
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) -> cti::result<int, int> {
/// return recover(1, 2);
/// })
/// .fail([](cti::exception_t exception) -> cti::result<int, int> {
/// return recover(1, 2);
/// })
/// .then([](int a, int b) -> cti::result<int, int> {
/// // Recovered from the failure
/// })
/// ```
///
/// \since 4.0.0
///
template <typename... Args>
result<detail::traits::unrefcv_t<Args>...> recover(Args&&... args) {
return make_result(std::forward<Args>(args)...);
}
/// Can be used to rethrow an exception to the asynchronous continuation chain,
/// the failure handler which comes after will be called with the
/// corresponding exception.
///
/// The \ref exceptional_result returned by this function can be returned
/// from any result or failure handler in order to rethrow the exception.
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// })
/// .fail([](cti::exception_t exception) {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// })
/// .next([](auto&&...) {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// });
/// ```
/// The returned \ref exceptional_result is convertible to
/// any \ref result as shown below:
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) -> cti::result<> {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// })
/// .fail([](cti::exception_t exception) -> cti::result<> {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// })
/// .next([](auto&&...) -> cti::result<> {
/// return rethrow(std::make_exception_ptr(std::exception{}));
/// });
/// ```
///
/// \since 4.0.0
///
// NOLINTNEXTLINE(performance-unnecessary-value-param)
inline exceptional_result rethrow(exception_t exception) {
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
return exceptional_result{std::move(exception)};
}
/// Can be used to cancel an asynchronous continuation chain,
/// the next failure handler which comes after cancel will be called
/// with a default constructed exception_t object.
///
/// The \ref cancellation_result returned by this function can be returned from
/// any result or failure handler in order to cancel the chain.
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) {
/// return cancel();
/// })
/// .fail([](cti::exception_t exception) {
/// return cancel();
/// })
/// .next([](auto&&...) {
/// return cancel();
/// });
/// ```
/// The returned \ref empty_result is convertible to
/// any \ref result as shown below:
/// ```cpp
/// http_request("example.com")
/// .then([](std::string content) -> cti::result<> {
/// return cancel();
/// })
/// .fail([](cti::exception_t exception) -> cti::result<> {
/// return cancel();
/// })
/// .next([](auto&&...) -> cti::result<> {
/// return cancel();
/// });
/// ```
///
/// \since 4.0.0
///
inline cancellation_result cancel() {
return {};
}
/// Can be used to stop an asynchronous continuation chain,
/// no handler which comes after stop was received won't be called.
///
/// \since 4.0.0
///
inline empty_result stop() {
return {};
}
/// \}
} // namespace cti
#endif // CONTINUABLE_BASE_HPP_INCLUDED
// #include <continuable/continuable-connections.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_CONNECTIONS_HPP_INCLUDED
#define CONTINUABLE_CONNECTIONS_HPP_INCLUDED
#include <initializer_list>
#include <memory>
#include <utility>
#include <vector>
// #include <continuable/detail/connection/connection-all.hpp>
// #include <continuable/detail/connection/connection-any.hpp>
// #include <continuable/detail/connection/connection-seq.hpp>
// #include <continuable/detail/connection/connection.hpp>
// #include <continuable/detail/traversal/range.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_RANGE_HPP_INCLUDED
#define CONTINUABLE_DETAIL_RANGE_HPP_INCLUDED
#include <iterator>
#include <type_traits>
#include <utility>
#include <vector>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace range {
/// Deduces to a true_type if the given type is an interator
template <typename T, typename = void>
struct is_iterator : std::false_type {};
template <typename T>
struct is_iterator<T,
traits::void_t<typename std::iterator_traits<T>::value_type>>
: std::true_type {};
/// Moves the content of the given iterators to a persistent storage
template <typename Iterator>
auto persist_range(Iterator begin, Iterator end) {
std::vector<typename std::iterator_traits<Iterator>::value_type> storage;
// TODO Find out why the superior idiom below has issues with move only types:
// storage.insert(storage.end(), std::make_move_iterator(begin),
// std::make_move_iterator(end));
std::move(begin, end, std::back_inserter(storage));
return storage;
}
} // namespace range
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_RANGE_HPP_INCLUDED
namespace cti {
/// \defgroup Connections Connections
/// provides functions to connect \link continuable_base
/// continuable_bases\endlink through various strategies.
/// \{
/// Connects the given arguments with an all logic.
/// All continuables contained inside the given nested pack are
/// invoked at once. On completion the final handler is called
/// with the aggregated result of all continuables.
///
/// \param args Arbitrary arguments which are connected.
/// Every type is allowed as arguments, continuables may be
/// contained inside tuple like types (`std::tuple`)
/// or in homogeneous containers such as `std::vector`.
/// Non continuable arguments are preserved and passed
/// to the final result as shown below:
/// ```cpp
/// cti::when_all(
/// cti::make_ready_continuable(0, 1),
/// 2, //< See this plain value
/// cti::populate(cti::make_ready_continuable(3), // Creates a runtime
/// cti::make_ready_continuable(4)), // sized container.
/// std::make_tuple(std::make_tuple(cti::make_ready_continuable(5))))
/// .then([](int r0, int r1, int r2, std::vector<int> r34,
/// std::tuple<std::tuple<int>> r5) {
/// // ...
/// });
/// ```
///
/// \see continuable_base::operator&& for details.
///
/// \since 1.1.0
template <typename... Args>
auto when_all(Args&&... args) {
return detail::connection::apply_connection(
detail::connection::connection_strategy_all_tag{},
std::forward<Args>(args)...);
}
/// Connects the given arguments with an all logic.
/// The content of the iterator is moved out and converted
/// to a temporary `std::vector` which is then passed to when_all.
///
/// ```cpp
/// // cti::populate just creates a std::vector from the two continuables.
/// auto v = cti::populate(cti::make_ready_continuable(0),
/// cti::make_ready_continuable(1));
///
/// cti::when_all(v.begin(), v.end())
/// .then([](std::vector<int> r01) {
/// // ...
/// });
/// ```
///
/// \param begin The begin iterator to the range which will be moved out
/// and used as the arguments to the all connection
///
/// \param end The end iterator to the range which will be moved out
/// and used as the arguments to the all connection
///
/// \see when_all for details.
///
/// \attention Prefer to invoke when_all with the whole container the
/// iterators were taken from, since this saves us
/// the creation of a temporary storage.
///
/// \since 3.0.0
template <
typename Iterator,
std::enable_if_t<detail::range::is_iterator<Iterator>::value>* = nullptr>
auto when_all(Iterator begin, Iterator end) {
return when_all(detail::range::persist_range(begin, end));
}
/// Connects the given arguments with a sequential logic.
/// All continuables contained inside the given nested pack are
/// invoked one after one. On completion the final handler is called
/// with the aggregated result of all continuables.
///
/// \param args Arbitrary arguments which are connected.
/// Every type is allowed as arguments, continuables may be
/// contained inside tuple like types (`std::tuple`)
/// or in homogeneous containers such as `std::vector`.
/// Non continuable arguments are preserved and passed
/// to the final result as shown below:
/// ```cpp
/// cti::when_seq(
/// cti::make_ready_continuable(0, 1),
/// 2, //< See this plain value
/// cti::populate(cti::make_ready_continuable(3), // Creates a runtime
/// cti::make_ready_continuable(4)), // sized container.
/// std::make_tuple(std::make_tuple(cti::make_ready_continuable(5))))
/// .then([](int r0, int r1, int r2, std::vector<int> r34,
/// std::tuple<std::tuple<int>> r5) {
/// // ...
/// });
/// ```
///
/// \see continuable_base::operator>> for details.
///
/// \since 1.1.0
template <typename... Args>
auto when_seq(Args&&... args) {
return detail::connection::apply_connection(
detail::connection::connection_strategy_seq_tag{},
std::forward<Args>(args)...);
}
/// Connects the given arguments with a sequential logic.
/// The content of the iterator is moved out and converted
/// to a temporary `std::vector` which is then passed to when_seq.
///
/// ```cpp
/// // cti::populate just creates a std::vector from the two continuables.
/// auto v = cti::populate(cti::make_ready_continuable(0),
/// cti::make_ready_continuable(1));
///
/// cti::when_seq(v.begin(), v.end())
/// .then([](std::vector<int> r01) {
/// // ...
/// });
/// ```
///
/// \param begin The begin iterator to the range which will be moved out
/// and used as the arguments to the sequential connection
///
/// \param end The end iterator to the range which will be moved out
/// and used as the arguments to the sequential connection
///
/// \see when_seq for details.
///
/// \attention Prefer to invoke when_seq with the whole container the
/// iterators were taken from, since this saves us
/// the creation of a temporary storage.
///
/// \since 3.0.0
template <
typename Iterator,
std::enable_if_t<detail::range::is_iterator<Iterator>::value>* = nullptr>
auto when_seq(Iterator begin, Iterator end) {
return when_seq(detail::range::persist_range(begin, end));
}
/// Connects the given arguments with an any logic.
/// All continuables contained inside the given nested pack are
/// invoked at once. On completion of one continuable the final handler
/// is called with the result of the resolved continuable.
///
/// \param args Arbitrary arguments which are connected.
/// Every type is allowed as arguments, continuables may be
/// contained inside tuple like types (`std::tuple`)
/// or in homogeneous containers such as `std::vector`.
/// Non continuable arguments are preserved and passed
/// to the final result as shown below:
/// ```cpp
/// cti::when_any(
/// cti::make_ready_continuable(0, 1),
/// 2, //< See this plain value
/// cti::populate(cti::make_ready_continuable(3), // Creates a runtime
/// cti::make_ready_continuable(4)), // sized container.
/// std::make_tuple(std::make_tuple(cti::make_ready_continuable(5))))
/// .then([](int r0) {
/// // ...
/// });
/// ```
///
/// \see continuable_base::operator|| for details.
///
/// \since 1.1.0
template <typename... Args>
auto when_any(Args&&... args) {
return detail::connection::apply_connection(
detail::connection::connection_strategy_any_tag{},
std::forward<Args>(args)...);
}
/// Connects the given arguments with an any logic.
/// The content of the iterator is moved out and converted
/// to a temporary `std::vector` which is then passed to when_all.
///
/// ```cpp
/// // cti::populate just creates a std::vector from the two continuables.
/// auto v = cti::populate(cti::make_ready_continuable(0),
/// cti::make_ready_continuable(1));
///
/// cti::when_any(v.begin(), v.end())
/// .then([](int r01) {
/// // ...
/// });
/// ```
///
/// \param begin The begin iterator to the range which will be moved out
/// and used as the arguments to the all connection
///
/// \param end The end iterator to the range which will be moved out
/// and used as the arguments to the all connection
///
/// \see when_any for details.
///
/// \attention Prefer to invoke when_any with the whole container the
/// iterators were taken from, since this saves us
/// the creation of a temporary storage.
///
/// \since 3.0.0
template <
typename Iterator,
std::enable_if_t<detail::range::is_iterator<Iterator>::value>* = nullptr>
auto when_any(Iterator begin, Iterator end) {
return when_any(detail::range::persist_range(begin, end));
}
/// Populates a homogeneous container from the given arguments.
/// All arguments need to be convertible to the first one,
/// by default `std::vector` is used as container type.
///
/// This method mainly helps to create a homogeneous container from
/// a runtime known count of continuables which type isn't exactly known.
/// All continuables which are passed to this function should be originating
/// from the same source or a method called with the same types of arguments:
/// ```cpp
/// auto container = cti::populate(cti::make_ready_continuable(0),
/// cti::make_ready_continuable(1)),
///
/// for (int i = 2; i < 5; ++i) {
/// // You may add more continuables to the container afterwards
/// container.emplace_back(cti::make_ready_continuable(i));
/// }
///
/// cti::when_any(std::move(container))
/// .then([](int) {
/// // ...
/// });
/// ```
/// Additionally it is possible to change the targeted container as below:
/// ```cpp
/// auto container = cti::populate<std::list>(cti::make_ready_continuable(0),
/// cti::make_ready_continuable(1)),
/// ```
///
/// \tparam C The container type which is used to store the arguments into.
///
/// \since 3.0.0
template <template <typename, typename> class C = std::vector, typename First,
typename... Args>
C<std::decay_t<First>, std::allocator<std::decay_t<First>>>
populate(First&& first, Args&&... args) {
C<std::decay_t<First>, std::allocator<std::decay_t<First>>> container;
container.reserve(1 + sizeof...(Args));
container.emplace_back(std::forward<First>(first));
(void)std::initializer_list<int>{
0, ((void)container.emplace_back(std::forward<Args>(args)), 0)...};
return container; // RVO
}
/// \}
} // namespace cti
#endif // CONTINUABLE_CONNECTIONS_HPP_INCLUDED
// #include <continuable/continuable-coroutine.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_COROUTINE_HPP_INCLUDED
#define CONTINUABLE_COROUTINE_HPP_INCLUDED
// #include <continuable/continuable-base.hpp>
// #include <continuable/continuable-types.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TYPES_HPP_INCLUDED
#define CONTINUABLE_TYPES_HPP_INCLUDED
// #include <function2/function2.hpp>
// Copyright 2015-2020 Denis Blank <denis.blank at outlook dot com>
// Distributed under the Boost Software License, Version 1.0
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#ifndef FU2_INCLUDED_FUNCTION2_HPP_
#define FU2_INCLUDED_FUNCTION2_HPP_
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
// Defines:
// - FU2_HAS_DISABLED_EXCEPTIONS
#if defined(FU2_WITH_DISABLED_EXCEPTIONS) || \
defined(FU2_MACRO_DISABLE_EXCEPTIONS)
#define FU2_HAS_DISABLED_EXCEPTIONS
#else // FU2_WITH_DISABLED_EXCEPTIONS
#if defined(_MSC_VER)
#if !defined(_HAS_EXCEPTIONS) || (_HAS_EXCEPTIONS == 0)
#define FU2_HAS_DISABLED_EXCEPTIONS
#endif
#elif defined(__clang__)
#if !(__EXCEPTIONS && __has_feature(cxx_exceptions))
#define FU2_HAS_DISABLED_EXCEPTIONS
#endif
#elif defined(__GNUC__)
#if !__EXCEPTIONS
#define FU2_HAS_DISABLED_EXCEPTIONS
#endif
#endif
#endif // FU2_WITH_DISABLED_EXCEPTIONS
// - FU2_HAS_NO_FUNCTIONAL_HEADER
#if !defined(FU2_WITH_NO_FUNCTIONAL_HEADER) && \
!defined(FU2_NO_FUNCTIONAL_HEADER) && \
!defined(FU2_HAS_DISABLED_EXCEPTIONS)
#include <functional>
#else
#define FU2_HAS_NO_FUNCTIONAL_HEADER
#endif
// - FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#if defined(FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE)
#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#else // FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE
#if defined(_MSC_VER)
#if defined(_HAS_CXX17) && _HAS_CXX17
#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#endif
#elif defined(__cpp_noexcept_function_type)
#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#elif defined(__cplusplus) && (__cplusplus >= 201703L)
#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#endif
#endif // FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE
// - FU2_HAS_NO_EMPTY_PROPAGATION
#if defined(FU2_WITH_NO_EMPTY_PROPAGATION)
#define FU2_HAS_NO_EMPTY_PROPAGATION
#endif // FU2_WITH_NO_EMPTY_PROPAGATION
#if !defined(FU2_HAS_DISABLED_EXCEPTIONS)
#include <exception>
#endif
/// Hint for the compiler that this point should be unreachable
#if defined(_MSC_VER)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __assume(false)
#elif defined(__GNUC__)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable()
#elif defined(__has_builtin) && __has_builtin(__builtin_unreachable)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE_INTRINSIC() abort()
#endif
/// Causes the application to exit abnormally
#if defined(_MSC_VER)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_TRAP() __debugbreak()
#elif defined(__GNUC__)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_TRAP() __builtin_trap()
#elif defined(__has_builtin) && __has_builtin(__builtin_trap)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_TRAP() __builtin_trap()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_TRAP() *(volatile int*)0x11 = 0
#endif
#ifndef NDEBUG
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE() ::fu2::detail::unreachable_debug()
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FU2_DETAIL_UNREACHABLE() FU2_DETAIL_UNREACHABLE_INTRINSIC()
#endif
namespace fu2 {
inline namespace abi_400 {
namespace detail {
template <typename Config, typename Property>
class function;
template <typename...>
struct identity {};
// Equivalent to C++17's std::void_t which targets a bug in GCC,
// that prevents correct SFINAE behavior.
// See http://stackoverflow.com/questions/35753920 for details.
template <typename...>
struct deduce_to_void : std::common_type<void> {};
template <typename... T>
using void_t = typename deduce_to_void<T...>::type;
template <typename T>
using unrefcv_t = std::remove_cv_t<std::remove_reference_t<T>>;
// Copy enabler helper class
template <bool /*Copyable*/>
struct copyable {};
template <>
struct copyable<false> {
copyable() = default;
~copyable() = default;
copyable(copyable const&) = delete;
copyable(copyable&&) = default;
copyable& operator=(copyable const&) = delete;
copyable& operator=(copyable&&) = default;
};
/// Configuration trait to configure the function_base class.
template <bool Owning, bool Copyable, typename Capacity>
struct config {
// Is true if the function is owning.
static constexpr auto const is_owning = Owning;
// Is true if the function is copyable.
static constexpr auto const is_copyable = Copyable;
// The internal capacity of the function
// used in small functor optimization.
// The object shall expose the real capacity through Capacity::capacity
// and the intended alignment through Capacity::alignment.
using capacity = Capacity;
};
/// A config which isn't compatible to other configs
template <bool Throws, bool HasStrongExceptGuarantee, typename... Args>
struct property {
// Is true when the function throws an exception on empty invocation.
static constexpr auto const is_throwing = Throws;
// Is true when the function throws an exception on empty invocation.
static constexpr auto const is_strong_exception_guaranteed =
HasStrongExceptGuarantee;
};
#ifndef NDEBUG
[[noreturn]] inline void unreachable_debug() {
FU2_DETAIL_TRAP();
std::abort();
}
#endif
/// Provides utilities for invocing callable objects
namespace invocation {
/// Invokes the given callable object with the given arguments
template <typename Callable, typename... Args>
constexpr auto invoke(Callable&& callable, Args&&... args) noexcept(
noexcept(std::forward<Callable>(callable)(std::forward<Args>(args)...)))
-> decltype(std::forward<Callable>(callable)(std::forward<Args>(args)...)) {
return std::forward<Callable>(callable)(std::forward<Args>(args)...);
}
/// Invokes the given member function pointer by reference
template <typename T, typename Type, typename Self, typename... Args>
constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
noexcept((std::forward<Self>(self).*member)(std::forward<Args>(args)...)))
-> decltype((std::forward<Self>(self).*
member)(std::forward<Args>(args)...)) {
return (std::forward<Self>(self).*member)(std::forward<Args>(args)...);
}
/// Invokes the given member function pointer by pointer
template <typename T, typename Type, typename Self, typename... Args>
constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
noexcept((std::forward<Self>(self)->*member)(std::forward<Args>(args)...)))
-> decltype(
(std::forward<Self>(self)->*member)(std::forward<Args>(args)...)) {
return (std::forward<Self>(self)->*member)(std::forward<Args>(args)...);
}
/// Invokes the given pointer to a scalar member by reference
template <typename T, typename Type, typename Self>
constexpr auto
invoke(Type T::*member,
Self&& self) noexcept(noexcept(std::forward<Self>(self).*member))
-> decltype(std::forward<Self>(self).*member) {
return (std::forward<Self>(self).*member);
}
/// Invokes the given pointer to a scalar member by pointer
template <typename T, typename Type, typename Self>
constexpr auto
invoke(Type T::*member,
Self&& self) noexcept(noexcept(std::forward<Self>(self)->*member))
-> decltype(std::forward<Self>(self)->*member) {
return std::forward<Self>(self)->*member;
}
/// Deduces to a true type if the callable object can be invoked with
/// the given arguments.
/// We don't use invoke here because MSVC can't evaluate the nested expression
/// SFINAE here.
template <typename T, typename Args, typename = void>
struct can_invoke : std::false_type {};
template <typename T, typename... Args>
struct can_invoke<T, identity<Args...>,
decltype((void)std::declval<T>()(std::declval<Args>()...))>
: std::true_type {};
template <typename Pointer, typename T, typename... Args>
struct can_invoke<Pointer, identity<T&, Args...>,
decltype((void)((std::declval<T&>().*std::declval<Pointer>())(
std::declval<Args>()...)))> : std::true_type {};
template <typename Pointer, typename T, typename... Args>
struct can_invoke<Pointer, identity<T&&, Args...>,
decltype(
(void)((std::declval<T&&>().*std::declval<Pointer>())(
std::declval<Args>()...)))> : std::true_type {};
template <typename Pointer, typename T, typename... Args>
struct can_invoke<Pointer, identity<T*, Args...>,
decltype(
(void)((std::declval<T*>()->*std::declval<Pointer>())(
std::declval<Args>()...)))> : std::true_type {};
template <typename Pointer, typename T>
struct can_invoke<Pointer, identity<T&>,
decltype((void)(std::declval<T&>().*std::declval<Pointer>()))>
: std::true_type {};
template <typename Pointer, typename T>
struct can_invoke<Pointer, identity<T&&>,
decltype(
(void)(std::declval<T&&>().*std::declval<Pointer>()))>
: std::true_type {};
template <typename Pointer, typename T>
struct can_invoke<Pointer, identity<T*>,
decltype(
(void)(std::declval<T*>()->*std::declval<Pointer>()))>
: std::true_type {};
template <bool RequiresNoexcept, typename T, typename Args>
struct is_noexcept_correct : std::true_type {};
template <typename T, typename... Args>
struct is_noexcept_correct<true, T, identity<Args...>>
: std::integral_constant<bool, noexcept(invoke(std::declval<T>(),
std::declval<Args>()...))> {
};
} // end namespace invocation
namespace overloading {
template <typename... Args>
struct overload_impl;
template <typename Current, typename Next, typename... Rest>
struct overload_impl<Current, Next, Rest...> : Current,
overload_impl<Next, Rest...> {
explicit overload_impl(Current current, Next next, Rest... rest)
: Current(std::move(current)), overload_impl<Next, Rest...>(
std::move(next), std::move(rest)...) {
}
using Current::operator();
using overload_impl<Next, Rest...>::operator();
};
template <typename Current>
struct overload_impl<Current> : Current {
explicit overload_impl(Current current) : Current(std::move(current)) {
}
using Current::operator();
};
template <typename... T>
constexpr auto overload(T&&... callables) {
return overload_impl<std::decay_t<T>...>{std::forward<T>(callables)...};
}
} // namespace overloading
/// Declares the namespace which provides the functionality to work with a
/// type-erased object.
namespace type_erasure {
/// Specialization to work with addresses of callable objects
template <typename T, typename = void>
struct address_taker {
template <typename O>
static void* take(O&& obj) {
return std::addressof(obj);
}
static T& restore(void* ptr) {
return *static_cast<T*>(ptr);
}
static T const& restore(void const* ptr) {
return *static_cast<T const*>(ptr);
}
static T volatile& restore(void volatile* ptr) {
return *static_cast<T volatile*>(ptr);
}
static T const volatile& restore(void const volatile* ptr) {
return *static_cast<T const volatile*>(ptr);
}
};
/// Specialization to work with addresses of raw function pointers
template <typename T>
struct address_taker<T, std::enable_if_t<std::is_pointer<T>::value>> {
template <typename O>
static void* take(O&& obj) {
return reinterpret_cast<void*>(obj);
}
template <typename O>
static T restore(O ptr) {
return reinterpret_cast<T>(const_cast<void*>(ptr));
}
};
template <typename Box>
struct box_factory;
/// Store the allocator inside the box
template <bool IsCopyable, typename T, typename Allocator>
struct box : private Allocator {
friend box_factory<box>;
T value_;
explicit box(T value, Allocator allocator)
: Allocator(std::move(allocator)), value_(std::move(value)) {
}
box(box&&) = default;
box(box const&) = default;
box& operator=(box&&) = default;
box& operator=(box const&) = default;
~box() = default;
};
template <typename T, typename Allocator>
struct box<false, T, Allocator> : private Allocator {
friend box_factory<box>;
T value_;
explicit box(T value, Allocator allocator)
: Allocator(std::move(allocator)), value_(std::move(value)) {
}
box(box&&) = default;
box(box const&) = delete;
box& operator=(box&&) = default;
box& operator=(box const&) = delete;
~box() = default;
};
template <bool IsCopyable, typename T, typename Allocator>
struct box_factory<box<IsCopyable, T, Allocator>> {
using real_allocator =
typename std::allocator_traits<std::decay_t<Allocator>>::
template rebind_alloc<box<IsCopyable, T, Allocator>>;
/// Allocates space through the boxed allocator
static box<IsCopyable, T, Allocator>*
box_allocate(box<IsCopyable, T, Allocator> const* me) {
real_allocator allocator(*static_cast<Allocator const*>(me));
return static_cast<box<IsCopyable, T, Allocator>*>(
std::allocator_traits<real_allocator>::allocate(allocator, 1U));
}
/// Destroys the box through the given allocator
static void box_deallocate(box<IsCopyable, T, Allocator>* me) {
real_allocator allocator(*static_cast<Allocator const*>(me));
me->~box();
std::allocator_traits<real_allocator>::deallocate(allocator, me, 1U);
}
};
/// Creates a box containing the given value and allocator
template <bool IsCopyable, typename T, typename Allocator>
auto make_box(std::integral_constant<bool, IsCopyable>, T&& value,
Allocator&& allocator) {
return box<IsCopyable, std::decay_t<T>, std::decay_t<Allocator>>(
std::forward<T>(value), std::forward<Allocator>(allocator));
}
template <typename T>
struct is_box : std::false_type {};
template <bool IsCopyable, typename T, typename Allocator>
struct is_box<box<IsCopyable, T, Allocator>> : std::true_type {};
/// Provides access to the pointer to a heal allocated erased object
/// as well to the inplace storage.
union data_accessor {
data_accessor() = default;
explicit constexpr data_accessor(std::nullptr_t) noexcept : ptr_(nullptr) {
}
explicit constexpr data_accessor(void* ptr) noexcept : ptr_(ptr) {
}
/// The pointer we use if the object is on the heap
void* ptr_;
/// The first field of the inplace storage
std::size_t inplace_storage_;
};
/// See opcode::op_fetch_empty
constexpr void write_empty(data_accessor* accessor, bool empty) noexcept {
accessor->inplace_storage_ = std::size_t(empty);
}
template <typename From, typename To>
using transfer_const_t =
std::conditional_t<std::is_const<std::remove_pointer_t<From>>::value,
std::add_const_t<To>, To>;
template <typename From, typename To>
using transfer_volatile_t =
std::conditional_t<std::is_volatile<std::remove_pointer_t<From>>::value,
std::add_volatile_t<To>, To>;
/// The retriever when the object is allocated inplace
template <typename T, typename Accessor>
constexpr auto retrieve(std::true_type /*is_inplace*/, Accessor from,
std::size_t from_capacity) {
using type = transfer_const_t<Accessor, transfer_volatile_t<Accessor, void>>*;
/// Process the command by using the data inside the internal capacity
auto storage = &(from->inplace_storage_);
auto inplace = const_cast<void*>(static_cast<type>(storage));
return type(std::align(alignof(T), sizeof(T), inplace, from_capacity));
}
/// The retriever which is used when the object is allocated
/// through the allocator
template <typename T, typename Accessor>
constexpr auto retrieve(std::false_type /*is_inplace*/, Accessor from,
std::size_t /*from_capacity*/) {
return from->ptr_;
}
namespace invocation_table {
#if !defined(FU2_HAS_DISABLED_EXCEPTIONS)
#if defined(FU2_HAS_NO_FUNCTIONAL_HEADER)
struct bad_function_call : std::exception {
bad_function_call() noexcept {
}
char const* what() const noexcept override {
return "bad function call";
}
};
#else
using std::bad_function_call;
#endif
#endif
#ifdef FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) \
F(, , noexcept, , &) \
F(const, , noexcept, , &) \
F(, volatile, noexcept, , &) \
F(const, volatile, noexcept, , &) \
F(, , noexcept, &, &) \
F(const, , noexcept, &, &) \
F(, volatile, noexcept, &, &) \
F(const, volatile, noexcept, &, &) \
F(, , noexcept, &&, &&) \
F(const, , noexcept, &&, &&) \
F(, volatile, noexcept, &&, &&) \
F(const, volatile, noexcept, &&, &&)
#define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F) \
F(, , noexcept) \
F(const, , noexcept) \
F(, volatile, noexcept) \
F(const, volatile, noexcept)
#else // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F)
#define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F)
#endif // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
#define FU2_DETAIL_EXPAND_QUALIFIERS(F) \
F(, , , , &) \
F(const, , , , &) \
F(, volatile, , , &) \
F(const, volatile, , , &) \
F(, , , &, &) \
F(const, , , &, &) \
F(, volatile, , &, &) \
F(const, volatile, , &, &) \
F(, , , &&, &&) \
F(const, , , &&, &&) \
F(, volatile, , &&, &&) \
F(const, volatile, , &&, &&) \
FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F)
#define FU2_DETAIL_EXPAND_CV(F) \
F(, , ) \
F(const, , ) \
F(, volatile, ) \
F(const, volatile, ) \
FU2_DETAIL_EXPAND_CV_NOEXCEPT(F)
/// If the function is qualified as noexcept, the call will never throw
template <bool IsNoexcept>
[[noreturn]] void throw_or_abortnoexcept(
std::integral_constant<bool, IsNoexcept> /*is_throwing*/) noexcept {
std::abort();
}
/// Calls std::abort on empty function calls
[[noreturn]] inline void
throw_or_abort(std::false_type /*is_throwing*/) noexcept {
std::abort();
}
/// Throws bad_function_call on empty funciton calls
[[noreturn]] inline void throw_or_abort(std::true_type /*is_throwing*/) {
#ifdef FU2_HAS_DISABLED_EXCEPTIONS
throw_or_abort(std::false_type{});
#else
throw bad_function_call{};
#endif
}
template <typename T>
struct function_trait;
using is_noexcept_ = std::false_type;
using is_noexcept_noexcept = std::true_type;
#define FU2_DEFINE_FUNCTION_TRAIT(CONST, VOLATILE, NOEXCEPT, OVL_REF, REF) \
template <typename Ret, typename... Args> \
struct function_trait<Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT> { \
using pointer_type = Ret (*)(data_accessor CONST VOLATILE*, \
std::size_t capacity, Args...); \
template <typename T, bool IsInplace> \
struct internal_invoker { \
static Ret invoke(data_accessor CONST VOLATILE* data, \
std::size_t capacity, Args... args) NOEXCEPT { \
auto obj = retrieve<T>(std::integral_constant<bool, IsInplace>{}, \
data, capacity); \
auto box = static_cast<T CONST VOLATILE*>(obj); \
return invocation::invoke( \
static_cast<std::decay_t<decltype(box->value_)> CONST VOLATILE \
REF>(box->value_), \
std::forward<Args>(args)...); \
} \
}; \
\
template <typename T> \
struct view_invoker { \
static Ret invoke(data_accessor CONST VOLATILE* data, std::size_t, \
Args... args) NOEXCEPT { \
\
auto ptr = static_cast<void CONST VOLATILE*>(data->ptr_); \
return invocation::invoke(address_taker<T>::restore(ptr), \
std::forward<Args>(args)...); \
} \
}; \
\
template <typename T> \
using callable = T CONST VOLATILE REF; \
\
using arguments = identity<Args...>; \
\
using is_noexcept = is_noexcept_##NOEXCEPT; \
\
template <bool Throws> \
struct empty_invoker { \
static Ret invoke(data_accessor CONST VOLATILE* /*data*/, \
std::size_t /*capacity*/, Args... /*args*/) NOEXCEPT { \
throw_or_abort##NOEXCEPT(std::integral_constant<bool, Throws>{}); \
} \
}; \
};
FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT)
#undef FU2_DEFINE_FUNCTION_TRAIT
/// Deduces to the function pointer to the given signature
template <typename Signature>
using function_pointer_of = typename function_trait<Signature>::pointer_type;
template <typename... Args>
struct invoke_table;
/// We optimize the vtable_t in case there is a single function overload
template <typename First>
struct invoke_table<First> {
using type = function_pointer_of<First>;
/// Return the function pointer itself
template <std::size_t Index>
static constexpr auto fetch(type pointer) noexcept {
static_assert(Index == 0U, "The index should be 0 here!");
return pointer;
}
/// Returns the thunk of an single overloaded callable
template <typename T, bool IsInplace>
static constexpr type get_invocation_table_of() noexcept {
return &function_trait<First>::template internal_invoker<T,
IsInplace>::invoke;
}
/// Returns the thunk of an single overloaded callable
template <typename T>
static constexpr type get_invocation_view_table_of() noexcept {
return &function_trait<First>::template view_invoker<T>::invoke;
}
/// Returns the thunk of an empty single overloaded callable
template <bool IsThrowing>
static constexpr type get_empty_invocation_table() noexcept {
return &function_trait<First>::template empty_invoker<IsThrowing>::invoke;
}
};
/// We generate a table in case of multiple function overloads
template <typename First, typename Second, typename... Args>
struct invoke_table<First, Second, Args...> {
using type =
std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
function_pointer_of<Args>...> const*;
/// Return the function pointer at the particular index
template <std::size_t Index>
static constexpr auto fetch(type table) noexcept {
return std::get<Index>(*table);
}
/// The invocation vtable for a present object
template <typename T, bool IsInplace>
struct invocation_vtable : public std::tuple<function_pointer_of<First>,
function_pointer_of<Second>,
function_pointer_of<Args>...> {
constexpr invocation_vtable() noexcept
: std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
function_pointer_of<Args>...>(std::make_tuple(
&function_trait<First>::template internal_invoker<
T, IsInplace>::invoke,
&function_trait<Second>::template internal_invoker<
T, IsInplace>::invoke,
&function_trait<Args>::template internal_invoker<
T, IsInplace>::invoke...)) {
}
};
/// Returns the thunk of an multi overloaded callable
template <typename T, bool IsInplace>
static type get_invocation_table_of() noexcept {
static invocation_vtable<T, IsInplace> const table;
return &table;
}
/// The invocation vtable for a present object
template <typename T>
struct invocation_view_vtable
: public std::tuple<function_pointer_of<First>,
function_pointer_of<Second>,
function_pointer_of<Args>...> {
constexpr invocation_view_vtable() noexcept
: std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
function_pointer_of<Args>...>(std::make_tuple(
&function_trait<First>::template view_invoker<T>::invoke,
&function_trait<Second>::template view_invoker<T>::invoke,
&function_trait<Args>::template view_invoker<T>::invoke...)) {
}
};
/// Returns the thunk of an multi overloaded callable
template <typename T>
static type get_invocation_view_table_of() noexcept {
static invocation_view_vtable<T> const table;
return &table;
}
/// The invocation table for an empty wrapper
template <bool IsThrowing>
struct empty_vtable : public std::tuple<function_pointer_of<First>,
function_pointer_of<Second>,
function_pointer_of<Args>...> {
constexpr empty_vtable() noexcept
: std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
function_pointer_of<Args>...>(
std::make_tuple(&function_trait<First>::template empty_invoker<
IsThrowing>::invoke,
&function_trait<Second>::template empty_invoker<
IsThrowing>::invoke,
&function_trait<Args>::template empty_invoker<
IsThrowing>::invoke...)) {
}
};
/// Returns the thunk of an multi single overloaded callable
template <bool IsThrowing>
static type get_empty_invocation_table() noexcept {
static empty_vtable<IsThrowing> const table;
return &table;
}
};
template <std::size_t Index, typename Function, typename... Signatures>
class operator_impl;
#define FU2_DEFINE_FUNCTION_TRAIT(CONST, VOLATILE, NOEXCEPT, OVL_REF, REF) \
template <std::size_t Index, typename Function, typename Ret, \
typename... Args, typename Next, typename... Signatures> \
class operator_impl<Index, Function, \
Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT, Next, \
Signatures...> \
: operator_impl<Index + 1, Function, Next, Signatures...> { \
\
template <std::size_t, typename, typename...> \
friend class operator_impl; \
\
protected: \
operator_impl() = default; \
~operator_impl() = default; \
operator_impl(operator_impl const&) = default; \
operator_impl(operator_impl&&) = default; \
operator_impl& operator=(operator_impl const&) = default; \
operator_impl& operator=(operator_impl&&) = default; \
\
using operator_impl<Index + 1, Function, Next, Signatures...>::operator(); \
\
Ret operator()(Args... args) CONST VOLATILE OVL_REF NOEXCEPT { \
auto parent = static_cast<Function CONST VOLATILE*>(this); \
using erasure_t = std::decay_t<decltype(parent->erasure_)>; \
\
/* `std::decay_t<decltype(parent->erasure_)>` is a workaround for a */ \
/* compiler regression of MSVC 16.3.1, see #29 for details. */ \
return std::decay_t<decltype(parent->erasure_)>::template invoke<Index>( \
static_cast<erasure_t CONST VOLATILE REF>(parent->erasure_), \
std::forward<Args>(args)...); \
} \
}; \
template <std::size_t Index, typename Config, typename Property, \
typename Ret, typename... Args> \
class operator_impl<Index, function<Config, Property>, \
Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT> \
: copyable<!Config::is_owning || Config::is_copyable> { \
\
template <std::size_t, typename, typename...> \
friend class operator_impl; \
\
protected: \
operator_impl() = default; \
~operator_impl() = default; \
operator_impl(operator_impl const&) = default; \
operator_impl(operator_impl&&) = default; \
operator_impl& operator=(operator_impl const&) = default; \
operator_impl& operator=(operator_impl&&) = default; \
\
Ret operator()(Args... args) CONST VOLATILE OVL_REF NOEXCEPT { \
auto parent = \
static_cast<function<Config, Property> CONST VOLATILE*>(this); \
using erasure_t = std::decay_t<decltype(parent->erasure_)>; \
\
/* `std::decay_t<decltype(parent->erasure_)>` is a workaround for a */ \
/* compiler regression of MSVC 16.3.1, see #29 for details. */ \
return std::decay_t<decltype(parent->erasure_)>::template invoke<Index>( \
static_cast<erasure_t CONST VOLATILE REF>(parent->erasure_), \
std::forward<Args>(args)...); \
} \
};
FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT)
#undef FU2_DEFINE_FUNCTION_TRAIT
} // namespace invocation_table
namespace tables {
/// Identifies the action which is dispatched on the erased object
enum class opcode {
op_move, //< Move the object and set the vtable
op_copy, //< Copy the object and set the vtable
op_destroy, //< Destroy the object and reset the vtable
op_weak_destroy, //< Destroy the object without resetting the vtable
op_fetch_empty, //< Stores true or false into the to storage
//< to indicate emptiness
};
/// Abstraction for a vtable together with a command table
/// TODO Add optimization for a single formal argument
/// TODO Add optimization to merge both tables if the function is size
/// optimized
template <typename Property>
class vtable;
template <bool IsThrowing, bool HasStrongExceptGuarantee,
typename... FormalArgs>
class vtable<property<IsThrowing, HasStrongExceptGuarantee, FormalArgs...>> {
using command_function_t = void (*)(vtable* /*this*/, opcode /*op*/,
data_accessor* /*from*/,
std::size_t /*from_capacity*/,
data_accessor* /*to*/,
std::size_t /*to_capacity*/);
using invoke_table_t = invocation_table::invoke_table<FormalArgs...>;
command_function_t cmd_;
typename invoke_table_t::type vtable_;
template <typename T>
struct trait {
static_assert(is_box<T>::value,
"The trait must be specialized with a box!");
/// The command table
template <bool IsInplace>
static void process_cmd(vtable* to_table, opcode op, data_accessor* from,
std::size_t from_capacity, data_accessor* to,
std::size_t to_capacity) {
switch (op) {
case opcode::op_move: {
/// Retrieve the pointer to the object
auto box = static_cast<T*>(retrieve<T>(
std::integral_constant<bool, IsInplace>{}, from, from_capacity));
assert(box && "The object must not be over aligned or null!");
if (!IsInplace) {
// Just swap both pointers if we allocated on the heap
to->ptr_ = from->ptr_;
#ifndef NDEBUG
// We don't need to null the pointer since we know that
// we don't own the data anymore through the vtable
// which is set to empty.
from->ptr_ = nullptr;
#endif
to_table->template set_allocated<T>();
}
// The object is allocated inplace
else {
construct(std::true_type{}, std::move(*box), to_table, to,
to_capacity);
box->~T();
}
return;
}
case opcode::op_copy: {
auto box = static_cast<T const*>(retrieve<T>(
std::integral_constant<bool, IsInplace>{}, from, from_capacity));
assert(box && "The object must not be over aligned or null!");
assert(std::is_copy_constructible<T>::value &&
"The box is required to be copyable here!");
// Try to allocate the object inplace
construct(std::is_copy_constructible<T>{}, *box, to_table, to,
to_capacity);
return;
}
case opcode::op_destroy:
case opcode::op_weak_destroy: {
assert(!to && !to_capacity && "Arg overflow!");
auto box = static_cast<T*>(retrieve<T>(
std::integral_constant<bool, IsInplace>{}, from, from_capacity));
if (IsInplace) {
box->~T();
} else {
box_factory<T>::box_deallocate(box);
}
if (op == opcode::op_destroy) {
to_table->set_empty();
}
return;
}
case opcode::op_fetch_empty: {
write_empty(to, false);
return;
}
}
FU2_DETAIL_UNREACHABLE();
}
template <typename Box>
static void
construct(std::true_type /*apply*/, Box&& box, vtable* to_table,
data_accessor* to,
std::size_t to_capacity) noexcept(HasStrongExceptGuarantee) {
// Try to allocate the object inplace
void* storage = retrieve<T>(std::true_type{}, to, to_capacity);
if (storage) {
to_table->template set_inplace<T>();
} else {
// Allocate the object through the allocator
to->ptr_ = storage =
box_factory<std::decay_t<Box>>::box_allocate(std::addressof(box));
to_table->template set_allocated<T>();
}
new (storage) T(std::forward<Box>(box));
}
template <typename Box>
static void
construct(std::false_type /*apply*/, Box&& /*box*/, vtable* /*to_table*/,
data_accessor* /*to*/,
std::size_t /*to_capacity*/) noexcept(HasStrongExceptGuarantee) {
}
};
/// The command table
static void empty_cmd(vtable* to_table, opcode op, data_accessor* /*from*/,
std::size_t /*from_capacity*/, data_accessor* to,
std::size_t /*to_capacity*/) {
switch (op) {
case opcode::op_move:
case opcode::op_copy: {
to_table->set_empty();
break;
}
case opcode::op_destroy:
case opcode::op_weak_destroy: {
// Do nothing
break;
}
case opcode::op_fetch_empty: {
write_empty(to, true);
break;
}
default: {
FU2_DETAIL_UNREACHABLE();
}
}
}
public:
vtable() noexcept = default;
/// Initialize an object at the given position
template <typename T>
static void init(vtable& table, T&& object, data_accessor* to,
std::size_t to_capacity) {
trait<std::decay_t<T>>::construct(std::true_type{}, std::forward<T>(object),
&table, to, to_capacity);
}
/// Moves the object at the given position
void move(vtable& to_table, data_accessor* from, std::size_t from_capacity,
data_accessor* to,
std::size_t to_capacity) noexcept(HasStrongExceptGuarantee) {
cmd_(&to_table, opcode::op_move, from, from_capacity, to, to_capacity);
set_empty();
}
/// Destroys the object at the given position
void copy(vtable& to_table, data_accessor const* from,
std::size_t from_capacity, data_accessor* to,
std::size_t to_capacity) const {
cmd_(&to_table, opcode::op_copy, const_cast<data_accessor*>(from),
from_capacity, to, to_capacity);
}
/// Destroys the object at the given position
void destroy(data_accessor* from,
std::size_t from_capacity) noexcept(HasStrongExceptGuarantee) {
cmd_(this, opcode::op_destroy, from, from_capacity, nullptr, 0U);
}
/// Destroys the object at the given position without invalidating the
/// vtable
void
weak_destroy(data_accessor* from,
std::size_t from_capacity) noexcept(HasStrongExceptGuarantee) {
cmd_(this, opcode::op_weak_destroy, from, from_capacity, nullptr, 0U);
}
/// Returns true when the vtable doesn't hold any erased object
bool empty() const noexcept {
data_accessor data;
cmd_(nullptr, opcode::op_fetch_empty, nullptr, 0U, &data, 0U);
return bool(data.inplace_storage_);
}
/// Invoke the function at the given index
template <std::size_t Index, typename... Args>
constexpr decltype(auto) invoke(Args&&... args) const {
auto thunk = invoke_table_t::template fetch<Index>(vtable_);
return thunk(std::forward<Args>(args)...);
}
/// Invoke the function at the given index
template <std::size_t Index, typename... Args>
constexpr decltype(auto) invoke(Args&&... args) const volatile {
auto thunk = invoke_table_t::template fetch<Index>(vtable_);
return thunk(std::forward<Args>(args)...);
}
template <typename T>
void set_inplace() noexcept {
using type = std::decay_t<T>;
vtable_ = invoke_table_t::template get_invocation_table_of<type, true>();
cmd_ = &trait<type>::template process_cmd<true>;
}
template <typename T>
void set_allocated() noexcept {
using type = std::decay_t<T>;
vtable_ = invoke_table_t::template get_invocation_table_of<type, false>();
cmd_ = &trait<type>::template process_cmd<false>;
}
void set_empty() noexcept {
vtable_ = invoke_table_t::template get_empty_invocation_table<IsThrowing>();
cmd_ = &empty_cmd;
}
};
} // namespace tables
/// A union which makes the pointer to the heap object share the
/// same space with the internal capacity.
/// The storage type is distinguished by multiple versions of the
/// control and vtable.
template <typename Capacity, typename = void>
struct internal_capacity {
/// We extend the union through a technique similar to the tail object hack
typedef union {
/// Tag to access the structure in a type-safe way
data_accessor accessor_;
/// The internal capacity we use to allocate in-place
std::aligned_storage_t<Capacity::capacity, Capacity::alignment> capacity_;
} type;
};
template <typename Capacity>
struct internal_capacity<
Capacity, std::enable_if_t<(Capacity::capacity < sizeof(void*))>> {
typedef struct {
/// Tag to access the structure in a type-safe way
data_accessor accessor_;
} type;
};
template <typename Capacity>
class internal_capacity_holder {
// Tag to access the structure in a type-safe way
typename internal_capacity<Capacity>::type storage_;
public:
constexpr internal_capacity_holder() = default;
constexpr data_accessor* opaque_ptr() noexcept {
return &storage_.accessor_;
}
constexpr data_accessor const* opaque_ptr() const noexcept {
return &storage_.accessor_;
}
constexpr data_accessor volatile* opaque_ptr() volatile noexcept {
return &storage_.accessor_;
}
constexpr data_accessor const volatile* opaque_ptr() const volatile noexcept {
return &storage_.accessor_;
}
static constexpr std::size_t capacity() noexcept {
return sizeof(storage_);
}
};
/// An owning erasure
template <bool IsOwning /* = true*/, typename Config, typename Property>
class erasure : internal_capacity_holder<typename Config::capacity> {
template <bool, typename, typename>
friend class erasure;
template <std::size_t, typename, typename...>
friend class operator_impl;
using vtable_t = tables::vtable<Property>;
vtable_t vtable_;
public:
/// Returns the capacity of this erasure
static constexpr std::size_t capacity() noexcept {
return internal_capacity_holder<typename Config::capacity>::capacity();
}
constexpr erasure() noexcept {
vtable_.set_empty();
}
constexpr erasure(std::nullptr_t) noexcept {
vtable_.set_empty();
}
constexpr erasure(erasure&& right) noexcept(
Property::is_strong_exception_guaranteed) {
right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
}
constexpr erasure(erasure const& right) {
right.vtable_.copy(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
}
template <typename OtherConfig>
constexpr erasure(erasure<true, OtherConfig, Property> right) noexcept(
Property::is_strong_exception_guaranteed) {
right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
}
template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
constexpr erasure(std::false_type /*use_bool_op*/, T&& callable,
Allocator&& allocator = Allocator{}) {
vtable_t::init(vtable_,
type_erasure::make_box(
std::integral_constant<bool, Config::is_copyable>{},
std::forward<T>(callable),
std::forward<Allocator>(allocator)),
this->opaque_ptr(), capacity());
}
template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
constexpr erasure(std::true_type /*use_bool_op*/, T&& callable,
Allocator&& allocator = Allocator{}) {
if (bool(callable)) {
vtable_t::init(vtable_,
type_erasure::make_box(
std::integral_constant<bool, Config::is_copyable>{},
std::forward<T>(callable),
std::forward<Allocator>(allocator)),
this->opaque_ptr(), capacity());
} else {
vtable_.set_empty();
}
}
~erasure() {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
}
constexpr erasure&
operator=(std::nullptr_t) noexcept(Property::is_strong_exception_guaranteed) {
vtable_.destroy(this->opaque_ptr(), capacity());
return *this;
}
constexpr erasure& operator=(erasure&& right) noexcept(
Property::is_strong_exception_guaranteed) {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
return *this;
}
constexpr erasure& operator=(erasure const& right) {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
right.vtable_.copy(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
return *this;
}
template <typename OtherConfig>
constexpr erasure&
operator=(erasure<true, OtherConfig, Property> right) noexcept(
Property::is_strong_exception_guaranteed) {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
return *this;
}
template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
void assign(std::false_type /*use_bool_op*/, T&& callable,
Allocator&& allocator = {}) {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
vtable_t::init(vtable_,
type_erasure::make_box(
std::integral_constant<bool, Config::is_copyable>{},
std::forward<T>(callable),
std::forward<Allocator>(allocator)),
this->opaque_ptr(), capacity());
}
template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
void assign(std::true_type /*use_bool_op*/, T&& callable,
Allocator&& allocator = {}) {
if (bool(callable)) {
assign(std::false_type{}, std::forward<T>(callable),
std::forward<Allocator>(allocator));
} else {
operator=(nullptr);
}
}
/// Returns true when the erasure doesn't hold any erased object
constexpr bool empty() const noexcept {
return vtable_.empty();
}
/// Invoke the function of the erasure at the given index
///
/// We define this out of class to be able to forward the qualified
/// erasure correctly.
template <std::size_t Index, typename Erasure, typename... Args>
static constexpr decltype(auto) invoke(Erasure&& erasure, Args&&... args) {
auto const capacity = erasure.capacity();
return erasure.vtable_.template invoke<Index>(
std::forward<Erasure>(erasure).opaque_ptr(), capacity,
std::forward<Args>(args)...);
}
};
// A non owning erasure
template </*bool IsOwning = false, */ typename Config, bool IsThrowing,
bool HasStrongExceptGuarantee, typename... Args>
class erasure<false, Config,
property<IsThrowing, HasStrongExceptGuarantee, Args...>> {
template <bool, typename, typename>
friend class erasure;
template <std::size_t, typename, typename...>
friend class operator_impl;
using property_t = property<IsThrowing, HasStrongExceptGuarantee, Args...>;
using invoke_table_t = invocation_table::invoke_table<Args...>;
typename invoke_table_t::type invoke_table_;
/// The internal pointer to the non owned object
data_accessor view_;
public:
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure() noexcept
: invoke_table_(
invoke_table_t::template get_empty_invocation_table<IsThrowing>()),
view_(nullptr) {
}
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure(std::nullptr_t) noexcept
: invoke_table_(
invoke_table_t::template get_empty_invocation_table<IsThrowing>()),
view_(nullptr) {
}
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure(erasure&& right) noexcept
: invoke_table_(right.invoke_table_), view_(right.view_) {
}
constexpr erasure(erasure const& /*right*/) = default;
template <typename OtherConfig>
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure(erasure<false, OtherConfig, property_t> right) noexcept
: invoke_table_(right.invoke_table_), view_(right.view_) {
}
template <typename T>
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure(std::false_type /*use_bool_op*/, T&& object)
: invoke_table_(invoke_table_t::template get_invocation_view_table_of<
std::decay_t<T>>()),
view_(address_taker<std::decay_t<T>>::take(std::forward<T>(object))) {
}
template <typename T>
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
constexpr erasure(std::true_type use_bool_op, T&& object) {
this->assign(use_bool_op, std::forward<T>(object));
}
~erasure() = default;
constexpr erasure&
operator=(std::nullptr_t) noexcept(HasStrongExceptGuarantee) {
invoke_table_ =
invoke_table_t::template get_empty_invocation_table<IsThrowing>();
view_.ptr_ = nullptr;
return *this;
}
constexpr erasure& operator=(erasure&& right) noexcept {
invoke_table_ = right.invoke_table_;
view_ = right.view_;
right = nullptr;
return *this;
}
constexpr erasure& operator=(erasure const& /*right*/) = default;
template <typename OtherConfig>
constexpr erasure&
operator=(erasure<true, OtherConfig, property_t> right) noexcept {
invoke_table_ = right.invoke_table_;
view_ = right.view_;
return *this;
}
template <typename T>
constexpr void assign(std::false_type /*use_bool_op*/, T&& callable) {
invoke_table_ = invoke_table_t::template get_invocation_view_table_of<
std::decay_t<T>>();
view_.ptr_ =
address_taker<std::decay_t<T>>::take(std::forward<T>(callable));
}
template <typename T>
constexpr void assign(std::true_type /*use_bool_op*/, T&& callable) {
if (bool(callable)) {
assign(std::false_type{}, std::forward<T>(callable));
} else {
operator=(nullptr);
}
}
/// Returns true when the erasure doesn't hold any erased object
constexpr bool empty() const noexcept {
return view_.ptr_ == nullptr;
}
template <std::size_t Index, typename Erasure, typename... T>
static constexpr decltype(auto) invoke(Erasure&& erasure, T&&... args) {
auto thunk = invoke_table_t::template fetch<Index>(erasure.invoke_table_);
return thunk(&(erasure.view_), 0UL, std::forward<T>(args)...);
}
};
} // namespace type_erasure
/// Deduces to a true_type if the type T provides the given signature and the
/// signature is noexcept correct callable.
template <typename T, typename Signature,
typename Trait =
type_erasure::invocation_table::function_trait<Signature>>
struct accepts_one
: std::integral_constant<
bool, invocation::can_invoke<typename Trait::template callable<T>,
typename Trait::arguments>::value &&
invocation::is_noexcept_correct<
Trait::is_noexcept::value,
typename Trait::template callable<T>,
typename Trait::arguments>::value> {};
/// Deduces to a true_type if the type T provides all signatures
template <typename T, typename Signatures, typename = void>
struct accepts_all : std::false_type {};
template <typename T, typename... Signatures>
struct accepts_all<
T, identity<Signatures...>,
void_t<std::enable_if_t<accepts_one<T, Signatures>::value>...>>
: std::true_type {};
/// Deduces to a true_type if the type T is implementing operator bool()
/// or if the type is convertible to bool directly, this also implements an
/// optimizations for function references `void(&)()` which are can never
/// be null and for such a conversion to bool would never return false.
#if defined(FU2_HAS_NO_EMPTY_PROPAGATION)
template <typename T>
struct use_bool_op : std::false_type {};
#else
template <typename T, typename = void>
struct has_bool_op : std::false_type {};
template <typename T>
struct has_bool_op<T, void_t<decltype(bool(std::declval<T>()))>>
: std::true_type {
#ifndef NDEBUG
static_assert(!std::is_pointer<T>::value,
"Missing deduction for function pointer!");
#endif
};
template <typename T>
struct use_bool_op : has_bool_op<T> {};
#define FU2_DEFINE_USE_OP_TRAIT(CONST, VOLATILE, NOEXCEPT) \
template <typename Ret, typename... Args> \
struct use_bool_op<Ret (*CONST VOLATILE)(Args...) NOEXCEPT> \
: std::true_type {};
FU2_DETAIL_EXPAND_CV(FU2_DEFINE_USE_OP_TRAIT)
#undef FU2_DEFINE_USE_OP_TRAIT
template <typename Ret, typename... Args>
struct use_bool_op<Ret(Args...)> : std::false_type {};
#if defined(FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE)
template <typename Ret, typename... Args>
struct use_bool_op<Ret(Args...) noexcept> : std::false_type {};
#endif
#endif // FU2_HAS_NO_EMPTY_PROPAGATION
template <typename Config, typename T>
struct assert_wrong_copy_assign {
static_assert(!Config::is_owning || !Config::is_copyable ||
std::is_copy_constructible<std::decay_t<T>>::value,
"Can't wrap a non copyable object into a unique function!");
using type = void;
};
template <bool IsStrongExceptGuaranteed, typename T>
struct assert_no_strong_except_guarantee {
static_assert(
!IsStrongExceptGuaranteed ||
(std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_destructible<T>::value),
"Can't wrap a object an object that has no strong exception guarantees "
"if this is required by the wrapper!");
using type = void;
};
/// SFINAES out if the given callable is not copyable correct to the left one.
template <typename LeftConfig, typename RightConfig>
using enable_if_copyable_correct_t =
std::enable_if_t<(!LeftConfig::is_copyable || RightConfig::is_copyable)>;
template <typename LeftConfig, typename RightConfig>
using is_owning_correct =
std::integral_constant<bool,
(LeftConfig::is_owning == RightConfig::is_owning)>;
/// SFINAES out if the given function2 is not owning correct to this one
template <typename LeftConfig, typename RightConfig>
using enable_if_owning_correct_t =
std::enable_if_t<is_owning_correct<LeftConfig, RightConfig>::value>;
template <typename Config, bool IsThrowing, bool HasStrongExceptGuarantee,
typename... Args>
class function<Config, property<IsThrowing, HasStrongExceptGuarantee, Args...>>
: type_erasure::invocation_table::operator_impl<
0U,
function<Config,
property<IsThrowing, HasStrongExceptGuarantee, Args...>>,
Args...> {
template <typename, typename>
friend class function;
template <std::size_t, typename, typename...>
friend class type_erasure::invocation_table::operator_impl;
using property_t = property<IsThrowing, HasStrongExceptGuarantee, Args...>;
using erasure_t =
type_erasure::erasure<Config::is_owning, Config, property_t>;
template <typename T>
using enable_if_can_accept_all_t =
std::enable_if_t<accepts_all<std::decay_t<T>, identity<Args...>>::value>;
template <typename Function, typename = void>
struct is_convertible_to_this : std::false_type {};
template <typename RightConfig>
struct is_convertible_to_this<
function<RightConfig, property_t>,
void_t<enable_if_copyable_correct_t<Config, RightConfig>,
enable_if_owning_correct_t<Config, RightConfig>>>
: std::true_type {};
template <typename T>
using enable_if_not_convertible_to_this =
std::enable_if_t<!is_convertible_to_this<std::decay_t<T>>::value>;
template <typename T>
using enable_if_owning_t =
std::enable_if_t<std::is_same<T, T>::value && Config::is_owning>;
template <typename T>
using assert_wrong_copy_assign_t =
typename assert_wrong_copy_assign<Config, std::decay_t<T>>::type;
template <typename T>
using assert_no_strong_except_guarantee_t =
typename assert_no_strong_except_guarantee<HasStrongExceptGuarantee,
std::decay_t<T>>::type;
erasure_t erasure_;
public:
/// Default constructor which empty constructs the function
function() = default;
~function() = default;
explicit constexpr function(function const& /*right*/) = default;
explicit constexpr function(function&& /*right*/) = default;
/// Copy construction from another copyable function
template <typename RightConfig,
std::enable_if_t<RightConfig::is_copyable>* = nullptr,
enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
constexpr function(function<RightConfig, property_t> const& right)
: erasure_(right.erasure_) {
}
/// Move construction from another function
template <typename RightConfig,
enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
constexpr function(function<RightConfig, property_t>&& right)
: erasure_(std::move(right.erasure_)) {
}
/// Construction from a callable object which overloads the `()` operator
template <typename T, //
enable_if_not_convertible_to_this<T>* = nullptr,
enable_if_can_accept_all_t<T>* = nullptr,
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
constexpr function(T&& callable)
: erasure_(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable)) {
}
template <typename T, typename Allocator, //
enable_if_not_convertible_to_this<T>* = nullptr,
enable_if_can_accept_all_t<T>* = nullptr,
enable_if_owning_t<T>* = nullptr,
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
constexpr function(T&& callable, Allocator&& allocator)
: erasure_(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable),
std::forward<Allocator>(allocator)) {
}
/// Empty constructs the function
constexpr function(std::nullptr_t np) : erasure_(np) {
}
function& operator=(function const& /*right*/) = default;
function& operator=(function&& /*right*/) = default;
/// Copy assigning from another copyable function
template <typename RightConfig,
std::enable_if_t<RightConfig::is_copyable>* = nullptr,
enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
function& operator=(function<RightConfig, property_t> const& right) {
erasure_ = right.erasure_;
return *this;
}
/// Move assigning from another function
template <typename RightConfig,
enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
function& operator=(function<RightConfig, property_t>&& right) {
erasure_ = std::move(right.erasure_);
return *this;
}
/// Move assigning from a callable object
template <typename T, // ...
enable_if_not_convertible_to_this<T>* = nullptr,
enable_if_can_accept_all_t<T>* = nullptr,
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
function& operator=(T&& callable) {
erasure_.assign(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable));
return *this;
}
/// Clears the function
function& operator=(std::nullptr_t np) {
erasure_ = np;
return *this;
}
/// Returns true when the function is empty
bool empty() const noexcept {
return erasure_.empty();
}
/// Returns true when the function isn't empty
explicit operator bool() const noexcept {
return !empty();
}
/// Assigns a new target with an optional allocator
template <typename T, typename Allocator = std::allocator<std::decay_t<T>>,
enable_if_not_convertible_to_this<T>* = nullptr,
enable_if_can_accept_all_t<T>* = nullptr,
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
void assign(T&& callable, Allocator&& allocator = Allocator{}) {
erasure_.assign(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable),
std::forward<Allocator>(allocator));
}
/// Swaps this function with the given function
void swap(function& other) noexcept(HasStrongExceptGuarantee) {
if (&other == this) {
return;
}
function cache = std::move(other);
other = std::move(*this);
*this = std::move(cache);
}
/// Swaps the left function with the right one
friend void swap(function& left,
function& right) noexcept(HasStrongExceptGuarantee) {
left.swap(right);
}
/// Calls the wrapped callable object
using type_erasure::invocation_table::operator_impl<
0U, function<Config, property_t>, Args...>::operator();
};
template <typename Config, typename Property>
bool operator==(function<Config, Property> const& f, std::nullptr_t) {
return !bool(f);
}
template <typename Config, typename Property>
bool operator!=(function<Config, Property> const& f, std::nullptr_t) {
return bool(f);
}
template <typename Config, typename Property>
bool operator==(std::nullptr_t, function<Config, Property> const& f) {
return !bool(f);
}
template <typename Config, typename Property>
bool operator!=(std::nullptr_t, function<Config, Property> const& f) {
return bool(f);
}
// Default intended object size of the function
using object_size = std::integral_constant<std::size_t, 32U>;
} // namespace detail
} // namespace abi_400
/// Can be passed to function_base as template argument which causes
/// the internal small buffer to be sized according to the given size,
/// and aligned with the given alignment.
template <std::size_t Capacity,
std::size_t Alignment = alignof(std::max_align_t)>
struct capacity_fixed {
static constexpr std::size_t capacity = Capacity;
static constexpr std::size_t alignment = Alignment;
};
/// Default capacity for small functor optimization
struct capacity_default
: capacity_fixed<detail::object_size::value - (2 * sizeof(void*))> {};
/// Can be passed to function_base as template argument which causes
/// the internal small buffer to be removed from the callable wrapper.
/// The owning function_base will then allocate memory for every object
/// it applies a type erasure on.
struct capacity_none : capacity_fixed<0UL> {};
/// Can be passed to function_base as template argument which causes
/// the internal small buffer to be sized such that it can hold
/// the given object without allocating memory for an applied type erasure.
template <typename T>
struct capacity_can_hold {
static constexpr std::size_t capacity = sizeof(T);
static constexpr std::size_t alignment = alignof(T);
};
/// An adaptable function wrapper base for arbitrary functional types.
///
/// \tparam IsOwning Is true when the type erasure shall be owning the object.
///
/// \tparam IsCopyable Defines whether the function is copyable or not
///
/// \tparam Capacity Defines the internal capacity of the function
/// for small functor optimization.
/// The size of the whole function object will be the capacity
/// plus the size of two pointers. If the capacity is zero,
/// the size will increase through one additional pointer
/// so the whole object has the size of 3 * sizeof(void*).
/// The type which is passed to the Capacity template parameter
/// shall provide a capacity and alignment member which
/// looks like the following example:
/// ```cpp
/// struct my_capacity {
/// static constexpr std::size_t capacity = sizeof(my_type);
/// static constexpr std::size_t alignment = alignof(my_type);
/// };
/// ```
///
/// \tparam IsThrowing Defines whether the function throws an exception on
/// empty function call, `std::abort` is called otherwise.
///
/// \tparam HasStrongExceptGuarantee Defines whether all objects satisfy the
/// strong exception guarantees,
/// which means the function type will satisfy
/// the strong exception guarantees too.
///
/// \tparam Signatures Defines the signature of the callable wrapper
///
template <bool IsOwning, bool IsCopyable, typename Capacity, bool IsThrowing,
bool HasStrongExceptGuarantee, typename... Signatures>
using function_base = detail::function<
detail::config<IsOwning, IsCopyable, Capacity>,
detail::property<IsThrowing, HasStrongExceptGuarantee, Signatures...>>;
/// An owning copyable function wrapper for arbitrary callable types.
template <typename... Signatures>
using function = function_base<true, true, capacity_default, //
true, false, Signatures...>;
/// An owning non copyable function wrapper for arbitrary callable types.
template <typename... Signatures>
using unique_function = function_base<true, false, capacity_default, //
true, false, Signatures...>;
/// A non owning copyable function wrapper for arbitrary callable types.
template <typename... Signatures>
using function_view = function_base<false, true, capacity_default, //
true, false, Signatures...>;
#if !defined(FU2_HAS_DISABLED_EXCEPTIONS)
/// Exception type that is thrown when invoking empty function objects
/// and exception support isn't disabled.
///
/// Exception support is enabled if
/// the template parameter 'Throwing' is set to true (default).
///
/// This type will default to std::bad_function_call if the
/// functional header is used, otherwise the library provides its own type.
///
/// You may disable the inclusion of the functional header
/// through defining `FU2_WITH_NO_FUNCTIONAL_HEADER`.
///
using detail::type_erasure::invocation_table::bad_function_call;
#endif
/// Returns a callable object, which unifies all callable objects
/// that were passed to this function.
///
/// ```cpp
/// auto overloaded = fu2::overload([](std::true_type) { return true; },
/// [](std::false_type) { return false; });
/// ```
///
/// \param callables A pack of callable objects with arbitrary signatures.
///
/// \returns A callable object which exposes the
///
template <typename... T>
constexpr auto overload(T&&... callables) {
return detail::overloading::overload(std::forward<T>(callables)...);
}
} // namespace fu2
#undef FU2_DETAIL_EXPAND_QUALIFIERS
#undef FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT
#undef FU2_DETAIL_EXPAND_CV
#undef FU2_DETAIL_EXPAND_CV_NOEXCEPT
#undef FU2_DETAIL_UNREACHABLE_INTRINSIC
#undef FU2_DETAIL_UNREACHABLE_INTRINSIC
#undef FU2_DETAIL_TRAP
#endif // FU2_INCLUDED_FUNCTION2_HPP_
// #include <continuable/continuable-base.hpp>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-promise-base.hpp>
// #include <continuable/detail/other/erasure.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_ERASURE_HPP_INCLUDED
#define CONTINUABLE_DETAIL_ERASURE_HPP_INCLUDED
#include <type_traits>
#include <utility>
// #include <function2/function2.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace erasure {
template <typename... Args>
using callback_erasure_t =
fu2::function_base<true, false, fu2::capacity_none, true, false,
void(Args...)&&, void(exception_arg_t, exception_t) &&>;
#ifdef CONTINUABLE_HAS_IMMEDIATE_TYPES
template <typename... Args>
using callback = callback_erasure_t<Args...>;
#else
template <typename... Args>
class callback;
template <typename T>
struct is_callback : std::false_type {};
template <typename... Args>
struct is_callback<callback<Args...>> : std::true_type {};
template <typename... Args>
class callback : public callback_erasure_t<Args...> {
public:
using erasure_t = callback_erasure_t<Args...>;
erasure_t erasure_;
callback() = default;
~callback() = default;
callback(callback const&) = delete;
callback(callback&&) = default;
callback& operator=(callback const&) = delete;
callback& operator=(callback&&) = default;
template <
typename T,
std::enable_if_t<std::is_convertible<T, erasure_t>::value>* = nullptr,
std::enable_if_t<!is_callback<traits::unrefcv_t<T>>::value>* = nullptr>
/* implicit */ callback(T&& callable) : erasure_(std::forward<T>(callable)) {
}
template <
typename T,
std::enable_if_t<std::is_assignable<erasure_t, T>::value>* = nullptr,
std::enable_if_t<!is_callback<traits::unrefcv_t<T>>::value>* = nullptr>
callback& operator=(T&& callable) {
erasure_ = std::forward<T>(callable);
return *this;
}
void operator()(Args... args) && noexcept {
std::move(erasure_)(std::move(args)...);
}
void operator()(exception_arg_t exception_arg, exception_t exception) &&
noexcept {
std::move(erasure_)(exception_arg, std::move(exception));
}
explicit operator bool() const noexcept {
return bool(erasure_);
}
};
#endif
using work_erasure_t =
fu2::function_base<true, false, fu2::capacity_fixed<32UL>, true, false,
void()&&, void(exception_arg_t, exception_t) &&>;
#ifdef CONTINUABLE_HAS_IMMEDIATE_TYPES
using work = work_erasure_t;
#else
class work;
template <typename T>
struct is_work : std::false_type {};
template <>
struct is_work<work> : std::true_type {};
class work {
using erasure_t = work_erasure_t;
erasure_t erasure_;
public:
work() = default;
~work() = default;
work(work const&) = delete;
work(work&&) = default;
work& operator=(work const&) = delete;
work& operator=(work&&) = default;
template <
typename T,
std::enable_if_t<std::is_convertible<T, erasure_t>::value>* = nullptr,
std::enable_if_t<!is_work<traits::unrefcv_t<T>>::value>* = nullptr>
/* implicit */ work(T&& callable) : erasure_(std::forward<T>(callable)) {
}
template <
typename T,
std::enable_if_t<std::is_assignable<erasure_t, T>::value>* = nullptr,
std::enable_if_t<!is_work<traits::unrefcv_t<T>>::value>* = nullptr>
work& operator=(T&& callable) {
erasure_ = std::forward<T>(callable);
return *this;
}
void operator()() && noexcept {
std::move(erasure_)();
}
void operator()(exception_arg_t, exception_t exception) && noexcept {
std::move(erasure_)(exception_arg_t{}, std::move(exception));
}
explicit operator bool() const noexcept {
return bool(erasure_);
}
};
#endif
template <typename... Args>
struct continuation_capacity {
using type = union {
void* pointer_;
base::ready_continuation<Args...> continuation_;
};
static constexpr std::size_t capacity = sizeof(type);
static constexpr std::size_t alignment = alignof(type);
};
template <typename... Args>
using continuation_erasure_t = fu2::function_base<
true, false, continuation_capacity<Args...>, true, false,
void(promise_base<callback<Args...>, signature_arg_t<Args...>>),
bool(is_ready_arg_t) const, result<Args...>(unpack_arg_t)>;
#ifdef CONTINUABLE_HAS_IMMEDIATE_TYPES
template <typename... Args>
using continuation = continuation_erasure_t<Args...>;
#else
template <typename... Args>
class continuation;
template <typename T>
struct is_continuation : std::false_type {};
template <typename... Args>
struct is_continuation<continuation<Args...>> : std::true_type {};
template <typename... Args>
class continuation {
using erasure_t = continuation_erasure_t<Args...>;
erasure_t erasure_;
public:
continuation() = default;
~continuation() = default;
continuation(continuation const&) = delete;
continuation(continuation&&) = default;
continuation& operator=(continuation const&) = delete;
continuation& operator=(continuation&&) = default;
template <
typename T,
std::enable_if_t<std::is_convertible<T, erasure_t>::value>* = nullptr,
std::enable_if_t<!is_continuation<traits::unrefcv_t<T>>::value>* =
nullptr>
/* implicit */ continuation(T&& callable)
: erasure_(std::forward<T>(callable)) {
}
template <
typename T,
std::enable_if_t<std::is_assignable<erasure_t, T>::value>* = nullptr,
std::enable_if_t<!is_continuation<traits::unrefcv_t<T>>::value>* =
nullptr>
continuation& operator=(T&& callable) {
erasure_ = std::forward<T>(callable);
return *this;
}
void operator()(promise_base<callback<Args...>, //
signature_arg_t<Args...>>
promise) {
erasure_(std::move(promise));
}
bool operator()(is_ready_arg_t is_ready_arg) const {
return erasure_(is_ready_arg);
}
result<Args...> operator()(unpack_arg_t query_arg) {
return erasure_(query_arg);
}
};
#endif
} // namespace erasure
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_ERASURE_HPP_INCLUDED
namespace cti {
/// \defgroup Types Types
/// provides the \link cti::continuable continuable\endlink and \link
/// cti::promise promise\endlink facility for type erasure.
/// \{
/// Deduces to the preferred continuation capacity for a possible
/// small functor optimization. The given capacity size is always enough to
/// to avoid any allocation when storing a ready continuable_base.
///
/// \since 4.0.0
template <typename... Args>
using continuation_capacity = detail::erasure::continuation_capacity<Args...>;
/// Defines a non-copyable continuation type which uses the
/// function2 backend for type erasure.
///
/// Usable like: `continuable<int, float>`
///
/// \note You can always define your own continuable with a type erasure of
/// choice, the type erasure wrapper just needs to accept a
/// callable object with a continuation signature as specified
/// in the Primitives section.
///
/// \since 1.0.0
template <typename... Args>
using continuable = continuable_base<detail::erasure::continuation<Args...>, //
signature_arg_t<Args...>>;
/// Defines a non-copyable promise type which is using the
/// function2 backend for type erasure.
///
/// Usable like: `promise<int, float>`
///
/// \note You can always define your own promise with a type erasure of
/// choice, the type erasure wrapper just needs to accept a
/// callable object with a callback signature as specified
/// in the Primitives section.
///
/// \since 1.0.0
template <typename... Args>
using promise = promise_base<detail::erasure::callback<Args...>, //
signature_arg_t<Args...>>;
/// Defines a non-copyable type erasure which is capable of carrying
/// callable objects passed to executors.
///
/// The work behaves like a `promise<>` but the work type erasure uses extra
/// stack space for small object optimization.
/// Additionally the outstanding work can be resolved through an exception.
///
/// \note You can always define your own cancelable_work with a type erasure of
/// choice, the type erasure wrapper just needs to accept a
/// callable object which is callable with a `void()` and
/// `void(exception_arg_t, exception_t)` signature.
///
/// \since 4.0.0
using work = promise_base<detail::erasure::work, //
signature_arg_t<>>;
/// \}
} // namespace cti
#endif // CONTINUABLE_TYPES_HPP_INCLUDED
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
# include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS
#if defined(CONTINUABLE_HAS_COROUTINE)
// # include <continuable/detail/other/coroutines.hpp>
namespace cti {
# if defined(CONTINUABLE_HAS_EXCEPTIONS)
/// Is thrown from co_await expressions if the awaited continuable is canceled
///
/// Default constructed exception types that are returned by a cancelled
/// continuable are converted automatically to await_canceled_exception when
/// being returned by a co_await expression.
///
/// The await_canceled_exception gets converted again to a default constructed
/// exception type if it becomes unhandled inside a coroutine which
/// returns a continuable_base.
/// ```cpp
/// continuable<> cancelled_coroutine() {
/// co_await make_cancelling_continuable<void>();
///
/// co_return;
/// }
///
/// // ...
///
/// cancelled_coroutine().fail([](exception_t e) {
/// assert(bool(e) == false);
/// });
/// ```
///
/// \since 4.1.0
using await_canceled_exception = detail::awaiting::await_canceled_exception;
# endif // CONTINUABLE_HAS_EXCEPTIONS
} // namespace cti
/// \cond false
// As far as I know there is no other way to implement this specialization...
// NOLINTNEXTLINE(cert-dcl58-cpp)
namespace std {
# if defined(CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE)
namespace experimental {
# endif // defined(CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE)
template <typename Data, typename... Args, typename... FunctionArgs>
struct coroutine_traits<
cti::continuable_base<Data, cti::detail::identity<Args...>>,
FunctionArgs...> {
using promise_type = cti::detail::awaiting::promise_type<
cti::continuable<Args...>, cti::promise<Args...>, Args...>;
};
# if defined(CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE)
} // namespace experimental
# endif // defined(CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE)
} // namespace std
/// \endcond
#endif // defined(CONTINUABLE_HAS_COROUTINE)
#endif // CONTINUABLE_COROUTINE_HPP_INCLUDED
// #include <continuable/continuable-operations.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_OPERATIONS_HPP_INCLUDED
#define CONTINUABLE_OPERATIONS_HPP_INCLUDED
/// \defgroup Operations Operations
/// provides functions to work with asynchronous control flows.
// #include <continuable/operations/async.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_OPERATIONS_ASYNC_HPP_INCLUDED
#define CONTINUABLE_OPERATIONS_ASYNC_HPP_INCLUDED
#include <utility>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/operations/async.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_OPERATIONS_ASYNC_HPP_INCLUDED
#define CONTINUABLE_DETAIL_OPERATIONS_ASYNC_HPP_INCLUDED
// #include <continuable/continuable-base.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/utility/identity.hpp>
namespace cti {
namespace detail {
namespace operations {
template <typename Callable, typename Executor, typename... Args>
auto async(Callable&& callable, Executor&& executor, Args&&... args) {
using result_t =
decltype(util::invoke(std::forward<decltype(callable)>(callable),
std::forward<decltype(args)>(args)...));
constexpr auto hint =
decltype(base::decoration::invoker_of(identity<result_t>{}))::hint();
auto continuation = [callable = std::forward<decltype(callable)>(callable),
executor = std::forward<decltype(executor)>(executor),
args = std::make_tuple(std::forward<decltype(args)>(
args)...)](auto&& promise) mutable {
auto invoker = base::decoration::invoker_of(identity<result_t>{});
using promise_t = decltype(promise);
// Invoke the callback
traits::unpack(
[&](auto&&... args) mutable {
// Invoke the promise through the dedicated invoker
// and through the given executor
base::on_executor(std::move(executor), std::move(invoker),
std::move(callable),
std::forward<promise_t>(promise),
std::forward<decltype(args)>(args)...);
},
std::move(args));
};
return base::attorney::create_from(std::move(continuation), //
hint, util::ownership{});
}
} // namespace operations
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_OPERATIONS_ASYNC_HPP_INCLUDED
namespace cti {
/// \ingroup Operations
/// \{
/// Wraps the given callable inside a continuable_base such that it is
/// invoked when the asynchronous result is requested to return the result.
///
/// The async function shall be seen as an equivalent to std::async.
///
/// The behaviour will be equal as when using make_ready_continuable together
/// with continuable_base::then, but async is implemented in
/// a more efficient way:
/// ```cpp
/// auto do_sth() {
/// return async([] {
/// do_sth_more();
/// return 0;
/// });
/// }
/// ```
///
/// \param callable The callable type which is invoked on request.
///
/// \param args The arguments which are passed to the callable upon invocation.
///
/// \returns A continuable_base which asynchronous result type will
/// be computed with the same rules as continuable_base::then .
///
/// \since 4.0.0
///
template <typename Callable, typename... Args>
auto async(Callable&& callable, Args&&... args) {
return detail::operations::async(std::forward<Callable>(callable),
detail::types::this_thread_executor_tag{},
std::forward<Args>(args)...);
}
/// Wraps the given callable inside a continuable_base such that it is
/// invoked through the given executor when the asynchronous result
/// is requested to return the result.
///
/// The behaviour will be equal as when using make_ready_continuable together
/// with continuable_base::then and the given executor but async_on
/// is implemented in a more efficient way:
/// ```cpp
/// auto do_sth() {
/// auto executor = [](auto&& work) {
/// // Do something with the work here
/// std::forward<decltype(work)>(work);
/// };
///
/// return async_on([] {
/// do_sth_more();
/// return 0;
/// }, my_executor);
/// }
/// ```
///
/// \param callable The callable type which is invoked on request.
///
/// \param executor The executor that is used to dispatch the given callable.
///
/// \param args The arguments which are passed to the callable upon invocation.
///
/// \returns A continuable_base which asynchronous result type will
/// be computed with the same rules as continuable_base::then .
///
/// \since 4.0.0
///
template <typename Callable, typename Executor, typename... Args>
auto async_on(Callable&& callable, Executor&& executor, Args&&... args) {
return detail::operations::async(std::forward<Callable>(callable),
std::forward<Executor>(executor),
std::forward<Args>(args)...);
}
/// \}
} // namespace cti
#endif // CONTINUABLE_OPERATIONS_ASYNC_HPP_INCLUDED
// #include <continuable/operations/loop.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_OPERATIONS_LOOP_HPP_INCLUDED
#define CONTINUABLE_OPERATIONS_LOOP_HPP_INCLUDED
#include <utility>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-result.hpp>
// #include <continuable/detail/operations/loop.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_OPERATIONS_LOOP_HPP_INCLUDED
#define CONTINUABLE_DETAIL_OPERATIONS_LOOP_HPP_INCLUDED
#include <cassert>
#include <memory>
#include <tuple>
#include <type_traits>
// #include <continuable/continuable-base.hpp>
// #include <continuable/continuable-result.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
#include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS
namespace cti {
namespace detail {
template <typename T>
struct loop_trait {
static_assert(!std::is_same<T, T>::value,
"The callable passed to cti::loop must always return a "
"cti::continuable_base which resolves to a cti::result.");
};
template <typename... Args>
struct loop_trait<identity<result<Args...>>> {
template <typename Callable>
static auto make(Callable&& callable) {
return make_continuable<Args...>(std::forward<Callable>(callable));
}
};
template <>
struct loop_trait<identity<result<>>> {
template <typename Callable>
static auto make(Callable&& callable) {
return make_continuable<void>(std::forward<Callable>(callable));
}
};
namespace operations {
template <typename Promise, typename Callable, typename ArgsTuple>
class loop_frame : public std::enable_shared_from_this<
loop_frame<Promise, Callable, ArgsTuple>> {
Promise promise_;
Callable callable_;
ArgsTuple args_;
public:
explicit loop_frame(Promise promise, Callable callable, ArgsTuple args)
: promise_(std::move(promise)), callable_(std::move(callable)),
args_(std::move(args)) {
}
void loop() {
// MSVC can't evaluate this inside the lambda capture
auto me = this->shared_from_this();
traits::unpack(
[&](auto&&... args) mutable {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
try {
#endif // CONTINUABLE_HAS_EXCEPTIONS
util::invoke(callable_, std::forward<decltype(args)>(args)...)
.next([me = std::move(me)](auto&&... args) {
me->resolve(std::forward<decltype(args)>(args)...);
});
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
} catch (...) {
me->resolve(exception_arg_t{}, std::current_exception());
}
#endif // CONTINUABLE_HAS_EXCEPTIONS
},
args_);
}
template <typename Result>
void resolve(Result&& result) {
if (result.is_empty()) {
loop();
} else if (result.is_value()) {
traits::unpack(std::move(promise_), std::forward<Result>(result));
} else {
assert(result.is_exception());
std::move(promise_).set_exception(
std::forward<Result>(result).get_exception());
}
}
void resolve(exception_arg_t, exception_t exception) {
promise_.set_exception(std::move(exception));
}
};
template <typename Promise, typename Callable, typename ArgsTuple>
auto make_loop_frame(Promise&& promise, Callable&& callable,
ArgsTuple&& args_tuple) {
using frame_t =
loop_frame<traits::unrefcv_t<Promise>, traits::unrefcv_t<Callable>,
traits::unrefcv_t<ArgsTuple>>;
return std::make_shared<frame_t>(std::forward<Promise>(promise),
std::forward<Callable>(callable),
std::forward<ArgsTuple>(args_tuple));
}
template <typename Callable, typename... Args>
auto loop(Callable&& callable, Args&&... args) {
using invocation_result_t =
decltype(util::invoke(callable, args...).finish());
auto constexpr hint = base::annotation_of(identify<invocation_result_t>{});
using trait_t = loop_trait<std::remove_const_t<decltype(hint)>>;
return trait_t::make([callable = std::forward<decltype(callable)>(callable),
args = std::make_tuple(std::forward<decltype(args)>(
args)...)](auto&& promise) mutable {
// Do the actual looping
auto frame = make_loop_frame(std::forward<decltype(promise)>(promise),
std::move(callable), std::move(args));
frame->loop();
});
}
template <typename Callable, typename Begin, typename End>
auto make_range_looper(Callable&& callable, Begin&& begin, End&& end) {
return [callable = std::forward<Callable>(callable),
begin = std::forward<Begin>(begin),
end = std::forward<End>(end)]() mutable {
return util::invoke(callable, begin)
.then([&begin, &end]() mutable -> plain_t<result<>> {
// begin and end stays valid over the `then` here
if (++begin != end) {
return make_plain(result<>::empty());
} else {
return make_plain(make_result());
}
});
};
}
} // namespace operations
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_OPERATIONS_LOOP_HPP_INCLUDED
namespace cti {
/// \ingroup Operations
/// \{
/// Can be used to create an asynchronous loop.
///
/// The callable will be called repeatedly until it returns a
/// cti::continuable_base which then resolves to a present cti::result.
///
/// For better readability cti::loop_result, cti::loop_break and
/// cti::loop_continue are provided which can be used as following:
/// ```cpp
/// auto while_answer_not_yes() {
/// return loop([] {
/// return ask_something().then([](std::string answer) -> loop_result<> {
/// if (answer == "yes") {
/// return loop_break();
/// } else {
/// return loop_continue();
/// }
/// });
/// });
/// }
/// ```
///
/// \param callable The callable type which must return a cti::continuable_base
/// which then resolves to a cti::result of arbitrary values.
///
/// \param args The arguments that are passed to the callable upon
/// each invocation.
///
/// \since 4.0.0
///
template <typename Callable, typename... Args>
auto loop(Callable&& callable, Args&&... args) {
return detail::operations::loop(std::forward<Callable>(callable),
std::forward<Args>(args)...);
}
/// Can be used to indicate a specific result inside an asynchronous loop.
///
/// See cti::loop for details.
///
/// \since 4.0.0
template <typename... T>
using loop_result = plain_t<result<T...>>;
/// Can be used to create a loop_result which causes the loop to be
/// cancelled and resolved with the given arguments.
///
/// See cti::loop for details.
///
/// \since 4.0.0
template <typename... T>
auto loop_break(T&&... args) {
return make_plain(make_result(std::forward<T>(args)...));
}
/// Can be used to create a loop_result which causes the loop to be repeated.
///
/// See cti::loop for details.
///
/// \since 4.0.0
inline auto loop_continue() noexcept {
return empty_result{};
}
/// Can be used to create an asynchronous loop over a specific range.
///
/// The callable will be called repeatedly with each with begin increased
/// until end is reached.
///
/// ```cpp
/// auto iterate_some() {
/// // Iterate step from 0 to 9
/// return range_loop([] (int step) {
/// return do_something(i).then([] {
/// // You don't have to return a result here
/// });
/// }, 0, 10);
/// }
/// ```
///
/// \param callable The callable type which must return a cti::continuable_base
/// which then resolves to a cti::result of arbitrary values.
///
/// \param begin The iterator to iterate over
///
/// \param end The iterator to iterate until
///
/// \since 4.0.0
///
template <typename Callable, typename Iterator>
auto range_loop(Callable&& callable, Iterator begin, Iterator end) {
return detail::operations::loop( //
detail::operations::make_range_looper(std::forward<Callable>(callable),
begin, end));
}
/// \}
} // namespace cti
#endif // CONTINUABLE_OPERATIONS_LOOP_HPP_INCLUDED
// #include <continuable/operations/split.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_OPERATIONS_SPLIT_HPP_INCLUDED
#define CONTINUABLE_OPERATIONS_SPLIT_HPP_INCLUDED
#include <utility>
// #include <continuable/detail/operations/split.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_OPERATIONS_SPLIT_HPP_INCLUDED
#define CONTINUABLE_DETAIL_OPERATIONS_SPLIT_HPP_INCLUDED
#include <tuple>
#include <utility>
// #include <continuable/continuable-base.hpp>
// #include <continuable/continuable-traverse.hpp>
// #include <continuable/continuable-types.hpp>
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
namespace detail {
namespace operations {
template <typename T, bool Else, typename = void>
struct operator_bool_or {
template <typename O>
static bool get(O&& /*obj*/) noexcept {
return Else;
}
};
template <typename T, bool Else>
struct operator_bool_or<T, Else,
traits::void_t<decltype(bool(std::declval<T&>()))>> {
template <typename O>
static bool get(O&& obj) noexcept {
return bool(obj);
}
};
template <typename First, typename... Promises>
class split_promise {
First first_;
std::tuple<Promises...> promises_;
public:
explicit split_promise(First first, Promises... promises)
: first_(std::move(first)), promises_(std::move(promises)...) {
}
template <typename... Args>
void operator()(Args&&... args) && {
traverse_pack(
[&](auto&& promise) mutable -> void {
using accessor =
operator_bool_or<traits::unrefcv_t<decltype(promise)>, true>;
if (accessor::get(promise)) {
std::forward<decltype(promise)>(promise)(args...);
}
},
std::move(promises_));
if (operator_bool_or<First, true>::get(first_)) {
std::move(first_)(std::forward<Args>(args)...);
}
}
template <typename... Args>
void set_value(Args... args) noexcept {
std::move (*this)(std::move(args)...);
}
void set_exception(exception_t error) noexcept {
std::move (*this)(exception_arg_t{}, std::move(error));
}
void set_canceled() noexcept {
std::move (*this)(exception_arg_t{}, exception_t{});
}
explicit operator bool() const noexcept {
bool is_valid = operator_bool_or<First, true>::get(first_);
traverse_pack(
[&](auto&& promise) mutable -> void {
using accessor =
operator_bool_or<traits::unrefcv_t<decltype(promise)>, true>;
if (!is_valid && accessor::get(promise)) {
is_valid = true;
}
},
promises_);
return is_valid;
}
};
} // namespace operations
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_OPERATIONS_SPLIT_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
/// \ingroup Operations
/// \{
/// Splits the asynchronous control flow and merges multiple promises/callbacks
/// together, which take the same types of arguments, into one.
///
/// The invocation order of all promises is undefined.
///
/// The split function is the opposite of the connection functions
/// like `when_all` because is can merge multiple waiters together rather than
/// joining those.
///
/// The split function can be used to resolve multiple waiters when resolving
/// a single promise.
/// ```cpp
/// class my_class {
/// cti::promise<> promise_;
///
/// public:
/// cti::continuable<> wait_for_sth() {
/// return [this](auto&& promise) mutable {
/// // Make sure accessing promise_ is done in a thread safe way!
/// promise_ = cti::split(std::move(promise_),
/// std::forward<decltype(promise)>(promise));
/// };
/// }
///
/// void resolve_all() {
/// // Resolves all waiting promises
/// promise_.set_value();
/// }
/// };
/// ```
///
/// \note The split function only works if all asynchronous arguments are
/// copyable. All asynchronous arguments and exceptions will be passed
/// to all split promises.
///
/// \param promises The promises to split the control flow into,
/// can be single promises or heterogeneous or homogeneous
/// containers of promises (see traverse_pack for a description
/// of supported nested arguments).
///
/// \returns A new promise with the same asynchronous result types as
/// the given promises.
///
/// \since 4.0.0
///
template <typename... Promises>
auto split(Promises&&... promises) {
return detail::operations::split_promise<
detail::traits::unrefcv_t<Promises>...>(
std::forward<Promises>(promises)...);
}
/// \}
} // namespace cti
#endif // CONTINUABLE_OPERATIONS_SPLIT_HPP_INCLUDED
#endif // CONTINUABLE_OPERATIONS_HPP_INCLUDED
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-promise-base.hpp>
// #include <continuable/continuable-promisify.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_PROMISIFY_HPP_INCLUDED
#define CONTINUABLE_PROMISIFY_HPP_INCLUDED
#include <type_traits>
#include <utility>
// #include <continuable/detail/other/promisify.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_PROMISIFY_HPP_INCLUDED
#define CONTINUABLE_DETAIL_PROMISIFY_HPP_INCLUDED
#include <type_traits>
// #include <continuable/continuable-base.hpp>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/traits.hpp>
// #include <continuable/detail/utility/util.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
#include <exception>
#endif // CONTINUABLE_HAS_EXCEPTIONS
namespace cti {
namespace detail {
namespace convert {
/// A resolver for promisifying asio and js style callbacks.
inline auto default_resolver() {
return [](auto&& promise, auto&& e, auto&&... args) {
static_assert(
std::is_convertible<std::decay_t<decltype(e)>, exception_t>::value,
"The given error type must be convertible to the error type used! "
"Specify a custom resolver in order to apply a conversion to the "
"used error type.");
if (e) {
promise.set_exception(std::forward<decltype(e)>(e));
} else {
promise.set_value(std::forward<decltype(args)>(args)...);
}
};
}
template <typename... Result>
struct promisify_helper {
template <typename Resolver, typename Callable, typename... Args>
static auto from(Resolver&& resolver, Callable&& callable, Args&&... args) {
return make_continuable<Result...>(
[resolver = std::forward<Resolver>(resolver),
args = traits::make_flat_tuple(std::forward<Callable>(callable),
std::forward<Args>(args)...)](
auto&& promise) mutable {
traits::unpack(
[promise = std::forward<decltype(promise)>(promise),
&resolver](auto&&... args) mutable {
// Call the resolver from with the promise and result
auto callback =
[resolver = std::move(resolver),
promise = std::move(promise)](auto&&... args) mutable {
resolver(std::move(promise),
std::forward<decltype(args)>(args)...);
};
// Invoke the callback taking function
util::invoke(std::forward<decltype(args)>(args)...,
std::move(callback));
},
std::move(args));
});
}
};
} // namespace convert
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_PROMISIFY_HPP_INCLUDED
namespace cti {
/// \defgroup Promisify Promisify
/// provides helper methods to convert various callback styles to
/// \link continuable_base continuable_bases\endlink.
/// \{
/// Helper class for converting callback taking callable types into a
/// a continuable. Various styles are supported.
/// - `from`: Converts callback taking callable types into continuables
/// which pass an error code as first parameter and the rest of
/// the result afterwards.
///
/// \tparam Result The result of the converted continuable, this should align
/// with the arguments that are passed to the callback.
///
/// \since 3.0.0
template <typename... Result>
class promisify {
using helper = detail::convert::promisify_helper<Result...>;
public:
/// Converts callback taking callable types into a continuable.
/// This applies to calls which pass an error code as first parameter
/// and the rest of the asynchronous result afterwards.
///
/// See an example of how to promisify boost asio's async_resolve below:
/// ```cpp
/// auto async_resolve(std::string host, std::string service) {
/// return cti::promisify<asio::ip::udp::resolver::iterator>::from(
/// [&](auto&&... args) {
/// resolver_.async_resolve(std::forward<decltype(args)>(args)...);
/// },
/// std::move(host), std::move(service));
/// }
/// ```
///
/// A given error variable is converted to the used error type.
/// If this isn't possible you need to create a custom resolver callable
/// object \see with for details.
///
/// \since 3.0.0
template <typename Callable, typename... Args>
static auto from(Callable&& callable, Args&&... args) {
return helper::template from(detail::convert::default_resolver(),
std::forward<Callable>(callable),
std::forward<Args>(args)...);
}
/// \copybrief from
///
/// This modification of \ref from additionally takes a resolver callable
/// object which is used to resolve the promise from the given result.
///
/// See an example of how to promisify boost asio's async_resolve below:
/// ```cpp
/// auto async_resolve(std::string host, std::string service) {
/// return cti::promisify<asio::ip::udp::resolver::iterator>::with(
/// [](auto&& promise, auto&& e, auto&&... args) {
/// if (e) {
/// promise.set_exception(std::forward<decltype(e)>(e));
/// } else {
/// promise.set_value(std::forward<decltype(args)>(args)...);
/// }
/// },
/// [&](auto&&... args) {
/// resolver_.async_resolve(std::forward<decltype(args)>(args)...);
/// },
/// std::move(host), std::move(service));
/// }
/// ```
///
/// \since 4.0.0
template <typename Resolver, typename Callable, typename... Args>
static auto with(Resolver&& resolver, Callable&& callable, Args&&... args) {
return helper::template from(std::forward<Resolver>(resolver),
std::forward<Callable>(callable),
std::forward<Args>(args)...);
}
};
/// \}
} // namespace cti
#endif // CONTINUABLE_PROMISIFY_HPP_INCLUDED
// #include <continuable/continuable-result.hpp>
// #include <continuable/continuable-transforms.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TRANSFORMS_HPP_INCLUDED
#define CONTINUABLE_TRANSFORMS_HPP_INCLUDED
// #include <continuable/transforms/wait.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TRANSFORMS_WAIT_HPP_INCLUDED
#define CONTINUABLE_TRANSFORMS_WAIT_HPP_INCLUDED
#include <chrono>
#include <condition_variable>
#include <utility>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/transforms/wait.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TRANSFORMS_WAIT_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TRANSFORMS_WAIT_HPP_INCLUDED
#include <atomic>
#include <cassert>
#include <condition_variable>
#include <memory>
#include <mutex>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/continuable-result.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
# include <exception>
#endif
namespace cti {
namespace detail {
namespace transforms {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
class wait_transform_canceled_exception : public std::exception {
public:
wait_transform_canceled_exception() noexcept = default;
char const* what() const noexcept override {
return "cti::transforms::wait canceled due to cancellation of the "
"continuation";
}
};
#endif // CONTINUABLE_HAS_EXCEPTIONS
template <typename Hint>
struct sync_trait;
template <typename... Args>
struct sync_trait<identity<Args...>> {
using result_t = result<Args...>;
};
using lock_t = std::unique_lock<std::mutex>;
using condition_variable_t = std::condition_variable;
template <typename Result>
struct unsafe_unlocker {
explicit unsafe_unlocker(std::atomic_bool* ready, condition_variable_t* cv,
std::mutex* mutex, Result* result)
: ready_(ready)
, cv_(cv)
, mutex_(mutex)
, result_(result) {}
unsafe_unlocker(unsafe_unlocker const&) = delete;
unsafe_unlocker(unsafe_unlocker&&) = default;
unsafe_unlocker& operator=(unsafe_unlocker const&) = delete;
unsafe_unlocker& operator=(unsafe_unlocker&&) = default;
~unsafe_unlocker() {
unlock(Result::empty());
}
template <typename... Args>
void operator()(Args&&... args) {
unlock(Result::from(std::forward<Args>(args)...));
}
void unlock(Result&& result) {
if (!ownership_.is_acquired()) {
return;
}
ownership_.release();
lock_t lock(*mutex_);
*result_ = std::move(result);
assert(!ready_->load(std::memory_order_acquire));
ready_->store(true, std::memory_order_release);
cv_->notify_all();
}
std::atomic_bool* ready_;
condition_variable_t* cv_;
std::mutex* mutex_;
Result* result_;
util::ownership ownership_;
};
template <typename Data, typename Annotation,
typename Result = typename sync_trait<Annotation>::result_t>
Result wait_relaxed(continuable_base<Data, Annotation>&& continuable) {
// Do an immediate unpack if the continuable is ready
if (continuable.is_ready()) {
return std::move(continuable).unpack();
}
condition_variable_t cv;
std::mutex cv_mutex;
std::atomic_bool ready{false};
Result sync_result;
std::move(continuable)
.next(unsafe_unlocker<Result>{
&ready,
&cv,
&cv_mutex,
&sync_result,
})
.done();
lock_t lock(cv_mutex);
if (!ready.load(std::memory_order_acquire)) {
cv.wait(lock, [&] {
return ready.load(std::memory_order_acquire);
});
}
return sync_result;
}
/// Transforms the continuation to sync execution and unpacks the result the if
/// possible
template <typename Data, typename Annotation>
auto wait_and_unpack(continuable_base<Data, Annotation>&& continuable) {
auto sync_result = wait_relaxed(std::move(continuable));
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
if (sync_result.is_value()) {
return std::move(sync_result).get_value();
} else if (sync_result.is_exception()) {
if (sync_result.is_exception()) {
if (exception_t e = sync_result.get_exception()) {
std::rethrow_exception(e);
}
}
}
throw wait_transform_canceled_exception();
#else
return sync_result;
#endif // CONTINUABLE_HAS_EXCEPTIONS
}
template <typename Result>
struct wait_frame {
std::mutex cv_mutex;
std::mutex rw_mutex;
condition_variable_t cv;
std::atomic_bool ready{false};
Result sync_result;
};
template <typename Result>
struct unlocker {
unlocker(unlocker const&) = delete;
unlocker(unlocker&&) = default;
unlocker& operator=(unlocker const&) = delete;
unlocker& operator=(unlocker&&) = default;
~unlocker() {
unlock(Result::empty());
}
template <typename... Args>
void operator()(Args&&... args) {
unlock(Result::from(std::forward<decltype(args)>(args)...));
}
void unlock(Result&& result) {
if (!ownership_.is_acquired()) {
return;
}
ownership_.release();
if (auto locked = frame_.lock()) {
{
std::lock_guard<std::mutex> rw_lock(locked->rw_mutex);
assert(!locked->ready.load(std::memory_order_acquire));
locked->sync_result = std::move(result);
}
locked->ready.store(true, std::memory_order_release);
locked->cv.notify_all();
}
}
std::weak_ptr<wait_frame<Result>> frame_;
util::ownership ownership_;
};
template <typename Data, typename Annotation, typename Waiter,
typename Result = typename sync_trait<Annotation>::result_t>
Result wait_unsafe(continuable_base<Data, Annotation>&& continuable,
Waiter&& waiter) {
// Do an immediate unpack if the continuable is ready
if (continuable.is_ready()) {
return std::move(continuable).unpack();
}
using frame_t = wait_frame<Result>;
auto frame = std::make_shared<frame_t>();
std::move(continuable)
.next(unlocker<Result>{std::weak_ptr<frame_t>(frame)})
.done();
if (!frame->ready.load(std::memory_order_acquire)) {
lock_t lock(frame->cv_mutex);
std::forward<Waiter>(waiter)(frame->cv, lock, [&] {
return frame->ready.load(std::memory_order_acquire);
});
}
return ([&] {
std::lock_guard<std::mutex> rw_lock(frame->rw_mutex);
Result cached = std::move(frame->sync_result);
return cached;
})();
}
} // namespace transforms
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TRANSFORMS_WAIT_HPP_INCLUDED
namespace cti {
/// \ingroup Transforms
/// \{
namespace transforms {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
/// Is thrown from wait if the awaited continuable_base was cancelled,
/// which was signaled through resolving with a default
/// constructed exception type.
///
/// \since 4.1.0
using wait_transform_canceled_exception = detail::transforms::wait_transform_canceled_exception;
#endif // CONTINUABLE_HAS_EXCEPTIONS
/// Returns a transform that if applied to a continuable,
/// it will start the continuation chain and returns the result synchronously.
/// The current thread is blocked until the continuation chain is finished.
///
/// \returns Returns a value that is available immediately.
/// The signature of the future depends on the result type:
/// | Continuation type | Return type |
/// | : ------------------------------- | : -------------------------------- |
/// | `continuable_base with <>` | `void` |
/// | `continuable_base with <Arg>` | `Arg` |
/// | `continuable_base with <Args...>` | `std::tuple<Args...>` |
///
/// \throws wait_transform_canceled_exception if the awaited continuable_base
/// is cancelled, and thus was resolved with a default
/// constructed exception type.
///
/// \attention If exceptions are used, exceptions that are thrown, are rethrown
/// synchronously.
///
/// \since 4.0.0
inline auto wait() {
return [](auto&& continuable) {
return detail::transforms::wait_and_unpack(
std::forward<decltype(continuable)>(continuable));
};
}
/// \copybrief wait
///
/// \returns Returns a result that is available immediately.
/// The signature of the future depends on the result type:
/// | Continuation type | Return type |
/// | : ------------------------------- | : -------------------------------- |
/// | `continuable_base with <>` | `result<>` |
/// | `continuable_base with <Arg>` | `result<Arg>` |
/// | `continuable_base with <Args...>` | `result<Args...>` |
///
/// \attention Thrown exceptions returned through the result, also
/// make sure to check for a valid result value in case the
/// underlying time constraint timed out.
///
/// \since 4.0.0
template <typename Rep, typename Period>
auto wait_for(std::chrono::duration<Rep, Period> duration) {
return [duration](auto&& continuable) {
return detail::transforms::wait_unsafe(
std::forward<decltype(continuable)>(continuable),
[duration](detail::transforms::condition_variable_t& cv,
detail::transforms::lock_t& lock, auto&& predicate) {
cv.wait_for(lock, duration,
std::forward<decltype(predicate)>(predicate));
});
};
}
/// \copydoc wait_for
template <typename Clock, typename Duration>
auto wait_until(std::chrono::time_point<Clock, Duration> time_point) {
return [time_point](auto&& continuable) {
return detail::transforms::wait_unsafe(
std::forward<decltype(continuable)>(continuable),
[time_point](detail::transforms::condition_variable_t& cv,
detail::transforms::lock_t& lock, auto&& predicate) {
cv.wait_until(lock, time_point,
std::forward<decltype(predicate)>(predicate));
});
};
}
} // namespace transforms
/// \}
} // namespace cti
#endif // CONTINUABLE_TRANSFORMS_WAIT_HPP_INCLUDED
// #include <continuable/transforms/future.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TRANSFORMS_FUTURE_HPP_INCLUDED
#define CONTINUABLE_TRANSFORMS_FUTURE_HPP_INCLUDED
#include <utility>
// #include <continuable/detail/transforms/future.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_TRANSFORMS_FUTURE_HPP_INCLUDED
#define CONTINUABLE_DETAIL_TRANSFORMS_FUTURE_HPP_INCLUDED
#include <future>
// #include <continuable/continuable-primitives.hpp>
// #include <continuable/detail/core/annotation.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/core/types.hpp>
// #include <continuable/detail/features.hpp>
// #include <continuable/detail/utility/util.hpp>
namespace cti {
namespace detail {
/// Provides helper functions to transform continuations to other types
namespace transforms {
/// Provides helper functions and typedefs for converting callback arguments
/// to their types a promise can accept.
template <typename... Args>
struct future_trait {
/// The promise type used to create the future
using promise_t = std::promise<std::tuple<Args...>>;
/// Boxes the argument pack into a tuple
static void resolve(promise_t& promise, Args... args) {
promise.set_value(std::make_tuple(std::move(args)...));
}
};
template <>
struct future_trait<> {
/// The promise type used to create the future
using promise_t = std::promise<void>;
/// Boxes the argument pack into void
static void resolve(promise_t& promise) {
promise.set_value();
}
};
template <typename First>
struct future_trait<First> {
/// The promise type used to create the future
using promise_t = std::promise<First>;
/// Boxes the argument pack into nothing
static void resolve(promise_t& promise, First first) {
promise.set_value(std::move(first));
}
};
template <typename Hint>
class promise_callback;
template <typename... Args>
class promise_callback<identity<Args...>> : public future_trait<Args...> {
typename future_trait<Args...>::promise_t promise_;
public:
constexpr promise_callback() = default;
promise_callback(promise_callback const&) = delete;
constexpr promise_callback(promise_callback&&) = default;
promise_callback& operator=(promise_callback const&) = delete;
promise_callback& operator=(promise_callback&&) = delete;
/// Resolves the promise
void operator()(Args... args) {
this->resolve(promise_, std::move(args)...);
}
/// Resolves the promise through the exception
void operator()(exception_arg_t, exception_t error) {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
promise_.set_exception(error);
#else
(void)error;
// Can't forward a std::error_condition or custom error type
// to a std::promise. Handle the error first in order
// to prevent this trap!
CTI_DETAIL_TRAP();
#endif // CONTINUABLE_HAS_EXCEPTIONS
}
/// Returns the future from the promise
auto get_future() {
return promise_.get_future();
}
};
/// Transforms the continuation to a future
template <typename Data, typename Annotation>
auto to_future(continuable_base<Data, Annotation>&& continuable) {
// Create the promise which is able to supply the current arguments
constexpr auto const hint =
base::annotation_of(identify<decltype(continuable)>{});
promise_callback<std::decay_t<decltype(hint)>> callback;
(void)hint;
// Receive the future
auto future = callback.get_future();
// Dispatch the continuation with the promise resolving callback
std::move(continuable).next(std::move(callback)).done();
return future;
}
} // namespace transforms
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_TRANSFORMS_FUTURE_HPP_INCLUDED
namespace cti {
/// \ingroup Transforms
/// \{
namespace transforms {
/// Returns a transform that if applied to a continuable,
/// it will start the continuation chain and returns the asynchronous
/// result as `std::future<...>`.
///
/// \returns Returns a `std::future<...>` which becomes ready as soon
/// as the the continuation chain has finished.
/// The signature of the future depends on the result type:
/// | Continuation type | Return type |
/// | : ------------------------------- | : -------------------------------- |
/// | `continuable_base with <>` | `std::future<void>` |
/// | `continuable_base with <Arg>` | `std::future<Arg>` |
/// | `continuable_base with <Args...>` | `std::future<std::tuple<Args...>>` |
///
/// \attention If exceptions are used, exceptions that are thrown, are forwarded
/// to the returned future. If there are no exceptions supported,
/// you shall not pass any errors to the end of the asynchronous
/// call chain!
/// Otherwise this will yield a trap that causes application exit.
///
/// \since 2.0.0
inline auto to_future() {
return [](auto&& continuable) {
return detail::transforms::to_future(
std::forward<decltype(continuable)>(continuable));
};
}
} // namespace transforms
/// \}
} // namespace cti
#endif // CONTINUABLE_OPERATIONS_SPLIT_HPP_INCLUDED
namespace cti {
/// \defgroup Transforms Transforms
/// provides utilities to convert
/// \link continuable_base continuable_bases\endlink to other
/// types such as (`std::future`).
/// \{
/// The namespace transforms declares callable objects that transform
/// any continuable_base to an object or to a continuable_base itself.
///
/// Transforms can be applied to continuables through using
/// the cti::continuable_base::apply method accordingly.
namespace transforms {}
} // namespace cti
#endif // CONTINUABLE_TRANSFORMS_HPP_INCLUDED
// #include <continuable/continuable-traverse-async.hpp>
// #include <continuable/continuable-traverse.hpp>
// #include <continuable/continuable-types.hpp>
#endif // CONTINUABLE_HPP_INCLUDED
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_EXTERNAL_ASIO_HPP_INCLUDED
#define CONTINUABLE_EXTERNAL_ASIO_HPP_INCLUDED
// #include <continuable/continuable-base.hpp>
// #include <continuable/detail/external/asio.hpp>
/*
/~` _ _ _|_. _ _ |_ | _
\_,(_)| | | || ||_|(_||_)|(/_
https://github.com/Naios/continuable
v4.2.0
Copyright(c) 2015 - 2022 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_ASIO_HPP_INCLUDED
#define CONTINUABLE_DETAIL_ASIO_HPP_INCLUDED
#include <array>
#include <utility>
// #include <continuable/continuable-base.hpp>
// #include <continuable/detail/core/base.hpp>
// #include <continuable/detail/features.hpp>
#if defined(ASIO_STANDALONE)
# include <asio/async_result.hpp>
# include <asio/error.hpp>
# include <asio/error_code.hpp>
# include <asio/version.hpp>
# if defined(CONTINUABLE_HAS_EXCEPTIONS)
# include <asio/system_error.hpp>
# endif
# if (ASIO_VERSION < 101300) // 1.13.0
# define CTI_DETAIL_ASIO_HAS_NO_INTEGRATION
# elif (ASIO_VERSION < 101600) // 1.16.0 (boost 1.72 baseline)
# define CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION
# endif
# define CTI_DETAIL_ASIO_NAMESPACE_BEGIN namespace asio {
# define CTI_DETAIL_ASIO_NAMESPACE_END }
#else
# include <boost/asio/async_result.hpp>
# include <boost/asio/error.hpp>
# include <boost/system/error_code.hpp>
# include <boost/version.hpp>
# if defined(CONTINUABLE_HAS_EXCEPTIONS)
# include <boost/system/system_error.hpp>
# endif
# if (BOOST_VERSION < 107000) // 1.70
# define CTI_DETAIL_ASIO_HAS_NO_INTEGRATION
# elif (BOOST_VERSION < 107200) // 1.72
# define CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION
# endif
# define CTI_DETAIL_ASIO_NAMESPACE_BEGIN \
namespace boost { \
namespace asio {
# define CTI_DETAIL_ASIO_NAMESPACE_END \
} \
}
#endif
#if defined(CTI_DETAIL_ASIO_HAS_NO_INTEGRATION)
# error "First-class ASIO support for continuable requires the form of "\
"`async_result` with an `initiate` static member function, which was added " \
"in standalone ASIO 1.13.0 and Boost ASIO 1.70. Older versions can be " \
"integrated manually with `cti::promisify`."
#endif
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
# include <exception>
#endif
namespace cti {
namespace detail {
namespace asio {
#if defined(ASIO_STANDALONE)
using error_code_t = ::asio::error_code;
using basic_errors_t = ::asio::error::basic_errors;
# if defined(CONTINUABLE_HAS_EXCEPTIONS)
using system_error_t = ::asio::system_error;
# endif
#else
using error_code_t = ::boost::system::error_code;
using basic_errors_t = ::boost::asio::error::basic_errors;
# if defined(CONTINUABLE_HAS_EXCEPTIONS)
using system_error_t = ::boost::system::system_error;
# endif
#endif
template <typename Promise, typename Token>
class promise_resolver {
public:
explicit promise_resolver(Promise promise, Token token)
: promise_(std::move(promise))
, token_(std::move(token)) {}
template <typename... T>
void operator()(T&&... args) noexcept {
promise_.set_value(std::forward<T>(args)...);
}
template <typename... T>
void operator()(error_code_t e, T&&... args) noexcept {
if (e) {
if (!token_.is_ignored(e)) {
if (token_.is_cancellation(e)) {
promise_.set_canceled();
return;
} else {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
promise_.set_exception(
std::make_exception_ptr(system_error_t(std::move(e))));
#else
promise_.set_exception(exception_t(e.value(), e.category()));
#endif
return;
}
}
}
promise_.set_value(std::forward<T>(args)...);
}
private:
Promise promise_;
Token token_;
};
// Binds `promise` to the first argument of a continuable resolver, giving it
// the signature of an ASIO handler.
template <typename Promise, typename Token>
auto promise_resolver_handler(Promise&& promise, Token&& token) noexcept {
return promise_resolver<std::decay_t<Promise>, std::decay_t<Token>>(
std::forward<Promise>(promise), std::forward<Token>(token));
}
// Helper struct wrapping a call to `cti::make_continuable` and, if needed,
// providing an erased, explicit `return_type` for `async_result`.
template <typename Signature>
struct initiate_make_continuable;
template <typename... Args>
struct initiate_make_continuable<void(Args...)> {
#if defined(CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION)
using erased_return_type = continuable<Args...>;
#endif
template <typename Continuation>
auto operator()(Continuation&& continuation) {
return base::attorney::create_from(std::forward<Continuation>(continuation),
identity<Args...>{}, util::ownership{});
}
};
template <typename... Args>
struct initiate_make_continuable<void(error_code_t, Args...)> {
#if defined(CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION)
using erased_return_type = continuable<Args...>;
#endif
template <typename Continuation>
auto operator()(Continuation&& continuation) {
return base::attorney::create_from(std::forward<Continuation>(continuation),
identity<Args...>{}, util::ownership{});
}
};
template <typename... Args>
struct initiate_make_continuable<void(error_code_t const&, Args...)>
: initiate_make_continuable<void(error_code_t, Args...)> {};
struct map_default {
constexpr map_default() noexcept {}
bool is_cancellation(error_code_t const& ec) const noexcept {
// Continuable uses a default constructed exception type to signal
// cancellation to the followed asynchronous control flow.
return ec == basic_errors_t::operation_aborted;
}
bool is_ignored(error_code_t const& /*ec*/) const noexcept {
return false;
}
};
struct map_none {
constexpr map_none() noexcept {}
bool is_cancellation(error_code_t const& /*ec*/) const noexcept {
return false;
}
bool is_ignored(error_code_t const& /*ec*/) const noexcept {
return false;
}
};
template <std::size_t Size>
class map_ignore {
public:
map_ignore(std::array<basic_errors_t, Size> ignored) noexcept
: ignored_(ignored) {}
bool is_cancellation(error_code_t const& ec) const noexcept {
return ec == basic_errors_t::operation_aborted;
}
bool is_ignored(error_code_t const& ec) const noexcept {
for (basic_errors_t ignored : ignored_) {
if (ec == ignored) {
return true;
}
}
return false;
}
private:
std::array<basic_errors_t, Size> ignored_;
};
} // namespace asio
} // namespace detail
} // namespace cti
#endif // CONTINUABLE_DETAIL_ASIO_HPP_INCLUDED
// #include <continuable/detail/utility/traits.hpp>
namespace cti {
/// The error code type used by your asio distribution
///
/// \since 4.1.0
using asio_error_code_t = detail::asio::error_code_t;
/// The basic error code enum used by your asio distribution
///
/// \since 4.1.0
using asio_basic_errors_t = detail::asio::basic_errors_t;
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
/// The system error type used by your asio distribution
///
/// \since 4.1.0
using asio_system_error_t = detail::asio::system_error_t;
#endif // CONTINUABLE_HAS_EXCEPTIONS
/// Type used as an ASIO completion token to specify an asynchronous operation
/// that should return a continuable_base.
///
/// - Boost 1.70 or asio 1.13.0 is required for the async initiation
/// - Until boost 1.72 or asio 1.16.0 overhead through an additional type
/// erasure is added. It is recommended to update to those versions.
///
/// The special static variable use_continuable can be appended to any
/// (boost) asio function that accepts a callback to make it return a
/// continuable_base.
///
/// ```cpp
/// #include <continuable/continuable.hpp>
/// #include <continuable/external/asio.hpp>
/// #include <asio.hpp>
///
/// // ...
///
/// asio::tcp::resolver resolver(...);
/// resolver.async_resolve("127.0.0.1", "daytime", cti::use_continuable)
/// .then([](asio::udp::resolver::iterator iterator) {
/// // ...
/// });
/// ```
///
/// \tparam Mapper The token can be instantiated with a custom mapper
/// for asio error codes which makes it possible to ignore
/// errors or treat them as cancellation types.
/// The mapper has the following form:
/// ```
/// struct my_mapper {
/// constexpr my_mapper() noexcept {}
///
/// /// Returns true when the error_code_t is a type which represents
/// /// cancellation and
/// bool is_cancellation(error_code_t const& /*ec*/) const noexcept {
/// return false;
/// }
/// bool is_ignored(error_code_t const& /*ec*/) const noexcept {
/// return false;
/// }
/// };
/// ```
///
/// \attention `asio::error::basic_errors::operation_aborted` errors returned
/// by asio are automatically transformed into a default constructed
/// exception type which represents "operation canceled" by the
/// user or program. If you intend to retrieve the full
/// asio::error_code without remapping use the use_continuable_raw_t
/// completion token instead!
///
/// \since 4.0.0
template <typename Mapper = detail::asio::map_default>
struct use_continuable_t : public Mapper {
using Mapper::Mapper;
};
/// \copydoc use_continuable_t
///
/// The raw async completion handler token does not remap the asio error
/// `asio::error::basic_errors::operation_aborted` to a default constructed
/// exception type.
///
/// \since 4.1.0
using use_continuable_raw_t = use_continuable_t<detail::asio::map_none>;
/// Special value for instance of use_continuable_t which performs remapping
/// of asio error codes to align the cancellation behaviour with the library.
///
/// \copydetails use_continuable_t
constexpr use_continuable_t<> use_continuable{};
/// Special value for instance of use_continuable_raw_t which doesn't perform
/// remapping of asio error codes and rethrows the raw error code.
///
/// \copydetails use_continuable_raw_t
constexpr use_continuable_raw_t use_continuable_raw{};
/// Represents a special asio completion token which treats the given
/// asio basic error codes as success instead of failure.
///
/// `asio::error::basic_errors::operation_aborted` is mapped
/// as cancellation token.
///
/// \since 4.1.0
template <typename... Args>
auto use_continuable_ignoring(Args&&... args) noexcept {
return use_continuable_t<detail::asio::map_ignore<sizeof...(Args)>>{
{asio_basic_errors_t(std::forward<Args>(args))...}};
}
} // namespace cti
CTI_DETAIL_ASIO_NAMESPACE_BEGIN
template <typename Signature, typename Matcher>
class async_result<cti::use_continuable_t<Matcher>, Signature> {
public:
#if defined(CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION)
using return_type = typename cti::detail::asio::initiate_make_continuable<
Signature>::erased_return_type;
#endif
template <typename Initiation, typename... Args>
static auto initiate(Initiation initiation,
cti::use_continuable_t<Matcher> token, Args... args) {
return cti::detail::asio::initiate_make_continuable<Signature>{}(
[initiation = std::move(initiation), token = std::move(token),
init_args = std::make_tuple(std::move(args)...)](
auto&& promise) mutable {
cti::detail::traits::unpack(
[initiation = std::move(initiation),
handler = cti::detail::asio::promise_resolver_handler(
std::forward<decltype(promise)>(promise), std::move(token))](
auto&&... args) mutable {
std::move(initiation)(std::move(handler),
std::forward<decltype(args)>(args)...);
},
std::move(init_args));
});
}
};
CTI_DETAIL_ASIO_NAMESPACE_END
#undef CTI_DETAIL_ASIO_NAMESPACE_BEGIN
#undef CTI_DETAIL_ASIO_NAMESPACE_END
#undef CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION
#endif // CONTINUABLE_EXTERNAL_ASIO_HPP_INCLUDED
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment