Created
June 25, 2025 01:21
-
-
Save tcbrindle/bc695b3b01a8c3b67046ca4c1910452e to your computer and use it in GitHub Desktop.
Flux temp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_HPP_INCLUDED | |
#define FLUX_HPP_INCLUDED | |
// Copyright (c) 2024 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_HPP_INCLUDED | |
#define FLUX_ADAPTOR_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_ADJACENT_HPP_INCLUDED | |
#define FLUX_ADAPTOR_ADJACENT_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_HPP_INCLUDED | |
#define FLUX_CORE_HPP_INCLUDED | |
// Copyright (c) 2025 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_AS_RANGE_HPP_INCLUDED | |
#define FLUX_CORE_AS_RANGE_HPP_INCLUDED | |
// Copyright (c) 2025 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_ITERABLE_CONCEPTS_HPP_INCLUDED | |
#define FLUX_CORE_ITERABLE_CONCEPTS_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_CONCEPTS_HPP_INCLUDED | |
#define FLUX_CORE_CONCEPTS_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_UTILS_HPP_INCLUDED | |
#define FLUX_CORE_UTILS_HPP_INCLUDED | |
#include <compare> | |
#include <concepts> | |
#include <functional> | |
#include <type_traits> | |
#include <utility> | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_ASSERT_HPP_INCLUDED | |
#define FLUX_CORE_ASSERT_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_CONFIG_HPP_INCLUDED | |
#define FLUX_CORE_CONFIG_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_MACROS_HPP_INCLUDED | |
#define FLUX_MACROS_HPP_INCLUDED | |
#include <version> | |
#define FLUX_VERSION_MAJOR 0 | |
#define FLUX_VERSION_MINOR 4 | |
#define FLUX_VERSION_PATCH 0 | |
#define FLUX_VERSION_DEVEL 1 // 0 => Release, 1 => development post Major.Minor.Patch | |
#define FLUX_VERSION \ | |
(FLUX_VERSION_MAJOR * 100'000 \ | |
+ FLUX_VERSION_MINOR * 1'000 \ | |
+ FLUX_VERSION_PATCH * 10 \ | |
+ FLUX_VERSION_DEVEL) | |
#define FLUX_FWD(x) static_cast<decltype(x)&&>(x) | |
#define FLUX_DECLVAL(...) ((static_cast<__VA_ARGS__(*)()noexcept>(nullptr))()) | |
#if defined(__GNUC__) | |
# define FLUX_ALWAYS_INLINE [[gnu::always_inline]] inline | |
#elif defined(_MSC_VER) | |
# define FLUX_ALWAYS_INLINE __forceinline | |
#else | |
# define FLUX_ALWAYS_INLINE inline | |
#endif | |
#define FLUX_NO_UNIQUE_ADDRESS [[no_unique_address]] | |
#define FLUX_FOR(_flux_var_decl_, ...) \ | |
if (auto&& _flux_seq_ = __VA_ARGS__; true) \ | |
for (auto _flux_cur_ = ::flux::first(_flux_seq_); \ | |
!::flux::is_last(_flux_seq_, _flux_cur_); \ | |
::flux::inc(_flux_seq_, _flux_cur_)) \ | |
if (_flux_var_decl_ = ::flux::read_at(_flux_seq_, _flux_cur_); true) | |
#define FLUX_ASSERT(cond) (::flux::assert_(cond, "assertion '" #cond "' failed")) | |
#define FLUX_DEBUG_ASSERT(cond) (::flux::assert_(!::flux::config::enable_debug_asserts || (cond), "assertion '" #cond "' failed")); | |
#ifdef FLUX_MODULE_INTERFACE | |
#define FLUX_EXPORT export | |
#else | |
#define FLUX_EXPORT | |
#endif | |
#endif // FLUX_MACROS_HPP_INCLUDED | |
#include <concepts> | |
#include <cstddef> | |
#include <type_traits> | |
#define FLUX_ERROR_POLICY_TERMINATE 1 | |
#define FLUX_ERROR_POLICY_UNWIND 2 | |
#define FLUX_ERROR_POLICY_FAIL_FAST 3 | |
#define FLUX_OVERFLOW_POLICY_ERROR 10 | |
#define FLUX_OVERFLOW_POLICY_WRAP 11 | |
#define FLUX_OVERFLOW_POLICY_IGNORE 12 | |
#define FLUX_DIVIDE_BY_ZERO_POLICY_ERROR 100 | |
#define FLUX_DIVIDE_BY_ZERO_POLICY_IGNORE 101 | |
#define FLUX_INTEGER_CAST_POLICY_CHECKED 1001 | |
#define FLUX_INTEGER_CAST_POLICY_UNCHECKED 1002 | |
// Default error policy is terminate | |
#define FLUX_DEFAULT_ERROR_POLICY FLUX_ERROR_POLICY_TERMINATE | |
// Default overflow policy is error in debug builds, wrap in release builds | |
#ifdef NDEBUG | |
# define FLUX_DEFAULT_OVERFLOW_POLICY FLUX_OVERFLOW_POLICY_WRAP | |
#else | |
# define FLUX_DEFAULT_OVERFLOW_POLICY FLUX_OVERFLOW_POLICY_ERROR | |
#endif // NDEBUG | |
// Default divide by zero policy is error in debug builds, ignore in release builds | |
#ifdef NDEBUG | |
# define FLUX_DEFAULT_DIVIDE_BY_ZERO_POLICY FLUX_DIVIDE_BY_ZERO_POLICY_IGNORE | |
#else | |
# define FLUX_DEFAULT_DIVIDE_BY_ZERO_POLICY FLUX_DIVIDE_BY_ZERO_POLICY_ERROR | |
#endif // NDEBUG | |
// Select which error policy to use | |
#if defined(FLUX_TERMINATE_ON_ERROR) | |
# define FLUX_ERROR_POLICY FLUX_ERROR_POLICY_TERMINATE | |
#elif defined(FLUX_UNWIND_ON_ERROR) | |
# define FLUX_ERROR_POLICY FLUX_ERROR_POLICY_UNWIND | |
#elif defined(FLUX_FAIL_FAST_ON_ERROR) | |
# define FLUX_ERROR_POLICY FLUX_ERROR_POLICY_FAIL_FAST | |
#else | |
# define FLUX_ERROR_POLICY FLUX_DEFAULT_ERROR_POLICY | |
#endif // FLUX_TERMINATE_ON_ERROR | |
// Default integer cast policy is checked in debug builds, unchecked in release builds | |
#ifdef NDEBUG | |
# define FLUX_DEFAULT_INTEGER_CAST_POLICY FLUX_INTEGER_CAST_POLICY_UNCHECKED | |
#else | |
# define FLUX_DEFAULT_INTEGER_CAST_POLICY FLUX_INTEGER_CAST_POLICY_CHECKED | |
#endif // NDEBUG | |
// Should we print an error message before terminating? | |
#ifndef FLUX_PRINT_ERROR_ON_TERMINATE | |
# define FLUX_PRINT_ERROR_ON_TERMINATE 1 | |
#endif // FLUX_PRINT_ERROR_ON_TERMINATE | |
// Should we test debug assertions? | |
#ifndef FLUX_ENABLE_DEBUG_ASSERTS | |
# ifdef NDEBUG | |
# define FLUX_ENABLE_DEBUG_ASSERTS 0 | |
# else | |
# define FLUX_ENABLE_DEBUG_ASSERTS 1 | |
# endif | |
#endif | |
// Select which overflow policy to use | |
#if defined(FLUX_ERROR_ON_OVERFLOW) | |
# define FLUX_OVERFLOW_POLICY FLUX_OVERFLOW_POLICY_ERROR | |
#elif defined(FLUX_WRAP_ON_OVERFLOW) | |
# define FLUX_OVERFLOW_POLICY FLUX_OVERFLOW_POLICY_WRAP | |
#elif defined(FLUX_IGNORE_OVERFLOW) | |
# define FLUX_OVERFLOW_POLICY FLUX_OVERFLOW_POLICY_IGNORE | |
#else | |
# define FLUX_OVERFLOW_POLICY FLUX_DEFAULT_OVERFLOW_POLICY | |
#endif // FLUX_ERROR_ON_OVERFLOW | |
// Select which divide by zero policy to use | |
#if defined(FLUX_ERROR_ON_DIVIDE_BY_ZERO) | |
# define FLUX_DIVIDE_BY_ZERO_POLICY FLUX_DIVIDE_BY_ZERO_POLICY_ERROR | |
#elif defined(FLUX_IGNORE_DIVIDE_BY_ZERO) | |
# define FLUX_DIVIDE_BY_ZERO_POLICY FLUX_DIVIDE_BY_ZERO_POLICY_IGNORE | |
#else | |
# define FLUX_DIVIDE_BY_ZERO_POLICY FLUX_DEFAULT_DIVIDE_BY_ZERO_POLICY | |
#endif // FLUX_ERROR_ON_DIVIDE_BY_ZERO | |
// Select which integer cast policy to use | |
#if defined(FLUX_CHECKED_INTEGER_CASTS) | |
# define FLUX_INTEGER_CAST_POLICY FLUX_INTEGER_CAST_POLICY_CHECKED | |
#elif defined(FLUX_UNCHECKED_INTEGER_CASTS) | |
# define FLUX_INTEGER_CAST_POLICY FLUX_INTEGER_CAST_POLICY_UNCHECKED | |
#else | |
# define FLUX_INTEGER_CAST_POLICY FLUX_DEFAULT_INTEGER_CAST_POLICY | |
#endif | |
// Should we try to use static bounds checking? | |
#if !defined(FLUX_DISABLE_STATIC_BOUNDS_CHECKING) | |
# if defined(__has_cpp_attribute) && defined(__has_builtin) | |
# if __has_builtin(__builtin_constant_p) && __has_cpp_attribute(gnu::error) | |
# define FLUX_HAVE_GCC_STATIC_BOUNDS_CHECKING 1 | |
# endif | |
# endif | |
#endif // FLUX_DISABLE_STATIC_BOUNDS_CHECKING | |
// Default int_t is ptrdiff_t | |
#define FLUX_DEFAULT_INT_TYPE std::ptrdiff_t | |
// Select which int type to use | |
#ifndef FLUX_INT_TYPE | |
#define FLUX_INT_TYPE FLUX_DEFAULT_INT_TYPE | |
#endif | |
namespace flux { | |
FLUX_EXPORT | |
enum class error_policy { | |
terminate = FLUX_ERROR_POLICY_TERMINATE, | |
unwind = FLUX_ERROR_POLICY_UNWIND, | |
fail_fast = FLUX_ERROR_POLICY_FAIL_FAST | |
}; | |
FLUX_EXPORT | |
enum class overflow_policy { | |
ignore = FLUX_OVERFLOW_POLICY_IGNORE, | |
wrap = FLUX_OVERFLOW_POLICY_WRAP, | |
error = FLUX_OVERFLOW_POLICY_ERROR | |
}; | |
FLUX_EXPORT | |
enum class divide_by_zero_policy { | |
ignore = FLUX_DIVIDE_BY_ZERO_POLICY_IGNORE, | |
error = FLUX_DIVIDE_BY_ZERO_POLICY_ERROR | |
}; | |
FLUX_EXPORT | |
enum class integer_cast_policy { | |
checked = FLUX_INTEGER_CAST_POLICY_CHECKED, | |
unchecked = FLUX_INTEGER_CAST_POLICY_UNCHECKED | |
}; | |
namespace config { | |
FLUX_EXPORT | |
using int_type = FLUX_INT_TYPE; | |
static_assert(std::signed_integral<int_type> && (sizeof(int_type) >= sizeof(std::ptrdiff_t)), | |
"Custom FLUX_INT_TYPE must be a signed integer type at least as large as ptrdiff_t"); | |
FLUX_EXPORT | |
inline constexpr error_policy on_error = static_cast<error_policy>(FLUX_ERROR_POLICY); | |
FLUX_EXPORT | |
inline constexpr overflow_policy on_overflow = static_cast<overflow_policy>(FLUX_OVERFLOW_POLICY); | |
FLUX_EXPORT | |
inline constexpr divide_by_zero_policy on_divide_by_zero = static_cast<divide_by_zero_policy>(FLUX_DIVIDE_BY_ZERO_POLICY); | |
FLUX_EXPORT | |
inline constexpr integer_cast_policy on_integer_cast = static_cast<integer_cast_policy>(FLUX_INTEGER_CAST_POLICY); | |
FLUX_EXPORT | |
inline constexpr bool print_error_on_terminate = FLUX_PRINT_ERROR_ON_TERMINATE; | |
FLUX_EXPORT | |
inline constexpr bool enable_debug_asserts = FLUX_ENABLE_DEBUG_ASSERTS; | |
} // namespace config | |
} // namespace flux | |
#endif | |
#include <cstdio> | |
#include <exception> | |
#include <source_location> | |
#include <stdexcept> | |
#include <type_traits> | |
#if defined(__has_builtin) | |
# if __has_builtin(__builtin_trap) | |
# define FLUX_HAS_BUILTIN_TRAP 1 | |
# endif | |
#elif defined(_MSC_VER) | |
# include <intrin.h> | |
# define FLUX_HAS_FASTFAIL 1 | |
#endif | |
namespace flux { | |
FLUX_EXPORT | |
struct unrecoverable_error : std::logic_error { | |
explicit inline unrecoverable_error(char const* msg) : std::logic_error(msg) {} | |
}; | |
namespace detail { | |
struct runtime_error_fn { | |
private: | |
[[noreturn]] | |
FLUX_ALWAYS_INLINE | |
static void fail_fast() | |
{ | |
#if FLUX_HAS_BUILTIN_TRAP | |
__builtin_trap(); | |
#elif FLUX_HAS_FASTFAIL | |
__fastfail(7); // FAST_FAIL_FATAL_APP_EXIT | |
#else | |
std::abort(); | |
#endif | |
} | |
[[noreturn]] | |
static void unwind(const char* msg, std::source_location loc) | |
{ | |
char buf[1024]; | |
std::snprintf(buf, 1024, "%s:%u: Fatal error: %s", | |
loc.file_name(), loc.line(), msg); | |
throw unrecoverable_error(buf); | |
} | |
[[noreturn]] | |
static void terminate(const char* msg, std::source_location loc) | |
{ | |
if constexpr (config::print_error_on_terminate) { | |
std::fprintf(stderr, "%s:%u: Fatal error: %s\n", | |
loc.file_name(), loc.line(), msg); | |
} | |
std::terminate(); | |
} | |
public: | |
[[noreturn]] | |
FLUX_ALWAYS_INLINE | |
void operator()(char const* msg, | |
std::source_location loc = std::source_location::current()) const | |
{ | |
if constexpr (config::on_error == error_policy::fail_fast) { | |
fail_fast(); | |
} else if constexpr (config::on_error == error_policy::unwind) { | |
unwind(msg, loc); | |
} else { | |
terminate(msg, loc); | |
} | |
} | |
}; | |
} | |
FLUX_EXPORT inline constexpr auto runtime_error = detail::runtime_error_fn{}; | |
#ifdef FLUX_HAVE_GCC_STATIC_BOUNDS_CHECKING | |
[[gnu::error("out-of-bounds sequence access detected")]] | |
void static_bounds_check_failed(); // not defined | |
#endif | |
namespace detail { | |
struct assert_fn { | |
inline constexpr void operator()(bool cond, char const* msg, | |
std::source_location loc = std::source_location::current()) const | |
{ | |
if (cond) { | |
return; | |
} else { | |
runtime_error(msg, std::move(loc)); | |
} | |
} | |
}; | |
struct bounds_check_fn { | |
inline constexpr void operator()(bool cond, std::source_location loc = std::source_location::current()) const | |
{ | |
if (!std::is_constant_evaluated()) { | |
assert_fn{}(cond, "out of bounds sequence access", std::move(loc)); | |
} | |
} | |
}; | |
struct indexed_bounds_check_fn { | |
template <typename T> | |
inline constexpr void operator()(T idx, T limit, | |
std::source_location loc = std::source_location::current()) const | |
{ | |
if (!std::is_constant_evaluated()) { | |
#ifdef FLUX_HAVE_GCC_STATIC_BOUNDS_CHECKING | |
if (__builtin_constant_p(idx) && __builtin_constant_p(limit)) { | |
if (idx < T{0} || idx >= limit) { | |
static_bounds_check_failed(); | |
} | |
} | |
#endif | |
assert_fn{}(idx >= T{0} && idx < limit, "out-of-bounds sequence access", loc); | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto assert_ = detail::assert_fn{}; | |
FLUX_EXPORT inline constexpr auto bounds_check = detail::bounds_check_fn{}; | |
FLUX_EXPORT inline constexpr auto indexed_bounds_check = detail::indexed_bounds_check_fn{}; | |
} // namespace flux | |
#endif // FLUX_CORE_ASSERT_HPP_INCLUDED | |
namespace flux { | |
/* | |
* Useful helpers | |
*/ | |
FLUX_EXPORT | |
template <typename From, typename To> | |
concept decays_to = std::same_as<std::remove_cvref_t<From>, To>; | |
FLUX_EXPORT | |
template <typename T, typename U> | |
concept same_decayed = std::same_as<std::remove_cvref_t<T>, | |
std::remove_cvref_t<U>>; | |
namespace detail { | |
struct copy_fn { | |
template <typename T> | |
[[nodiscard]] | |
constexpr auto operator()(T&& arg) const | |
noexcept(std::is_nothrow_convertible_v<T, std::decay_t<T>>) | |
-> std::decay_t<T> | |
{ | |
return FLUX_FWD(arg); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto copy = detail::copy_fn{}; | |
struct immovable { | |
immovable() = default; | |
~immovable() = default; | |
immovable(immovable const&) = delete; | |
immovable(immovable&&) = delete; | |
immovable& operator=(immovable const&) = delete; | |
immovable& operator=(immovable&&) = delete; | |
}; | |
namespace detail { | |
template <typename T, typename... U> | |
concept any_of = (std::same_as<T, U> || ...); | |
template <typename T, typename Cat> | |
concept compares_as = std::same_as<std::common_comparison_category_t<T, Cat>, Cat>; | |
template <typename Fn, typename T, typename U, typename Cat> | |
concept ordering_invocable_ = | |
std::regular_invocable<Fn, T, U> && | |
compares_as<std::decay_t<std::invoke_result_t<Fn, T, U>>, Cat>; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Fn, typename T, typename U, typename Cat = std::partial_ordering> | |
concept ordering_invocable = | |
detail::ordering_invocable_<Fn, T, U, Cat> && | |
detail::ordering_invocable_<Fn, U, T, Cat> && | |
detail::ordering_invocable_<Fn, T, T, Cat> && | |
detail::ordering_invocable_<Fn, U, U, Cat>; | |
FLUX_EXPORT template <typename Fn, typename... Args> | |
using callable_result_t = decltype(std::declval<Fn>()(std::declval<Args>()...)); | |
namespace detail { | |
template <typename Fn, typename... Args> | |
concept can_call_ = requires { typename callable_result_t<Fn, Args...>; }; | |
template <typename Fn, typename R, typename... Args> | |
concept can_call_r | |
= can_call_<Fn, Args...> && std::convertible_to<callable_result_t<Fn, Args...>, R>; | |
template <typename, typename> | |
inline constexpr bool callable_ = false; | |
template <typename Fn, typename R, typename... Args> | |
inline constexpr bool callable_<Fn, R(Args...)> | |
= std::is_void_v<R> ? can_call_<Fn, Args...> : can_call_r<Fn, R, Args...>; | |
} // namespace detail | |
FLUX_EXPORT template <typename Fn, typename Sig> | |
concept callable_once = detail::callable_<Fn, Sig>; | |
FLUX_EXPORT template <typename Fn, typename Sig> | |
concept callable_mut = detail::callable_<Fn&, Sig> && callable_once<Fn, Sig>; | |
FLUX_EXPORT template <typename Fn, typename Sig> | |
concept callable = detail::callable_<Fn const&, Sig> && callable_mut<Fn, Sig>; | |
namespace detail { | |
template <callable_once<void()> Fn> | |
struct [[nodiscard("Discarded defer_t will execute its associated function immediately")]] defer_t { | |
FLUX_NO_UNIQUE_ADDRESS Fn fn; | |
constexpr ~defer_t() { fn(); } | |
}; | |
template <typename F> | |
defer_t(F) -> defer_t<F>; | |
template <typename Fn> | |
constexpr auto copy_or_ref(Fn& fn) | |
{ | |
if constexpr (std::is_trivially_copyable_v<Fn> && sizeof(Fn) <= sizeof(void*)) { | |
return fn; | |
} else { | |
return std::ref(fn); | |
} | |
} | |
template <typename Fn> | |
using copy_or_ref_t = decltype(copy_or_ref(std::declval<Fn&>())); | |
template <typename Fn> | |
struct emplace_from { | |
Fn fn; | |
using type = decltype(std::move(fn)()); | |
constexpr operator type() && noexcept { return std::move(fn)(); } | |
constexpr type operator()() && { return std::move(fn)(); } | |
}; | |
template <typename Fn> | |
emplace_from(Fn) -> emplace_from<Fn>; | |
[[noreturn]] inline void unreachable() | |
{ | |
if constexpr (config::enable_debug_asserts) { | |
runtime_error( | |
"Unreachable code reached! This should never happen. Please file a bug report."); | |
} | |
#if defined(__cpp_lib_unreachable) && (__cpp_lib_unreachable >= 202202L) | |
std::unreachable(); | |
#elif defined(__has_builtin) && __has_builtin(__builtin_unreachable) | |
__builtin_unreachable(); | |
#elif defined(_MSC_VER) | |
__assume(false); | |
#else | |
std::abort(); | |
#endif | |
} | |
} // namespace detail | |
} // namespace flux | |
#endif | |
#include <compare> | |
#include <concepts> | |
#include <cstdint> | |
#include <functional> | |
#include <initializer_list> | |
#include <tuple> | |
#include <type_traits> | |
// clang-format off | |
// Workaround GCC12 ICE in sequence concept definition below | |
#if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 13) | |
#define FLUX_COMPILER_IS_GCC12 | |
#endif | |
#if defined(__cpp_lib_ranges_zip) && (__cpp_lib_ranges_zip >= 202110L) | |
#define FLUX_HAVE_CPP23_TUPLE_COMMON_REF | |
#endif | |
namespace flux { | |
/* | |
* Cursor concepts | |
*/ | |
FLUX_EXPORT | |
template <typename Cur> | |
concept cursor = std::movable<Cur>; | |
FLUX_EXPORT | |
template <typename Cur> | |
concept regular_cursor = cursor<Cur> && std::regular<Cur>; | |
FLUX_EXPORT | |
template <typename Cur> | |
concept ordered_cursor = | |
regular_cursor<Cur> && | |
std::totally_ordered<Cur>; | |
/* | |
* Sequence concepts and associated types | |
*/ | |
FLUX_EXPORT | |
template <typename T> | |
struct sequence_traits; | |
FLUX_EXPORT | |
struct default_sequence_traits; | |
namespace detail { | |
template <typename T> | |
using traits_t = sequence_traits<std::remove_cvref_t<T>>; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Seq> | |
using cursor_t = decltype(detail::traits_t<Seq>::first(FLUX_DECLVAL(Seq&))); | |
FLUX_EXPORT | |
template <typename Seq> | |
using element_t = decltype(detail::traits_t<Seq>::read_at(FLUX_DECLVAL(Seq&), FLUX_DECLVAL(cursor_t<Seq> const&))); | |
namespace detail { | |
template <typename T> | |
concept has_element_type = requires { typename element_t<T>; }; | |
template <has_element_type T> | |
struct value_type { using type = std::remove_cvref_t<element_t<T>>; }; | |
template <has_element_type T> | |
requires requires { typename traits_t<T>::value_type; } | |
struct value_type<T> { using type = typename traits_t<T>::value_type; }; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Seq> | |
using value_t = typename detail::value_type<Seq>::type; | |
FLUX_EXPORT | |
using int_t = flux::config::int_type; | |
FLUX_EXPORT | |
using index_t = flux::config::int_type; | |
FLUX_EXPORT | |
template <typename Seq> | |
using rvalue_element_t = decltype(detail::traits_t<Seq>::move_at(FLUX_DECLVAL(Seq&), FLUX_DECLVAL(cursor_t<Seq> const&))); | |
FLUX_EXPORT | |
template <typename Seq> | |
using common_element_t = std::common_reference_t<element_t<Seq>, value_t<Seq>&>; | |
FLUX_EXPORT | |
template <typename Seq> | |
using const_element_t = std::common_reference_t<value_t<Seq> const&&, element_t<Seq>>; | |
namespace detail { | |
template <typename B> | |
concept boolean_testable = | |
std::convertible_to<B, bool> && | |
requires (B&& b) { | |
{ !FLUX_FWD(b) } -> std::convertible_to<bool>; | |
}; | |
template <typename T> | |
using with_ref = T&; | |
template <typename T> | |
concept can_reference = requires { typename with_ref<T>; }; | |
template <typename Seq, typename Traits = sequence_traits<std::remove_cvref_t<Seq>>> | |
concept sequence_requirements = | |
requires (Seq& seq) { | |
{ Traits::first(seq) } -> cursor; | |
} && | |
requires (Seq& seq, cursor_t<Seq> const& cur) { | |
{ Traits::is_last(seq, cur) } -> boolean_testable; | |
{ Traits::read_at(seq, cur) } -> can_reference; | |
} && | |
requires (Seq& seq, cursor_t<Seq>& cur) { | |
{ Traits::inc(seq, cur) }; | |
}; | |
template <typename Seq, typename Traits = sequence_traits<std::remove_cvref_t<Seq>>> | |
concept sequence_concept = | |
sequence_requirements<Seq> && | |
requires (Seq& seq, cursor_t<Seq> const& cur) { | |
{ Traits::read_at_unchecked(seq, cur) } -> std::same_as<element_t<Seq>>; | |
{ Traits::move_at(seq, cur) } -> can_reference; | |
{ Traits::move_at_unchecked(seq, cur) } -> std::same_as<rvalue_element_t<Seq>>; | |
} && | |
#ifndef FLUX_COMPILER_IS_GCC12 | |
requires (Seq& seq, bool (*pred)(element_t<Seq>)) { | |
{ Traits::for_each_while(seq, pred) } -> std::same_as<cursor_t<Seq>>; | |
} && | |
#endif | |
#ifdef FLUX_HAVE_CPP23_TUPLE_COMMON_REF | |
std::common_reference_with<element_t<Seq>&&, value_t<Seq>&> && | |
std::common_reference_with<rvalue_element_t<Seq>&&, value_t<Seq> const&> && | |
#endif | |
std::common_reference_with<element_t<Seq>&&, rvalue_element_t<Seq>&&>; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Seq> | |
concept sequence = detail::sequence_concept<Seq>; | |
namespace detail { | |
template <typename> | |
inline constexpr bool disable_multipass = false; | |
template <typename T> | |
requires requires { T::disable_multipass; } && | |
decays_to<decltype(T::disable_multipass), bool> | |
inline constexpr bool disable_multipass<T> = T::disable_multipass; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Seq> | |
concept multipass_sequence = | |
sequence<Seq> && regular_cursor<cursor_t<Seq>> && | |
!detail::disable_multipass<detail::traits_t<Seq>>; | |
namespace detail { | |
template <typename Seq, typename Traits = sequence_traits<std::remove_cvref_t<Seq>>> | |
concept bidirectional_sequence_requirements = | |
requires (Seq& seq, cursor_t<Seq>& cur) { | |
{ Traits::dec(seq, cur) }; | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Seq> | |
concept bidirectional_sequence = multipass_sequence<Seq> && detail::bidirectional_sequence_requirements<Seq>; | |
namespace detail { | |
template <typename Seq, typename Traits = sequence_traits<std::remove_cvref_t<Seq>>> | |
concept random_access_sequence_requirements = | |
ordered_cursor<cursor_t<Seq>> && | |
requires (Seq& seq, cursor_t<Seq>& cur, int_t offset) { | |
{ Traits::inc(seq, cur, offset) }; | |
} && | |
requires (Seq& seq, cursor_t<Seq> const& cur) { | |
{ Traits::distance(seq, cur, cur) } -> std::convertible_to<int_t>; | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Seq> | |
concept random_access_sequence = | |
bidirectional_sequence<Seq> && | |
detail::random_access_sequence_requirements<Seq>; | |
namespace detail { | |
template <typename Seq, typename Traits = sequence_traits<std::remove_cvref_t<Seq>>> | |
concept bounded_sequence_requirements = | |
requires (Seq& seq) { | |
{ Traits::last(seq) } -> std::same_as<cursor_t<Seq>>; | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Seq> | |
concept bounded_sequence = sequence<Seq> && detail::bounded_sequence_requirements<Seq>; | |
namespace detail { | |
template <typename Seq, typename Traits = sequence_traits<std::remove_cvref_t<Seq>>> | |
concept contiguous_sequence_requirements = | |
std::is_lvalue_reference_v<element_t<Seq>> && | |
std::same_as<value_t<Seq>, std::remove_cvref_t<element_t<Seq>>> && | |
requires (Seq& seq) { | |
{ Traits::data(seq) } -> std::same_as<std::add_pointer_t<element_t<Seq>>>; | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Seq> | |
concept contiguous_sequence = | |
random_access_sequence<Seq> && | |
bounded_sequence<Seq> && | |
detail::contiguous_sequence_requirements<Seq>; | |
namespace detail { | |
template <typename Seq, typename Traits = sequence_traits<std::remove_cvref_t<Seq>>> | |
concept sized_sequence_requirements = | |
requires (Seq& seq) { | |
{ Traits::size(seq) } -> std::convertible_to<int_t>; | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Seq> | |
concept sized_sequence = sequence<Seq> && detail::sized_sequence_requirements<Seq>; | |
FLUX_EXPORT | |
template <typename Seq, typename T> | |
concept writable_sequence_of = | |
sequence<Seq> && | |
requires (element_t<Seq> elem, T&& item) { | |
{ elem = FLUX_FWD(item) } -> std::same_as<element_t<Seq>&>; | |
}; | |
namespace detail { | |
template <typename> | |
inline constexpr bool is_infinite_seq = false; | |
template <typename T> | |
requires requires { T::is_infinite; } && | |
decays_to<decltype(T::is_infinite), bool> | |
inline constexpr bool is_infinite_seq<T> = T::is_infinite; | |
} | |
FLUX_EXPORT | |
template <typename Seq> | |
concept infinite_sequence = | |
sequence<Seq> && | |
detail::is_infinite_seq<detail::traits_t<Seq>>; | |
FLUX_EXPORT | |
template <typename Seq> | |
concept read_only_sequence = | |
sequence<Seq> && | |
std::same_as<element_t<Seq>, const_element_t<Seq>>; | |
FLUX_EXPORT | |
template <typename Seq> | |
concept const_iterable_sequence = | |
// Seq and Seq const must both be sequences | |
sequence<Seq> && sequence<Seq const> && | |
// Seq and Seq const must have the same cursor and value types | |
std::same_as<cursor_t<Seq>, cursor_t<Seq const>> && | |
std::same_as<value_t<Seq>, value_t<Seq const>> && | |
// Seq and Seq const must have the same const_element type | |
#ifdef FLUX_HAVE_CPP23_TUPLE_COMMON_REF | |
std::same_as<const_element_t<Seq>, const_element_t<Seq const>> && | |
#endif | |
// Seq and Seq const must model the same extended sequence concepts | |
(multipass_sequence<Seq> == multipass_sequence<Seq const>) && | |
(bidirectional_sequence<Seq> == bidirectional_sequence<Seq const>) && | |
(random_access_sequence<Seq> == random_access_sequence<Seq const>) && | |
(contiguous_sequence<Seq> == contiguous_sequence<Seq const>) && | |
(bounded_sequence<Seq> == bounded_sequence<Seq const>) && | |
(sized_sequence<Seq> == sized_sequence<Seq const>) && | |
(infinite_sequence<Seq> == infinite_sequence<Seq const>) && | |
// If Seq is read-only, Seq const must be read-only as well | |
(!read_only_sequence<Seq> || read_only_sequence<Seq const>); | |
namespace detail { | |
template <typename T, typename R = std::remove_cvref_t<T>> | |
constexpr bool is_ilist = false; | |
template <typename T, typename E> | |
constexpr bool is_ilist<T, std::initializer_list<E>> = true; | |
template <typename Seq> | |
concept movable_rvalue = | |
std::is_object_v<Seq> && | |
std::same_as<std::decay_t<Seq>, Seq> && | |
std::move_constructible<Seq>; | |
template <typename Seq> | |
concept trivially_copyable_lvalue = | |
std::is_lvalue_reference_v<Seq> && | |
std::copyable<std::decay_t<Seq>> && | |
std::is_trivially_copyable_v<std::decay_t<Seq>>; | |
} | |
FLUX_EXPORT | |
template <typename Seq> | |
concept adaptable_sequence = | |
sequence<std::decay_t<Seq>> && | |
(detail::movable_rvalue<Seq> || detail::trivially_copyable_lvalue<Seq>) && | |
!(detail::is_ilist<Seq>); | |
FLUX_EXPORT | |
template <typename D> | |
struct inline_sequence_base; | |
namespace detail { | |
template <typename T, typename U> | |
requires (!std::same_as<T, inline_sequence_base<U>>) | |
void derived_from_inline_sequence_base_test(T const&, inline_sequence_base<U> const&); | |
template <typename T> | |
concept derived_from_inline_sequence_base = requires(T t) { | |
derived_from_inline_sequence_base_test(t, t); | |
}; | |
} // namespace detail | |
/* | |
* Default sequence_traits implementation | |
*/ | |
struct default_sequence_traits { | |
template <typename Self> | |
requires detail::sequence_requirements<Self> | |
static constexpr auto read_at_unchecked(Self& self, cursor_t<Self> const& cur) | |
-> decltype(detail::traits_t<Self>::read_at(self, cur)) | |
{ | |
return detail::traits_t<Self>::read_at(self, cur); | |
} | |
template <typename Self> | |
requires detail::sequence_requirements<Self> | |
static constexpr auto move_at(Self& self, cursor_t<Self> const& cur) | |
-> std::conditional_t<std::is_lvalue_reference_v<element_t<Self>>, | |
std::add_rvalue_reference_t<std::remove_reference_t<element_t<Self>>>, | |
element_t<Self>> | |
{ | |
using Traits = detail::traits_t<Self>; | |
if constexpr (std::is_lvalue_reference_v<element_t<Self>>) { | |
return std::move(Traits::read_at(self, cur)); | |
} else { | |
return Traits::read_at(self, cur); | |
} | |
} | |
template <typename Self> | |
requires detail::sequence_requirements<Self> | |
static constexpr auto move_at_unchecked(Self& self, cursor_t<Self> const& cur) | |
-> decltype(detail::traits_t<Self>::move_at(self, cur)) | |
{ | |
return detail::traits_t<Self>::move_at(self, cur); | |
} | |
template <typename Self> | |
requires detail::random_access_sequence_requirements<Self> && | |
detail::bounded_sequence_requirements<Self> | |
static constexpr auto size(Self& self) -> int_t | |
{ | |
using Traits = detail::traits_t<Self>; | |
return Traits::distance(self, Traits::first(self), Traits::last(self)); | |
} | |
template <typename Self, typename Pred> | |
requires detail::sequence_requirements<Self> | |
static constexpr auto for_each_while(Self& self, Pred&& pred) -> cursor_t<Self> | |
{ | |
using Traits = detail::traits_t<Self>; | |
auto cur = Traits::first(self); | |
if constexpr (bounded_sequence<Self> && regular_cursor<cursor_t<Self>>) { | |
auto const last = Traits::last(self); | |
while (cur != last) { | |
if (!std::invoke(pred, Traits::read_at(self, cur))) { | |
break; | |
} | |
Traits::inc(self, cur); | |
} | |
} else { | |
while (!Traits::is_last(self, cur)) { | |
if (!std::invoke(pred, Traits::read_at(self, cur))) { | |
break; | |
} | |
Traits::inc(self, cur); | |
} | |
} | |
return cur; | |
} | |
}; | |
namespace detail { | |
template <typename T> | |
concept has_nested_sequence_traits = | |
requires { typename T::flux_sequence_traits; } && | |
std::is_class_v<typename T::flux_sequence_traits>; | |
} | |
template <typename T> | |
requires detail::has_nested_sequence_traits<T> | |
struct sequence_traits<T> : T::flux_sequence_traits {}; | |
namespace detail { | |
template <typename O> | |
concept optional_like = | |
std::default_initializable<O> && | |
std::movable<O> && | |
requires (O& o) { | |
{ static_cast<bool>(o) }; | |
{ *o } -> flux::detail::can_reference; | |
}; | |
} | |
} // namespace flux | |
#endif // FLUX_CORE_CONCEPTS_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_NUMERIC_HPP_INCLUDED | |
#define FLUX_CORE_NUMERIC_HPP_INCLUDED | |
/* | |
* Copyright 2023 Justine Alexandra Roberts Tunney | |
* | |
* Permission to use, copy, modify, and/or distribute this software for | |
* any purpose with or without fee is hereby granted, provided that the | |
* above copyright notice and this permission notice appear in all copies. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | |
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | |
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | |
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR | |
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | |
* PERFORMANCE OF THIS SOFTWARE. | |
*/ | |
/* | |
* Upstream repo: https://github.com/jart/jtckdint | |
* | |
* This file contains the following changes from upstream v1.0: | |
* - All functions are constexpr | |
* - `if` changed to `if constexpr` where appropriate | |
* - Use GNU builtins even if __STRICT_ANSI__ is defined | |
* - Use pragma to disable MSVC integer conversion warning | |
*/ | |
/** | |
* @fileoverview C23 Checked Arithmetic | |
* | |
* This header defines three type generic functions: | |
* | |
* - `bool ckd_add(res, a, b)` | |
* - `bool ckd_sub(res, a, b)` | |
* - `bool ckd_mul(res, a, b)` | |
* | |
* Which allow integer arithmetic errors to be detected. There are many | |
* kinds of integer errors, e.g. overflow, truncation, etc. These funcs | |
* catch them all. Here's an example of how it works: | |
* | |
* uint32_t c; | |
* int32_t a = 0x7fffffff; | |
* int32_t b = 2; | |
* assert(!ckd_add(&c, a, b)); | |
* assert(c == 0x80000001u); | |
* | |
* Experienced C / C++ users should find this example counter-intuitive | |
* because the expression `0x7fffffff + 2` not only overflows it's also | |
* undefined behavior. However here we see it's specified, and does not | |
* result in an error. That's because C23 checked arithmetic is not the | |
* arithmetic you're used to. The new standard changes the mathematics. | |
* | |
* C23 checked arithmetic is defined as performing the arithmetic using | |
* infinite precision and then checking if the resulting value will fit | |
* in the output type. Our example above did not result in an error due | |
* to `0x80000001` being a legal value for `uint32_t`. | |
* | |
* This implementation will use the GNU compiler builtins, when they're | |
* available, only if you don't use build flags like `-std=c11` because | |
* they define `__STRICT_ANSI__` and GCC extensions aren't really ANSI. | |
* Instead, you'll get a pretty good pure C11 and C++11 implementation. | |
* | |
* @see https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf | |
* @version 1.0 (2024-12-07) | |
*/ | |
#ifndef JTCKDINT_H_ | |
#define JTCKDINT_H_ | |
#ifdef __has_include | |
#define __ckd_has_include(x) __has_include(x) | |
#else | |
#define __ckd_has_include(x) 0 | |
#endif | |
#if __ckd_has_include(<stdckdint.h>) && !defined(__cplusplus) | |
#include <stdckdint.h> | |
#else | |
#define __STDC_VERSION_STDCKDINT_H__ 202311L | |
#if defined(_MSC_VER) | |
#pragma warning(push) | |
#pragma warning(disable: 4146 4244) | |
#endif | |
#if (!defined(__STRICT_ANSI__) && defined(__SIZEOF_INT128__)) | |
#define __ckd_have_int128 | |
#define __ckd_intmax __int128 | |
#elif ((defined(__cplusplus) && __cplusplus >= 201103L) || \ | |
(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)) | |
#define __ckd_intmax long long | |
#else | |
#define __ckd_intmax long | |
#endif | |
typedef signed __ckd_intmax __ckd_intmax_t; | |
typedef unsigned __ckd_intmax __ckd_uintmax_t; | |
#ifdef __has_builtin | |
#define __ckd_has_builtin(x) __has_builtin(x) | |
#else | |
#define __ckd_has_builtin(x) 0 | |
#endif | |
#if ((defined(__GNUC__) && __GNUC__ >= 5 && !defined(__ICC)) || \ | |
(__ckd_has_builtin(__builtin_add_overflow) && \ | |
__ckd_has_builtin(__builtin_sub_overflow) && \ | |
__ckd_has_builtin(__builtin_mul_overflow))) | |
#define ckd_add(res, x, y) __builtin_add_overflow((x), (y), (res)) | |
#define ckd_sub(res, x, y) __builtin_sub_overflow((x), (y), (res)) | |
#define ckd_mul(res, x, y) __builtin_mul_overflow((x), (y), (res)) | |
#elif (defined(__cplusplus) && \ | |
(__cplusplus >= 201103L || \ | |
(defined(_MSC_VER) && __cplusplus >= 199711L && \ | |
__ckd_has_include(<type_traits>) && \ | |
__ckd_has_include(<limits>)))) | |
#include <type_traits> | |
#include <limits> | |
template <typename __T, typename __U, typename __V> | |
inline constexpr bool ckd_add(__T *__res, __U __a, __V __b) { | |
static_assert(std::is_integral<__T>::value && | |
std::is_integral<__U>::value && | |
std::is_integral<__V>::value, | |
"non-integral types not allowed"); | |
static_assert(!std::is_same<__T, bool>::value && | |
!std::is_same<__U, bool>::value && | |
!std::is_same<__V, bool>::value, | |
"checked booleans not supported"); | |
static_assert(!std::is_same<__T, char>::value && | |
!std::is_same<__U, char>::value && | |
!std::is_same<__V, char>::value, | |
"unqualified char type is ambiguous"); | |
__ckd_uintmax_t __x = __a; | |
__ckd_uintmax_t __y = __b; | |
__ckd_uintmax_t __z = __x + __y; | |
*__res = __z; | |
if constexpr (sizeof(__z) > sizeof(__U) && sizeof(__z) > sizeof(__V)) { | |
if constexpr (sizeof(__z) > sizeof(__T) || std::is_signed<__T>::value) { | |
return static_cast<__ckd_intmax_t>(__z) != static_cast<__T>(__z); | |
} else if (!std::is_same<__T, __ckd_uintmax_t>::value) { | |
return (__z != static_cast<__T>(__z) || | |
((std::is_signed<__U>::value || | |
std::is_signed<__V>::value) && | |
static_cast<__ckd_intmax_t>(__z) < 0)); | |
} | |
} | |
bool __truncated = false; | |
if constexpr (sizeof(__T) < sizeof(__ckd_intmax_t)) { | |
__truncated = __z != static_cast<__ckd_uintmax_t>(static_cast<__T>(__z)); | |
} | |
switch (std::is_signed<__T>::value << 2 | // | |
std::is_signed<__U>::value << 1 | // | |
std::is_signed<__V>::value) { | |
case 0: // u = u + u | |
return __truncated | (__z < __x); | |
case 1: // u = u + s | |
__y ^= std::numeric_limits<__ckd_intmax_t>::min(); | |
return __truncated | | |
(static_cast<__ckd_intmax_t>((__z ^ __x) & | |
(__z ^ __y)) < 0); | |
case 2: // u = s + u | |
__x ^= std::numeric_limits<__ckd_intmax_t>::min(); | |
return __truncated | | |
(static_cast<__ckd_intmax_t>((__z ^ __x) & | |
(__z ^ __y)) < 0); | |
case 3: // u = s + s | |
return __truncated | | |
(static_cast<__ckd_intmax_t>(((__z | __x) & __y) | | |
((__z & __x) & ~__y)) < 0); | |
case 4: // s = u + u | |
return __truncated | (__z < __x) | (static_cast<__ckd_intmax_t>(__z) < 0); | |
case 5: // s = u + s | |
__y ^= std::numeric_limits<__ckd_intmax_t>::min(); | |
return __truncated | (__x + __y < __y); | |
case 6: // s = s + u | |
__x ^= std::numeric_limits<__ckd_intmax_t>::min(); | |
return __truncated | (__x + __y < __x); | |
case 7: // s = s + s | |
return __truncated | | |
(static_cast<__ckd_intmax_t>((__z ^ __x) & | |
(__z ^ __y)) < 0); | |
default: | |
for (;;) (void)0; | |
} | |
} | |
template <typename __T, typename __U, typename __V> | |
inline constexpr bool ckd_sub(__T *__res, __U __a, __V __b) { | |
static_assert(std::is_integral<__T>::value && | |
std::is_integral<__U>::value && | |
std::is_integral<__V>::value, | |
"non-integral types not allowed"); | |
static_assert(!std::is_same<__T, bool>::value && | |
!std::is_same<__U, bool>::value && | |
!std::is_same<__V, bool>::value, | |
"checked booleans not supported"); | |
static_assert(!std::is_same<__T, char>::value && | |
!std::is_same<__U, char>::value && | |
!std::is_same<__V, char>::value, | |
"unqualified char type is ambiguous"); | |
__ckd_uintmax_t __x = __a; | |
__ckd_uintmax_t __y = __b; | |
__ckd_uintmax_t __z = __x - __y; | |
*__res = __z; | |
bool __truncated = false; | |
if constexpr (sizeof(__T) < sizeof(__ckd_intmax_t)) { | |
__truncated = __z != static_cast<__ckd_uintmax_t>(static_cast<__T>(__z)); | |
} | |
switch (std::is_signed<__T>::value << 2 | // | |
std::is_signed<__U>::value << 1 | // | |
std::is_signed<__V>::value) { | |
case 0: // u = u - u | |
return __truncated | (__x < __y); | |
case 1: // u = u - s | |
__y ^= std::numeric_limits<__ckd_intmax_t>::min(); | |
return __truncated | | |
(static_cast<__ckd_intmax_t>((__x ^ __y) & | |
(__z ^ __x)) < 0); | |
case 2: // u = s - u | |
return __truncated | (__y > __x) | (static_cast<__ckd_intmax_t>(__x) < 0); | |
case 3: // u = s - s | |
return __truncated | | |
(static_cast<__ckd_intmax_t>(((__z & __x) & __y) | | |
((__z | __x) & ~__y)) < 0); | |
case 4: // s = u - u | |
return __truncated | | |
((__x < __y) ^ (static_cast<__ckd_intmax_t>(__z) < 0)); | |
case 5: // s = u - s | |
__y ^= std::numeric_limits<__ckd_intmax_t>::min(); | |
return __truncated | (__x >= __y); | |
case 6: // s = s - u | |
__x ^= std::numeric_limits<__ckd_intmax_t>::min(); | |
return __truncated | (__x < __y); | |
case 7: // s = s - s | |
return __truncated | | |
(static_cast<__ckd_intmax_t>((__x ^ __y) & | |
(__z ^ __x)) < 0); | |
default: | |
for (;;) (void)0; | |
} | |
} | |
template <typename __T, typename __U, typename __V> | |
inline constexpr bool ckd_mul(__T *__res, __U __a, __V __b) { | |
static_assert(std::is_integral<__T>::value && | |
std::is_integral<__U>::value && | |
std::is_integral<__V>::value, | |
"non-integral types not allowed"); | |
static_assert(!std::is_same<__T, bool>::value && | |
!std::is_same<__U, bool>::value && | |
!std::is_same<__V, bool>::value, | |
"checked booleans not supported"); | |
static_assert(!std::is_same<__T, char>::value && | |
!std::is_same<__U, char>::value && | |
!std::is_same<__V, char>::value, | |
"unqualified char type is ambiguous"); | |
__ckd_uintmax_t __x = __a; | |
__ckd_uintmax_t __y = __b; | |
if constexpr ((sizeof(__U) * 8 - std::is_signed<__U>::value) + | |
(sizeof(__V) * 8 - std::is_signed<__V>::value) <= | |
(sizeof(__T) * 8 - std::is_signed<__T>::value)) { | |
if constexpr (sizeof(__ckd_uintmax_t) > sizeof(__T) || std::is_signed<__T>::value) { | |
__ckd_intmax_t __z = __x * __y; | |
return __z != (*__res = __z); | |
} else if (!std::is_same<__T, __ckd_uintmax_t>::value) { | |
__ckd_uintmax_t __z = __x * __y; | |
*__res = __z; | |
return (__z != static_cast<__T>(__z) || | |
((std::is_signed<__U>::value || | |
std::is_signed<__V>::value) && | |
static_cast<__ckd_intmax_t>(__z) < 0)); | |
} | |
} | |
switch (std::is_signed<__T>::value << 2 | // | |
std::is_signed<__U>::value << 1 | // | |
std::is_signed<__V>::value) { | |
case 0: { // u = u * u | |
__ckd_uintmax_t __z = __x * __y; | |
int __o = __x && __z / __x != __y; | |
*__res = __z; | |
return __o | (sizeof(__T) < sizeof(__z) && | |
__z != static_cast<__ckd_uintmax_t>(*__res)); | |
} | |
case 1: { // u = u * s | |
__ckd_uintmax_t __z = __x * __y; | |
int __o = __x && __z / __x != __y; | |
*__res = __z; | |
return (__o | ((static_cast<__ckd_intmax_t>(__y) < 0) & !!__x) | | |
(sizeof(__T) < sizeof(__z) && | |
__z != static_cast<__ckd_uintmax_t>(*__res))); | |
} | |
case 2: { // u = s * u | |
__ckd_uintmax_t __z = __x * __y; | |
int __o = __x && __z / __x != __y; | |
*__res = __z; | |
return (__o | ((static_cast<__ckd_intmax_t>(__x) < 0) & !!__y) | | |
(sizeof(__T) < sizeof(__z) && | |
__z != static_cast<__ckd_uintmax_t>(*__res))); | |
} | |
case 3: { // u = s * s | |
int __o = false; | |
if (static_cast<__ckd_intmax_t>(__x & __y) < 0) { | |
__x = 0 - __x; | |
__y = 0 - __y; | |
} else if (static_cast<__ckd_intmax_t>(__x ^ __y) < 0) { | |
__o = __x && __y; | |
} | |
__ckd_uintmax_t __z = __x * __y; | |
__o |= __x && __z / __x != __y; | |
*__res = __z; | |
return __o | (sizeof(__T) < sizeof(__z) && | |
__z != static_cast<__ckd_uintmax_t>(*__res)); | |
} | |
case 4: { // s = u * u | |
__ckd_uintmax_t __z = __x * __y; | |
int __o = __x && __z / __x != __y; | |
*__res = __z; | |
return (__o | (static_cast<__ckd_intmax_t>(__z) < 0) | | |
(sizeof(__T) < sizeof(__z) && | |
__z != static_cast<__ckd_uintmax_t>(*__res))); | |
} | |
case 5: { // s = u * s | |
__ckd_uintmax_t __t = 0 - __y; | |
__t = static_cast<__ckd_intmax_t>(__t) < 0 ? __y : __t; | |
__ckd_uintmax_t __p = __t * __x; | |
int __o = __t && __p / __t != __x; | |
int __n = static_cast<__ckd_intmax_t>(__y) < 0; | |
__ckd_uintmax_t __z = __n ? 0 - __p : __p; | |
*__res = __z; | |
__ckd_uintmax_t __m = std::numeric_limits<__ckd_intmax_t>::max(); | |
return (__o | (__p > __m + __n) | | |
(sizeof(__T) < sizeof(__z) && | |
__z != static_cast<__ckd_uintmax_t>(*__res))); | |
} | |
case 6: { // s = s * u | |
__ckd_uintmax_t __t = 0 - __x; | |
__t = static_cast<__ckd_intmax_t>(__t) < 0 ? __x : __t; | |
__ckd_uintmax_t __p = __t * __y; | |
int __o = __t && __p / __t != __y; | |
int __n = static_cast<__ckd_intmax_t>(__x) < 0; | |
__ckd_uintmax_t __z = __n ? 0 - __p : __p; | |
*__res = __z; | |
__ckd_uintmax_t __m = std::numeric_limits<__ckd_intmax_t>::max(); | |
return (__o | (__p > __m + __n) | | |
(sizeof(__T) < sizeof(__z) && | |
__z != static_cast<__ckd_uintmax_t>(*__res))); | |
} | |
case 7: { // s = s * s | |
__ckd_uintmax_t __z = __x * __y; | |
*__res = __z; | |
return ((((static_cast<__ckd_intmax_t>(__y) < 0) && | |
(static_cast<__ckd_intmax_t>(__x) == | |
std::numeric_limits<__ckd_intmax_t>::min())) || | |
(__y && ((static_cast<__ckd_intmax_t>(__z) / | |
static_cast<__ckd_intmax_t>(__y)) != | |
static_cast<__ckd_intmax_t>(__x)))) | | |
(sizeof(__T) < sizeof(__z) && | |
__z != static_cast<__ckd_uintmax_t>(*__res))); | |
} | |
default: | |
for (;;) (void)0; | |
} | |
} | |
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L | |
#define ckd_add(res, a, b) __ckd_expr(add, (res), (a), (b)) | |
#define ckd_sub(res, a, b) __ckd_expr(sub, (res), (a), (b)) | |
#define ckd_mul(res, a, b) __ckd_expr(mul, (res), (a), (b)) | |
#if defined(__GNUC__) || defined(__llvm__) | |
#define __ckd_inline \ | |
extern __inline __attribute__((__gnu_inline__, \ | |
__always_inline__, \ | |
__artificial__)) | |
#else | |
#define __ckd_inline static inline | |
#endif | |
#ifdef __ckd_have_int128 | |
#define __ckd_generic_int128(x, y) , signed __int128: x, unsigned __int128: y | |
#else | |
#define __ckd_generic_int128(x, y) | |
#endif | |
#define __ckd_sign(T) \ | |
((T)1 << (sizeof(T) * 8 - 1)) | |
#define __ckd_is_signed(x) \ | |
_Generic(x, \ | |
signed char: 1, \ | |
unsigned char: 0, \ | |
signed short: 1, \ | |
unsigned short: 0, \ | |
signed int: 1, \ | |
unsigned int: 0, \ | |
signed long: 1, \ | |
unsigned long: 0, \ | |
signed long long: 1, \ | |
unsigned long long: 0 \ | |
__ckd_generic_int128(1, 0)) | |
#define __ckd_expr(op, res, a, b) \ | |
(_Generic(*res, \ | |
signed char: __ckd_##op##_schar, \ | |
unsigned char: __ckd_##op##_uchar, \ | |
signed short: __ckd_##op##_sshort, \ | |
unsigned short: __ckd_##op##_ushort, \ | |
signed int: __ckd_##op##_sint, \ | |
unsigned int: __ckd_##op##_uint, \ | |
signed long: __ckd_##op##_slong, \ | |
unsigned long: __ckd_##op##_ulong, \ | |
signed long long: __ckd_##op##_slonger, \ | |
unsigned long long: __ckd_##op##_ulonger \ | |
__ckd_generic_int128( \ | |
__ckd_##op##_sint128, \ | |
__ckd_##op##_uint128))( \ | |
res, a, b, \ | |
__ckd_is_signed(a), \ | |
__ckd_is_signed(b))) | |
#define __ckd_declare_add(S, T) \ | |
__ckd_inline char S(void *__res, \ | |
__ckd_uintmax_t __x, \ | |
__ckd_uintmax_t __y, \ | |
char __a_signed, \ | |
char __b_signed) { \ | |
__ckd_uintmax_t __z = __x + __y; \ | |
*(T *)__res = __z; \ | |
char __truncated = 0; \ | |
if (sizeof(T) < sizeof(__ckd_intmax_t)) { \ | |
__truncated = __z != (__ckd_uintmax_t)(T)__z; \ | |
} \ | |
switch (__ckd_is_signed((T)0) << 2 | \ | |
__a_signed << 1 | __b_signed) { \ | |
case 0: /* u = u + u */ \ | |
return __truncated | (__z < __x); \ | |
case 1: /* u = u + s */ \ | |
__y ^= __ckd_sign(__ckd_uintmax_t); \ | |
return __truncated | \ | |
((__ckd_intmax_t)((__z ^ __x) & \ | |
(__z ^ __y)) < 0); \ | |
case 2: /* u = s + u */ \ | |
__x ^= __ckd_sign(__ckd_uintmax_t); \ | |
return __truncated | \ | |
((__ckd_intmax_t)((__z ^ __x) & \ | |
(__z ^ __y)) < 0); \ | |
case 3: /* u = s + s */ \ | |
return __truncated | \ | |
((__ckd_intmax_t)(((__z | __x) & __y) | \ | |
((__z & __x) & ~__y)) < 0); \ | |
case 4: /* s = u + u */ \ | |
return __truncated | (__z < __x) | ((__ckd_intmax_t)__z < 0); \ | |
case 5: /* s = u + s */ \ | |
__y ^= __ckd_sign(__ckd_uintmax_t); \ | |
return __truncated | (__x + __y < __y); \ | |
case 6: /* s = s + u */ \ | |
__x ^= __ckd_sign(__ckd_uintmax_t); \ | |
return __truncated | (__x + __y < __x); \ | |
case 7: /* s = s + s */ \ | |
return __truncated | \ | |
((__ckd_intmax_t)((__z ^ __x) & \ | |
(__z ^ __y)) < 0); \ | |
default: \ | |
for (;;) (void)0; \ | |
} \ | |
} | |
__ckd_declare_add(__ckd_add_schar, signed char) | |
__ckd_declare_add(__ckd_add_uchar, unsigned char) | |
__ckd_declare_add(__ckd_add_sshort, signed short) | |
__ckd_declare_add(__ckd_add_ushort, unsigned short) | |
__ckd_declare_add(__ckd_add_sint, signed int) | |
__ckd_declare_add(__ckd_add_uint, unsigned int) | |
__ckd_declare_add(__ckd_add_slong, signed long) | |
__ckd_declare_add(__ckd_add_ulong, unsigned long) | |
__ckd_declare_add(__ckd_add_slonger, signed long long) | |
__ckd_declare_add(__ckd_add_ulonger, unsigned long long) | |
#ifdef __ckd_have_int128 | |
__ckd_declare_add(__ckd_add_sint128, signed __int128) | |
__ckd_declare_add(__ckd_add_uint128, unsigned __int128) | |
#endif | |
#define __ckd_declare_sub(S, T) \ | |
__ckd_inline char S(void *__res, \ | |
__ckd_uintmax_t __x, \ | |
__ckd_uintmax_t __y, \ | |
char __a_signed, \ | |
char __b_signed) { \ | |
__ckd_uintmax_t __z = __x - __y; \ | |
*(T *)__res = __z; \ | |
char __truncated = 0; \ | |
if (sizeof(T) < sizeof(__ckd_intmax_t)) { \ | |
__truncated = __z != (__ckd_uintmax_t)(T)__z; \ | |
} \ | |
switch (__ckd_is_signed((T)0) << 2 | \ | |
__a_signed << 1 | __b_signed) { \ | |
case 0: /* u = u - u */ \ | |
return __truncated | (__x < __y); \ | |
case 1: /* u = u - s */ \ | |
__y ^= __ckd_sign(__ckd_uintmax_t); \ | |
return __truncated | \ | |
((__ckd_intmax_t)((__x ^ __y) & \ | |
(__z ^ __x)) < 0); \ | |
case 2: /* u = s - u */ \ | |
return __truncated | (__y > __x) | ((__ckd_intmax_t)__x < 0); \ | |
case 3: /* u = s - s */ \ | |
return __truncated | \ | |
((__ckd_intmax_t)(((__z & __x) & __y) | \ | |
((__z | __x) & ~__y)) < 0); \ | |
case 4: /* s = u - u */ \ | |
return __truncated | ((__x < __y) ^ ((__ckd_intmax_t)__z < 0)); \ | |
case 5: /* s = u - s */ \ | |
__y ^= __ckd_sign(__ckd_uintmax_t); \ | |
return __truncated | (__x >= __y); \ | |
case 6: /* s = s - u */ \ | |
__x ^= __ckd_sign(__ckd_uintmax_t); \ | |
return __truncated | (__x < __y); \ | |
case 7: /* s = s - s */ \ | |
return __truncated | \ | |
((__ckd_intmax_t)((__x ^ __y) & \ | |
(__z ^ __x)) < 0); \ | |
default: \ | |
for (;;) (void)0; \ | |
} \ | |
} | |
__ckd_declare_sub(__ckd_sub_schar, signed char) | |
__ckd_declare_sub(__ckd_sub_uchar, unsigned char) | |
__ckd_declare_sub(__ckd_sub_sshort, signed short) | |
__ckd_declare_sub(__ckd_sub_ushort, unsigned short) | |
__ckd_declare_sub(__ckd_sub_sint, signed int) | |
__ckd_declare_sub(__ckd_sub_uint, unsigned int) | |
__ckd_declare_sub(__ckd_sub_slong, signed long) | |
__ckd_declare_sub(__ckd_sub_ulong, unsigned long) | |
__ckd_declare_sub(__ckd_sub_slonger, signed long long) | |
__ckd_declare_sub(__ckd_sub_ulonger, unsigned long long) | |
#ifdef __ckd_have_int128 | |
__ckd_declare_sub(__ckd_sub_sint128, signed __int128) | |
__ckd_declare_sub(__ckd_sub_uint128, unsigned __int128) | |
#endif | |
#define __ckd_declare_mul(S, T) \ | |
__ckd_inline char S(void *__res, \ | |
__ckd_uintmax_t __x, \ | |
__ckd_uintmax_t __y, \ | |
char __a_signed, \ | |
char __b_signed) { \ | |
switch (__ckd_is_signed((T)0) << 2 | \ | |
__a_signed << 1 | __b_signed) { \ | |
case 0: { /* u = u * u */ \ | |
__ckd_uintmax_t __z = __x * __y; \ | |
int __o = __x && __z / __x != __y; \ | |
*(T *)__res = __z; \ | |
return __o | (sizeof(T) < sizeof(__z) && \ | |
__z != (__ckd_uintmax_t)*(T *)__res); \ | |
} \ | |
case 1: { /* u = u * s */ \ | |
__ckd_uintmax_t __z = __x * __y; \ | |
int __o = __x && __z / __x != __y; \ | |
*(T *)__res = __z; \ | |
return (__o | (((__ckd_intmax_t)__y < 0) & !!__x) | \ | |
(sizeof(T) < sizeof(__z) && \ | |
__z != (__ckd_uintmax_t)*(T *)__res)); \ | |
} \ | |
case 2: { /* u = s * u */ \ | |
__ckd_uintmax_t __z = __x * __y; \ | |
int __o = __x && __z / __x != __y; \ | |
*(T *)__res = __z; \ | |
return (__o | (((__ckd_intmax_t)__x < 0) & !!__y) | \ | |
(sizeof(T) < sizeof(__z) && \ | |
__z != (__ckd_uintmax_t)*(T *)__res)); \ | |
} \ | |
case 3: { /* u = s * s */ \ | |
int __o = 0; \ | |
if ((__ckd_intmax_t)(__x & __y) < 0) { \ | |
__x = 0 - __x; \ | |
__y = 0 - __y; \ | |
} else if ((__ckd_intmax_t)(__x ^ __y) < 0) { \ | |
__o = __x && __y; \ | |
} \ | |
__ckd_uintmax_t __z = __x * __y; \ | |
__o |= __x && __z / __x != __y; \ | |
*(T *)__res = __z; \ | |
return __o | (sizeof(T) < sizeof(__z) && \ | |
__z != (__ckd_uintmax_t)*(T *)__res); \ | |
} \ | |
case 4: { /* s = u * u */ \ | |
__ckd_uintmax_t __z = __x * __y; \ | |
int __o = __x && __z / __x != __y; \ | |
*(T *)__res = __z; \ | |
return (__o | ((__ckd_intmax_t)(__z) < 0) | \ | |
(sizeof(T) < sizeof(__z) && \ | |
__z != (__ckd_uintmax_t)*(T *)__res)); \ | |
} \ | |
case 5: { /* s = u * s */ \ | |
__ckd_uintmax_t __t = 0 - __y; \ | |
__t = (__ckd_intmax_t)(__t) < 0 ? __y : __t; \ | |
__ckd_uintmax_t __p = __t * __x; \ | |
int __o = __t && __p / __t != __x; \ | |
int __n = (__ckd_intmax_t)__y < 0; \ | |
__ckd_uintmax_t __z = __n ? 0 - __p : __p; \ | |
*(T *)__res = __z; \ | |
__ckd_uintmax_t __m = __ckd_sign(__ckd_uintmax_t) - 1; \ | |
return (__o | (__p > __m + __n) | \ | |
(sizeof(T) < sizeof(__z) && \ | |
__z != (__ckd_uintmax_t)*(T *)__res)); \ | |
} \ | |
case 6: { /* s = s * u */ \ | |
__ckd_uintmax_t __t = 0 - __x; \ | |
__t = (__ckd_intmax_t)(__t) < 0 ? __x : __t; \ | |
__ckd_uintmax_t __p = __t * __y; \ | |
int __o = __t && __p / __t != __y; \ | |
int __n = (__ckd_intmax_t)__x < 0; \ | |
__ckd_uintmax_t __z = __n ? 0 - __p : __p; \ | |
*(T *)__res = __z; \ | |
__ckd_uintmax_t __m = __ckd_sign(__ckd_uintmax_t) - 1; \ | |
return (__o | (__p > __m + __n) | \ | |
(sizeof(T) < sizeof(__z) && \ | |
__z != (__ckd_uintmax_t)*(T *)__res)); \ | |
} \ | |
case 7: { /* s = s * s */ \ | |
__ckd_uintmax_t __z = __x * __y; \ | |
*(T *)__res = __z; \ | |
return (((((__ckd_intmax_t)__y < 0) && \ | |
(__x == __ckd_sign(__ckd_uintmax_t))) || \ | |
(__y && (((__ckd_intmax_t)__z / \ | |
(__ckd_intmax_t)__y) != \ | |
(__ckd_intmax_t)__x))) | \ | |
(sizeof(T) < sizeof(__z) && \ | |
__z != (__ckd_uintmax_t)*(T *)__res)); \ | |
} \ | |
default: \ | |
for (;;) (void)0; \ | |
} \ | |
} | |
__ckd_declare_mul(__ckd_mul_schar, signed char) | |
__ckd_declare_mul(__ckd_mul_uchar, unsigned char) | |
__ckd_declare_mul(__ckd_mul_sshort, signed short) | |
__ckd_declare_mul(__ckd_mul_ushort, unsigned short) | |
__ckd_declare_mul(__ckd_mul_sint, signed int) | |
__ckd_declare_mul(__ckd_mul_uint, unsigned int) | |
__ckd_declare_mul(__ckd_mul_slong, signed long) | |
__ckd_declare_mul(__ckd_mul_ulong, unsigned long) | |
__ckd_declare_mul(__ckd_mul_slonger, signed long long) | |
__ckd_declare_mul(__ckd_mul_ulonger, unsigned long long) | |
#ifdef __ckd_have_int128 | |
__ckd_declare_mul(__ckd_mul_sint128, signed __int128) | |
__ckd_declare_mul(__ckd_mul_uint128, unsigned __int128) | |
#endif | |
#else | |
#pragma message "checked integer arithmetic unsupported in this environment" | |
#define ckd_add(res, x, y) (*(res) = (x) + (y), 0) | |
#define ckd_sub(res, x, y) (*(res) = (x) - (y), 0) | |
#define ckd_mul(res, x, y) (*(res) = (x) * (y), 0) | |
#endif /* GNU */ | |
#if defined(_MSC_VER) | |
#pragma warning(pop) | |
#endif | |
#endif /* stdckdint.h */ | |
#endif /* JTCKDINT_H_ */ | |
#include <climits> | |
#include <cstdint> | |
#include <limits> | |
#include <utility> | |
namespace flux::num { | |
FLUX_EXPORT | |
template <typename T> | |
concept integral = | |
std::integral<T> && | |
!flux::detail::any_of<T, bool, char, wchar_t, char8_t, char16_t, char32_t>; | |
FLUX_EXPORT | |
template <typename T> | |
concept signed_integral = integral<T> && std::signed_integral<T>; | |
FLUX_EXPORT | |
template <typename T> | |
concept unsigned_integral = integral<T> && std::unsigned_integral<T>; | |
FLUX_EXPORT | |
template <integral T> | |
struct overflow_result { | |
T value; | |
bool overflowed; | |
}; | |
namespace detail { | |
template <integral To> | |
struct unchecked_cast_fn { | |
template <integral From> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(From from) const noexcept -> To | |
{ | |
return static_cast<To>(from); | |
} | |
}; | |
template <integral To> | |
struct overflowing_cast_fn { | |
template <integral From> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(From from) const noexcept -> overflow_result<To> | |
{ | |
if constexpr (requires { To{from}; }) { | |
return {To{from}, false}; // not a narrowing conversion | |
} else { | |
return {static_cast<To>(from), !std::in_range<To>(from)}; | |
} | |
} | |
}; | |
template <integral To> | |
struct checked_cast_fn { | |
template <integral From> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(From from, | |
std::source_location loc = std::source_location::current()) const | |
-> To | |
{ | |
if constexpr (requires { To{from}; }) { | |
return To{from}; | |
} else { | |
if (std::in_range<To>(from)) { | |
return static_cast<To>(from); | |
} else { | |
runtime_error("checked_cast failed", loc); | |
} | |
} | |
} | |
}; | |
template <integral To> | |
struct cast_fn { | |
template <integral From> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(From from, | |
std::source_location loc = std::source_location::current()) const | |
-> To | |
{ | |
if constexpr (config::on_integer_cast == integer_cast_policy::checked) { | |
return checked_cast_fn<To>{}(from, loc); | |
} else { | |
return unchecked_cast_fn<To>{}(from); | |
} | |
} | |
}; | |
struct unchecked_add_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> T | |
{ | |
return static_cast<T>(lhs + rhs); | |
} | |
}; | |
struct unchecked_sub_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> T | |
{ | |
return static_cast<T>(lhs - rhs); | |
} | |
}; | |
struct unchecked_mul_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> T | |
{ | |
return static_cast<T>(lhs * rhs); | |
} | |
}; | |
struct unchecked_div_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> T | |
{ | |
return static_cast<T>(lhs / rhs); | |
} | |
}; | |
struct unchecked_mod_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> T | |
{ | |
return static_cast<T>(lhs % rhs); | |
} | |
}; | |
struct unchecked_shl_fn { | |
template <integral T, integral U> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, U rhs) const noexcept -> T | |
{ | |
return static_cast<T>(lhs << rhs); | |
} | |
}; | |
struct unchecked_shr_fn { | |
template <integral T, integral U> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, U rhs) const noexcept -> T | |
{ | |
return static_cast<T>(lhs >> rhs); | |
} | |
}; | |
struct unchecked_neg_fn { | |
template <signed_integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T val) const noexcept -> T | |
{ | |
return static_cast<T>(-val); | |
} | |
}; | |
struct wrapping_add_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> T | |
{ | |
T r; | |
(void) ckd_add(&r, lhs, rhs); | |
return r; | |
} | |
}; | |
struct wrapping_sub_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> T | |
{ | |
T r; | |
(void) ckd_sub(&r, lhs, rhs); | |
return r; | |
} | |
}; | |
struct wrapping_mul_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> T | |
{ | |
T r; | |
(void) ckd_mul(&r, lhs, rhs); | |
return r; | |
} | |
}; | |
struct wrapping_neg_fn { | |
template <signed_integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T val) const noexcept -> T | |
{ | |
T r; | |
(void) ckd_sub(&r, T{0}, val); | |
return r; | |
} | |
}; | |
struct overflowing_add_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> overflow_result<T> | |
{ | |
T r; | |
bool o = ckd_add(&r, lhs, rhs); | |
return {r, o}; | |
} | |
}; | |
struct overflowing_sub_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> overflow_result<T> | |
{ | |
T r; | |
bool o = ckd_sub(&r, lhs, rhs); | |
return {r, o}; | |
} | |
}; | |
struct overflowing_mul_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs) const noexcept -> overflow_result<T> | |
{ | |
T r; | |
bool o = ckd_mul(&r, lhs, rhs); | |
return {r, o}; | |
} | |
}; | |
struct overflowing_neg_fn { | |
template <signed_integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T val) const noexcept -> overflow_result<T> | |
{ | |
T r; | |
bool o = ckd_sub(&r, T{0}, val); | |
return {r, o}; | |
} | |
}; | |
struct checked_add_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs, | |
std::source_location loc = std::source_location::current()) const | |
-> T | |
{ | |
if (T r; !ckd_add(&r, lhs, rhs)) { | |
return r; | |
} else { | |
runtime_error("overflow in addition", loc); | |
} | |
} | |
}; | |
struct checked_sub_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs, | |
std::source_location loc = std::source_location::current()) const | |
-> T | |
{ | |
if (T r; !ckd_sub(&r, lhs, rhs)) { | |
return r; | |
} else { | |
runtime_error("overflow in subtraction", loc); | |
} | |
} | |
}; | |
struct checked_mul_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs, | |
std::source_location loc = std::source_location::current()) const | |
-> T | |
{ | |
if (T r; !ckd_mul(&r, lhs, rhs)) { | |
return r; | |
} else { | |
runtime_error("overflow in multiplication", loc); | |
} | |
} | |
}; | |
template <overflow_policy OnOverflow = overflow_policy::error, | |
divide_by_zero_policy OnDivByZero = divide_by_zero_policy::error> | |
struct checked_div_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs, | |
std::source_location loc = std::source_location::current()) const | |
-> T | |
{ | |
// If we're in constant evaluation, we already get a divide-by-zero check | |
if (!std::is_constant_evaluated()) { | |
if constexpr (OnDivByZero == divide_by_zero_policy::error) { | |
if (rhs == T{}) { | |
runtime_error("division by zero", loc); | |
} | |
} | |
} | |
// For signed types, MIN/-1 overflows | |
if constexpr (signed_integral<T> && (OnOverflow != overflow_policy::ignore)) { | |
if (lhs == std::numeric_limits<T>::lowest() && rhs == T{-1}) { | |
runtime_error("overflow in division", loc); | |
} | |
} | |
return unchecked_div_fn{}(lhs, rhs); | |
} | |
}; | |
template <overflow_policy OnOverflow = overflow_policy::error, | |
divide_by_zero_policy OnDivByZero = divide_by_zero_policy::error> | |
struct checked_mod_fn { | |
template <integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, T rhs, | |
std::source_location loc = std::source_location::current()) const | |
-> T | |
{ | |
if (!std::is_constant_evaluated()) { | |
if constexpr (OnDivByZero == divide_by_zero_policy::error) { | |
if (rhs == T{}) { | |
runtime_error("modulo with zero", loc); | |
} | |
} | |
} | |
if constexpr (signed_integral<T> && (OnOverflow != overflow_policy::ignore)) { | |
if (lhs == std::numeric_limits<T>::lowest() && rhs == T{-1}) { | |
runtime_error("overflow in modulo", loc); | |
} | |
} | |
return unchecked_mod_fn{}(lhs, rhs); | |
} | |
}; | |
struct checked_shl_fn { | |
template <integral T, integral U> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, U rhs, | |
std::source_location loc = std::source_location::current()) const | |
-> T | |
{ | |
constexpr std::size_t width = sizeof(T) * CHAR_BIT; | |
// If T is at least as large as int, we already get a check when | |
// in constant evaluation | |
if ((!std::is_constant_evaluated() || (sizeof(T) < sizeof(int))) && | |
((static_cast<std::size_t>(rhs) >= width) || rhs < U{})) { | |
flux::runtime_error("left shift argument too large or negative", loc); | |
} | |
return unchecked_shl_fn{}(lhs, rhs); | |
} | |
}; | |
struct checked_shr_fn { | |
template <integral T, integral U> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T lhs, U rhs, | |
std::source_location loc = std::source_location::current()) const | |
-> T | |
{ | |
constexpr std::size_t width = sizeof(T) * CHAR_BIT; | |
if ((!std::is_constant_evaluated() || (sizeof(T) < sizeof(int)))&& | |
((static_cast<std::size_t>(rhs) >= width) || rhs < U{})) { | |
flux::runtime_error("right shift argument too large or negative", loc); | |
} | |
return unchecked_shr_fn{}(lhs, rhs); | |
} | |
}; | |
struct checked_neg_fn { | |
template <signed_integral T> | |
[[nodiscard]] | |
FLUX_ALWAYS_INLINE | |
constexpr auto operator()(T val, | |
std::source_location loc = std::source_location::current()) const | |
-> T | |
{ | |
if (T r; !ckd_sub(&r, T{0}, val)) { | |
return r; | |
} else { | |
runtime_error("overflow in signed negation", loc); | |
} | |
} | |
}; | |
template <overflow_policy> | |
struct default_ops; | |
template <> | |
struct default_ops<overflow_policy::ignore> { | |
using add_fn = unchecked_add_fn; | |
using sub_fn = unchecked_sub_fn; | |
using mul_fn = unchecked_mul_fn; | |
using neg_fn = unchecked_neg_fn; | |
using shl_fn = unchecked_shl_fn; | |
using shr_fn = unchecked_shr_fn; | |
}; | |
template <> | |
struct default_ops<overflow_policy::wrap> { | |
using add_fn = wrapping_add_fn; | |
using sub_fn = wrapping_sub_fn; | |
using mul_fn = wrapping_mul_fn; | |
using neg_fn = wrapping_neg_fn; | |
// no wrapping versions of these yet, so use the checked versions | |
using shl_fn = checked_shl_fn; | |
using shr_fn = checked_shr_fn; | |
}; | |
template <> | |
struct default_ops<overflow_policy::error> { | |
using add_fn = checked_add_fn; | |
using sub_fn = checked_sub_fn; | |
using mul_fn = checked_mul_fn; | |
using neg_fn = checked_neg_fn; | |
using shl_fn = checked_shl_fn; | |
using shr_fn = checked_shr_fn; | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
template <integral To> | |
inline constexpr auto unchecked_cast = detail::unchecked_cast_fn<To>{}; | |
FLUX_EXPORT | |
template <integral To> | |
inline constexpr auto overflowing_cast = detail::overflowing_cast_fn<To>{}; | |
FLUX_EXPORT | |
template <integral To> | |
inline constexpr auto checked_cast = detail::checked_cast_fn<To>{}; | |
FLUX_EXPORT | |
template <integral To> | |
inline constexpr auto cast = detail::cast_fn<To>{}; | |
FLUX_EXPORT inline constexpr auto unchecked_add = detail::unchecked_add_fn{}; | |
FLUX_EXPORT inline constexpr auto unchecked_sub = detail::unchecked_sub_fn{}; | |
FLUX_EXPORT inline constexpr auto unchecked_mul = detail::unchecked_mul_fn{}; | |
FLUX_EXPORT inline constexpr auto unchecked_div = detail::unchecked_div_fn{}; | |
FLUX_EXPORT inline constexpr auto unchecked_mod = detail::unchecked_mod_fn{}; | |
FLUX_EXPORT inline constexpr auto unchecked_neg = detail::unchecked_neg_fn{}; | |
FLUX_EXPORT inline constexpr auto unchecked_shl = detail::unchecked_shl_fn{}; | |
FLUX_EXPORT inline constexpr auto unchecked_shr = detail::unchecked_shr_fn{}; | |
FLUX_EXPORT inline constexpr auto wrapping_add = detail::wrapping_add_fn{}; | |
FLUX_EXPORT inline constexpr auto wrapping_sub = detail::wrapping_sub_fn{}; | |
FLUX_EXPORT inline constexpr auto wrapping_mul = detail::wrapping_mul_fn{}; | |
FLUX_EXPORT inline constexpr auto wrapping_neg = detail::wrapping_neg_fn{}; | |
FLUX_EXPORT inline constexpr auto overflowing_add = detail::overflowing_add_fn{}; | |
FLUX_EXPORT inline constexpr auto overflowing_sub = detail::overflowing_sub_fn{}; | |
FLUX_EXPORT inline constexpr auto overflowing_mul = detail::overflowing_mul_fn{}; | |
FLUX_EXPORT inline constexpr auto overflowing_neg = detail::overflowing_neg_fn{}; | |
FLUX_EXPORT inline constexpr auto checked_add = detail::checked_add_fn{}; | |
FLUX_EXPORT inline constexpr auto checked_sub = detail::checked_sub_fn{}; | |
FLUX_EXPORT inline constexpr auto checked_mul = detail::checked_mul_fn{}; | |
FLUX_EXPORT inline constexpr auto checked_div = detail::checked_div_fn{}; | |
FLUX_EXPORT inline constexpr auto checked_mod = detail::checked_mod_fn{}; | |
FLUX_EXPORT inline constexpr auto checked_neg = detail::checked_neg_fn{}; | |
FLUX_EXPORT inline constexpr auto checked_shl = detail::checked_shl_fn{}; | |
FLUX_EXPORT inline constexpr auto checked_shr = detail::checked_shr_fn{}; | |
FLUX_EXPORT inline constexpr auto add = detail::default_ops<config::on_overflow>::add_fn{}; | |
FLUX_EXPORT inline constexpr auto sub = detail::default_ops<config::on_overflow>::sub_fn{}; | |
FLUX_EXPORT inline constexpr auto mul = detail::default_ops<config::on_overflow>::mul_fn{}; | |
FLUX_EXPORT inline constexpr auto div = | |
detail::checked_div_fn<config::on_overflow, config::on_divide_by_zero>{}; | |
FLUX_EXPORT inline constexpr auto mod = | |
detail::checked_mod_fn<config::on_overflow, config::on_divide_by_zero>{}; | |
FLUX_EXPORT inline constexpr auto neg = detail::default_ops<config::on_overflow>::neg_fn{}; | |
FLUX_EXPORT inline constexpr auto shl = detail::default_ops<config::on_overflow>::shl_fn{}; | |
FLUX_EXPORT inline constexpr auto shr = detail::default_ops<config::on_overflow>::shr_fn{}; | |
} // namespace flux::num | |
#endif | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_OPTIONAL_HPP_INCLUDED | |
#define FLUX_CORE_OPTIONAL_HPP_INCLUDED | |
#include <functional> | |
#include <optional> | |
namespace flux { | |
FLUX_EXPORT using nullopt_t = std::nullopt_t; | |
FLUX_EXPORT inline constexpr nullopt_t const& nullopt = std::nullopt; | |
namespace detail { | |
template <typename T> | |
concept can_optional = | |
(std::is_object_v<T> || std::is_lvalue_reference_v<T>) && | |
!decays_to<T, nullopt_t> && | |
!decays_to<T, std::in_place_t>; | |
} | |
FLUX_EXPORT | |
template <typename T> | |
class optional; | |
template <detail::can_optional T> | |
requires std::is_object_v<T> | |
class optional<T> { | |
struct dummy {}; | |
union { | |
dummy dummy_{}; | |
T item_; | |
}; | |
bool engaged_ = false; | |
template <typename... Args> | |
constexpr auto construct(Args&&... args) { | |
std::construct_at(std::addressof(item_), FLUX_FWD(args)...); | |
engaged_ = true; | |
} | |
public: | |
constexpr optional() noexcept {} | |
constexpr explicit(false) optional(nullopt_t) noexcept {} | |
template <decays_to<T> U = T> | |
constexpr explicit optional(U&& item) | |
noexcept(std::is_nothrow_constructible_v<T, U>) | |
: item_(FLUX_FWD(item)), | |
engaged_(true) | |
{} | |
template <typename... Args> | |
requires std::constructible_from<T, Args...> | |
constexpr explicit optional(std::in_place_t, Args&&... args) | |
noexcept(std::is_nothrow_constructible_v<T, Args...>) | |
: item_(FLUX_FWD(args)...), | |
engaged_(true) | |
{} | |
/* | |
* Destructors | |
*/ | |
constexpr ~optional() | |
{ | |
if (engaged_) { | |
item_.T::~T(); | |
} | |
} | |
~optional() requires std::is_trivially_destructible_v<T> = default; | |
/* | |
* Copy constructors | |
*/ | |
constexpr optional(optional const& other) | |
noexcept(std::is_nothrow_copy_constructible_v<T>) | |
requires std::copy_constructible<T> | |
{ | |
if (other.engaged_) { | |
construct(other.item_); | |
} | |
} | |
optional(optional const&) | |
requires std::copy_constructible<T> && | |
std::is_trivially_constructible_v<T> | |
= default; | |
/* | |
* Copy-assignment operators | |
*/ | |
constexpr optional& operator=(optional const& other) | |
noexcept(std::is_nothrow_copy_assignable_v<T> && | |
std::is_nothrow_copy_constructible_v<T>) | |
requires std::copy_constructible<T> | |
{ | |
if (engaged_) { | |
if (other.engaged_) { | |
if constexpr (std::is_copy_assignable_v<T>) { | |
item_ = other.item_; | |
} else { | |
reset(); | |
construct(other.item_); | |
} | |
} else { | |
reset(); | |
} | |
} else { | |
if (other.engaged_) { | |
construct(other.item_); | |
} | |
} | |
return *this; | |
} | |
optional& operator=(optional const&) | |
requires std::copy_constructible<T> && | |
std::is_trivially_copy_assignable_v<T> | |
= default; | |
/* | |
* Move constructors | |
*/ | |
constexpr optional(optional&& other) | |
noexcept(std::is_nothrow_move_constructible_v<T>) | |
requires std::move_constructible<T> | |
{ | |
if (other.engaged_) { | |
construct(std::move(other).item_); | |
} | |
} | |
optional(optional&&) | |
requires std::move_constructible<T> && | |
std::is_trivially_move_constructible_v<T> | |
= default; | |
/* | |
* Move-assignment operators | |
*/ | |
constexpr optional& operator=(optional&& other) | |
noexcept(std::is_nothrow_move_constructible_v<T> && | |
std::is_nothrow_move_assignable_v<T>) | |
requires std::move_constructible<T> | |
{ | |
if (engaged_) { | |
if (other.engaged_) { | |
if constexpr (std::is_move_assignable_v<T>) { | |
item_ = std::move(other).item_; | |
} else { | |
reset(); | |
construct(std::move(other).item_); | |
} | |
} else { | |
reset(); | |
} | |
} else { | |
if (other.engaged_) { | |
construct(std::move(other).item_); | |
} | |
} | |
return *this; | |
} | |
constexpr optional& operator=(optional&&) | |
requires std::move_constructible<T> && | |
std::is_trivially_move_assignable_v<T> | |
= default; | |
/* | |
* Observers | |
*/ | |
[[nodiscard]] | |
constexpr auto has_value() const -> bool { return engaged_; } | |
constexpr explicit operator bool() const { return engaged_; } | |
template <decays_to<optional> Opt> | |
[[nodiscard]] | |
friend constexpr auto operator*(Opt&& self) -> decltype(auto) | |
{ | |
if (!std::is_constant_evaluated()) { | |
FLUX_ASSERT(self.has_value()); | |
} | |
return (FLUX_FWD(self).item_); | |
} | |
constexpr auto operator->() -> T* | |
{ | |
if (!std::is_constant_evaluated()) { | |
FLUX_ASSERT(this->has_value()); | |
} | |
return std::addressof(item_); | |
} | |
constexpr auto operator->() const -> T const* | |
{ | |
if (!std::is_constant_evaluated()) { | |
FLUX_ASSERT(this->has_value()); | |
} | |
return std::addressof(item_); | |
} | |
[[nodiscard]] constexpr auto value() & -> T& { return **this; } | |
[[nodiscard]] constexpr auto value() const& -> T const& { return **this; } | |
[[nodiscard]] constexpr auto value() && -> T&& { return *std::move(*this); } | |
[[nodiscard]] constexpr auto value() const&& -> T const&& { return *std::move(*this); } | |
[[nodiscard]] constexpr auto value_unchecked() & noexcept -> T& { return item_; } | |
[[nodiscard]] constexpr auto value_unchecked() const& noexcept -> T const& { return item_; } | |
[[nodiscard]] constexpr auto value_unchecked() && noexcept -> T&& { return std::move(item_); } | |
[[nodiscard]] constexpr auto value_unchecked() const&& noexcept -> T const&& { return std::move(item_); } | |
[[nodiscard]] constexpr auto value_or(auto&& alt) & | |
-> decltype(has_value() ? value_unchecked() : FLUX_FWD(alt)) | |
{ | |
return has_value() ? value_unchecked() : FLUX_FWD(alt); | |
} | |
[[nodiscard]] constexpr auto value_or(auto&& alt) const& | |
-> decltype(has_value() ? value_unchecked() : FLUX_FWD(alt)) | |
{ | |
return has_value() ? value_unchecked() : FLUX_FWD(alt); | |
} | |
[[nodiscard]] constexpr auto value_or(auto&& alt) && | |
-> decltype(has_value() ? std::move(*this).value_unchecked() : FLUX_FWD(alt)) | |
{ | |
return has_value() ? std::move(*this).value_unchecked() : FLUX_FWD(alt); | |
} | |
[[nodiscard]] constexpr auto value_or(auto&& alt) const&& | |
-> decltype(has_value() ? std::move(*this).value_unchecked() : FLUX_FWD(alt)) | |
{ | |
return has_value() ? std::move(*this).value_unchecked() : FLUX_FWD(alt); | |
} | |
private: | |
template <typename Cmp> | |
static constexpr auto do_compare(optional const& lhs, optional const& rhs, Cmp cmp) | |
-> std::decay_t<std::invoke_result_t<Cmp&, T const&, T const&>> | |
{ | |
if (lhs.has_value() && rhs.has_value()) { | |
return cmp(lhs.value_unchecked(), rhs.value_unchecked()); | |
} else { | |
return cmp(lhs.has_value(), rhs.has_value()); | |
} | |
} | |
public: | |
[[nodiscard]] | |
friend constexpr auto operator==(optional const& lhs, optional const& rhs) -> bool | |
requires std::equality_comparable<T> | |
{ | |
return do_compare(lhs, rhs, std::equal_to{}); | |
} | |
[[nodiscard]] | |
friend constexpr auto operator<=>(optional const& lhs, optional const& rhs) | |
requires std::totally_ordered<T> && std::three_way_comparable<T> | |
{ | |
return do_compare(lhs, rhs, std::compare_three_way{}); | |
} | |
[[nodiscard]] | |
friend constexpr auto operator<=>(optional const& lhs, optional const& rhs) | |
requires std::totally_ordered<T> | |
{ | |
return do_compare(lhs, rhs, std::compare_partial_order_fallback); | |
} | |
[[nodiscard]] | |
friend constexpr auto operator==(optional const& o, nullopt_t) -> bool | |
{ | |
return !o.has_value(); | |
} | |
[[nodiscard]] | |
friend constexpr auto operator<=>(optional const& o, nullopt_t) | |
-> std::strong_ordering | |
{ | |
return o.has_value() ? std::strong_ordering::greater | |
: std::strong_ordering::equivalent; | |
} | |
/* | |
* Modifiers | |
*/ | |
constexpr auto reset() -> void | |
{ | |
if (engaged_) { | |
item_.T::~T(); | |
engaged_ = false; | |
} | |
} | |
template <typename... Args> | |
requires std::constructible_from<T, Args...> | |
constexpr auto emplace(Args&&... args) -> T& | |
{ | |
reset(); | |
construct(FLUX_FWD(args)...); | |
return item_; | |
} | |
/* | |
* Monadic operations | |
*/ | |
template <typename F> | |
requires std::invocable<F, T&> && | |
detail::can_optional<std::invoke_result_t<F, T&>> | |
[[nodiscard]] | |
constexpr auto map(F&& func) & -> optional<std::invoke_result_t<F, T&>> | |
{ | |
using R = optional<std::invoke_result_t<F, T&>>; | |
if (engaged_) { | |
return R(std::invoke(FLUX_FWD(func), value_unchecked())); | |
} else { | |
return nullopt; | |
} | |
} | |
template <typename F> | |
requires std::invocable<F, T const&> && | |
detail::can_optional<std::invoke_result_t<F, T const&>> | |
[[nodiscard]] | |
constexpr auto map(F&& func) const& -> optional<std::invoke_result_t<F, T const&>> | |
{ | |
using R = optional<std::invoke_result_t<F, T const&>>; | |
if (engaged_) { | |
return R(std::invoke(FLUX_FWD(func), value_unchecked())); | |
} else { | |
return nullopt; | |
} | |
} | |
template <typename F> | |
requires std::invocable<F, T&&> && | |
detail::can_optional<std::invoke_result_t<F, T&&>> | |
[[nodiscard]] | |
constexpr auto map(F&& func) && -> optional<std::invoke_result_t<F, T&&>> | |
{ | |
using R = optional<std::invoke_result_t<F, T&>>; | |
if (engaged_) { | |
return R(std::invoke(FLUX_FWD(func), std::move(*this).value_unchecked())); | |
} else { | |
return nullopt; | |
} | |
} | |
template <typename F> | |
requires std::invocable<F, T const&&> && | |
detail::can_optional<std::invoke_result_t<F, T const&&>> | |
[[nodiscard]] | |
constexpr auto map(F&& func) const&& -> optional<std::invoke_result_t<F, T const&&>> | |
{ | |
using R = optional<std::invoke_result_t<F, T const&&>>; | |
if (engaged_) { | |
return R(std::invoke(FLUX_FWD(func), std::move(*this).value_unchecked())); | |
} else { | |
return nullopt; | |
} | |
} | |
}; | |
template <typename T> | |
optional(T) -> optional<T>; | |
template <detail::can_optional T> | |
class optional<T&> { | |
T* ptr_ = nullptr; | |
static void test_fn(T&); | |
public: | |
optional() = default; | |
constexpr explicit(false) optional(nullopt_t) noexcept {}; | |
template <typename U = T> | |
requires requires(U& u) { test_fn(u); } | |
constexpr explicit optional(U& item) noexcept | |
: ptr_(std::addressof(item)) | |
{} | |
/* | |
* Observers | |
*/ | |
[[nodiscard]] | |
constexpr auto has_value() const noexcept { return ptr_ != nullptr; } | |
[[nodiscard]] | |
constexpr explicit operator bool() const noexcept { return ptr_ != nullptr; } | |
[[nodiscard]] | |
constexpr auto operator*() const -> T& | |
{ | |
if (!std::is_constant_evaluated()) { | |
FLUX_ASSERT(ptr_ != nullptr); | |
} | |
return *ptr_; | |
} | |
constexpr auto operator->() const -> T* | |
{ | |
if (!std::is_constant_evaluated()) { | |
FLUX_ASSERT(ptr_ != nullptr); | |
} | |
return ptr_; | |
} | |
[[nodiscard]] | |
constexpr auto value() const -> T& { return **this; } | |
[[nodiscard]] | |
constexpr auto value_unchecked() const noexcept -> T& { return *ptr_; } | |
[[nodiscard]] | |
constexpr auto value_or(auto&& alt) const | |
-> decltype(has_value() ? value_unchecked() : FLUX_FWD(alt)) | |
{ | |
return has_value() ? value_unchecked() : FLUX_FWD(alt); | |
} | |
[[nodiscard]] | |
friend constexpr auto operator==(optional const& o, nullopt_t) -> bool | |
{ | |
return !o.has_value(); | |
} | |
[[nodiscard]] | |
friend constexpr auto operator<=>(optional const& o, nullopt_t) | |
-> std::strong_ordering | |
{ | |
return o.has_value() ? std::strong_ordering::greater | |
: std::strong_ordering::equivalent; | |
} | |
/* | |
* Modifiers | |
*/ | |
constexpr auto reset() -> void { ptr_ = nullptr; } | |
template <typename U = T> | |
requires requires(U& u) { test_fn(u); } | |
constexpr auto emplace(U& item) -> void { ptr_ = std::addressof(item); } | |
/* | |
* Monadic operations | |
*/ | |
template <typename F> | |
requires std::invocable<F, T&> && | |
detail::can_optional<std::invoke_result_t<F, T&>> | |
[[nodiscard]] | |
constexpr auto map(F&& func) const -> optional<std::invoke_result_t<F, T&>> | |
{ | |
using R = optional<std::invoke_result_t<F, T&>>; | |
if (ptr_) { | |
return R(std::invoke(FLUX_FWD(func), *ptr_)); | |
} else { | |
return nullopt; | |
} | |
} | |
}; | |
} // namespace flux | |
#endif | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_SEQUENCE_ACCESS_HPP_INCLUDED | |
#define FLUX_CORE_SEQUENCE_ACCESS_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
struct first_fn { | |
template <sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq) const | |
noexcept(noexcept(traits_t<Seq>::first(seq))) -> cursor_t<Seq> | |
{ | |
return traits_t<Seq>::first(seq); | |
} | |
}; | |
struct is_last_fn { | |
template <sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> const& cur) const | |
noexcept(noexcept(traits_t<Seq>::is_last(seq, cur))) -> bool | |
{ | |
return traits_t<Seq>::is_last(seq, cur); | |
} | |
}; | |
struct read_at_fn { | |
template <sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> const& cur) const | |
noexcept(noexcept(traits_t<Seq>::read_at(seq, cur))) -> element_t<Seq> | |
{ | |
return traits_t<Seq>::read_at(seq, cur); | |
} | |
}; | |
struct inc_fn { | |
template <sequence Seq> | |
constexpr auto operator()(Seq& seq, cursor_t<Seq>& cur) const | |
noexcept(noexcept(traits_t<Seq>::inc(seq, cur))) -> cursor_t<Seq>& | |
{ | |
(void) traits_t<Seq>::inc(seq, cur); | |
return cur; | |
} | |
template <random_access_sequence Seq> | |
constexpr auto operator()(Seq& seq, cursor_t<Seq>& cur, int_t offset) const | |
noexcept(noexcept(traits_t<Seq>::inc(seq, cur, offset))) -> cursor_t<Seq>& | |
{ | |
(void) traits_t<Seq>::inc(seq, cur, offset); | |
return cur; | |
} | |
}; | |
struct dec_fn { | |
template <bidirectional_sequence Seq> | |
constexpr auto operator()(Seq& seq, cursor_t<Seq>& cur) const | |
noexcept(noexcept(traits_t<Seq>::dec(seq, cur))) -> cursor_t<Seq>& | |
{ | |
(void) traits_t<Seq>::dec(seq, cur); | |
return cur; | |
} | |
}; | |
struct distance_fn { | |
template <multipass_sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> const& from, cursor_t<Seq> const& to) const | |
-> int_t | |
{ | |
if constexpr (random_access_sequence<Seq>) { | |
return traits_t<Seq>::distance(seq, from, to); | |
} else { | |
int_t n = 0; | |
auto from_ = from; | |
while (from_ != to) { | |
inc_fn{}(seq, from_); | |
++n; | |
} | |
return n; | |
} | |
} | |
}; | |
struct data_fn { | |
template <contiguous_sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq) const | |
noexcept(noexcept(traits_t<Seq>::data(seq))) | |
{ | |
return traits_t<Seq>::data(seq); | |
} | |
}; | |
struct last_fn { | |
template <bounded_sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq) const | |
noexcept(noexcept(traits_t<Seq>::last(seq))) -> cursor_t<Seq> | |
{ | |
return traits_t<Seq>::last(seq); | |
} | |
}; | |
struct size_fn { | |
template <sized_sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const -> int_t | |
{ | |
return traits_t<Seq>::size(seq); | |
} | |
}; | |
struct usize_fn { | |
template <sized_sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const -> std::size_t | |
{ | |
return num::unchecked_cast<std::size_t>(size_fn{}(seq)); | |
} | |
}; | |
struct move_at_fn { | |
template <sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> const& cur) const | |
-> rvalue_element_t<Seq> | |
{ | |
return traits_t<Seq>::move_at(seq, cur); | |
} | |
}; | |
struct read_at_unchecked_fn { | |
template <sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> const& cur) const | |
-> element_t<Seq> | |
{ | |
return traits_t<Seq>::read_at_unchecked(seq, cur); | |
} | |
}; | |
struct move_at_unchecked_fn { | |
template <sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> const& cur) const | |
-> rvalue_element_t<Seq> | |
{ | |
return traits_t<Seq>::move_at_unchecked(seq, cur); | |
} | |
}; | |
struct seq_for_each_while_fn { | |
template <sequence Seq, typename Pred> | |
requires std::invocable<Pred&, element_t<Seq>> && | |
boolean_testable<std::invoke_result_t<Pred&, element_t<Seq>>> | |
constexpr auto operator()(Seq&& seq, Pred pred) const -> cursor_t<Seq> | |
{ | |
return traits_t<Seq>::for_each_while(seq, std::move(pred)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto first = detail::first_fn{}; | |
FLUX_EXPORT inline constexpr auto is_last = detail::is_last_fn{}; | |
FLUX_EXPORT inline constexpr auto read_at = detail::read_at_fn{}; | |
FLUX_EXPORT inline constexpr auto move_at = detail::move_at_fn{}; | |
FLUX_EXPORT inline constexpr auto read_at_unchecked = detail::read_at_unchecked_fn{}; | |
FLUX_EXPORT inline constexpr auto move_at_unchecked = detail::move_at_unchecked_fn{}; | |
FLUX_EXPORT inline constexpr auto inc = detail::inc_fn{}; | |
FLUX_EXPORT inline constexpr auto dec = detail::dec_fn{}; | |
FLUX_EXPORT inline constexpr auto distance = detail::distance_fn{}; | |
FLUX_EXPORT inline constexpr auto data = detail::data_fn{}; | |
FLUX_EXPORT inline constexpr auto last = detail::last_fn{}; | |
FLUX_EXPORT inline constexpr auto size = detail::size_fn{}; | |
FLUX_EXPORT inline constexpr auto usize = detail::usize_fn{}; | |
FLUX_EXPORT inline constexpr auto seq_for_each_while = detail::seq_for_each_while_fn {}; | |
namespace detail { | |
struct next_fn { | |
template <sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> cur) const | |
noexcept(noexcept(inc(seq, cur))) | |
-> cursor_t<Seq> | |
{ | |
return inc(seq, cur); | |
} | |
template <sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> cur, int_t offset) const -> cursor_t<Seq> | |
{ | |
if constexpr (random_access_sequence<Seq>) { | |
return inc(seq, cur, offset); | |
} else if constexpr (bidirectional_sequence<Seq>) { | |
auto const zero = int_t {0}; | |
if (offset > zero) { | |
while (offset-- > zero) { | |
inc(seq, cur); | |
} | |
} else { | |
while (offset++ < zero) { | |
dec(seq, cur); | |
} | |
} | |
return cur; | |
} else { | |
while (offset-- > int_t {0}) { | |
inc(seq, cur); | |
} | |
return cur; | |
} | |
} | |
}; | |
struct prev_fn { | |
template <sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> cur) const | |
noexcept(noexcept(dec(seq, cur))) | |
-> cursor_t<Seq> | |
{ | |
return dec(seq, cur); | |
} | |
}; | |
struct is_empty_fn { | |
template <sequence Seq> | |
requires (multipass_sequence<Seq> || sized_sequence<Seq>) | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const -> bool | |
{ | |
if constexpr (sized_sequence<Seq>) { | |
return flux::size(seq) == 0; | |
} else { | |
return is_last(seq, first(seq)); | |
} | |
} | |
}; | |
template <typename Seq1, typename Seq2> | |
concept element_swappable_with_ = | |
std::constructible_from<value_t<Seq1>, rvalue_element_t<Seq1>> && | |
writable_sequence_of<Seq1, rvalue_element_t<Seq2>> && | |
writable_sequence_of<Seq2, value_t<Seq1>&&>; | |
template <typename Seq1, typename Seq2> | |
concept element_swappable_with = | |
element_swappable_with_<Seq1, Seq2> && | |
element_swappable_with_<Seq2, Seq1>; | |
struct swap_with_fn { | |
template <sequence Seq1, sequence Seq2> | |
constexpr void operator()(Seq1& seq1, cursor_t<Seq1> const& cur1, | |
Seq2& seq2, cursor_t<Seq2> const& cur2) const | |
requires (std::swappable_with<element_t<Seq1>, element_t<Seq2>> || | |
element_swappable_with<Seq1, Seq2>) | |
{ | |
if constexpr (std::swappable_with<element_t<Seq1>, element_t<Seq2>>) { | |
return std::ranges::swap(read_at(seq1, cur1), read_at(seq2, cur2)); | |
} else { | |
value_t<Seq1> temp(move_at(seq1, cur1)); | |
read_at(seq1, cur1) = move_at(seq2, cur2); | |
read_at(seq2, cur2) = std::move(temp); | |
} | |
} | |
}; | |
struct swap_at_fn { | |
template <sequence Seq> | |
constexpr void operator()(Seq& seq, cursor_t<Seq> const& first, | |
cursor_t<Seq> const& second) const | |
requires requires { swap_with_fn{}(seq, first, seq, second); } | |
{ | |
return swap_with_fn{}(seq, first, seq, second); | |
} | |
}; | |
struct front_fn { | |
template <multipass_sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq) const -> optional<element_t<Seq>> | |
{ | |
auto cur = first(seq); | |
if (!is_last(seq, cur)) { | |
return optional<element_t<Seq>>(read_at(seq, cur)); | |
} else { | |
return nullopt; | |
} | |
} | |
}; | |
struct back_fn { | |
template <bidirectional_sequence Seq> | |
requires bounded_sequence<Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq) const -> optional<element_t<Seq>> | |
{ | |
auto cur = last(seq); | |
if (cur != first(seq)) { | |
return optional<element_t<Seq>>(read_at(seq, dec(seq, cur))); | |
} else { | |
return nullopt; | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto next = detail::next_fn{}; | |
FLUX_EXPORT inline constexpr auto prev = detail::prev_fn{}; | |
FLUX_EXPORT inline constexpr auto is_empty = detail::is_empty_fn{}; | |
FLUX_EXPORT inline constexpr auto swap_with = detail::swap_with_fn{}; | |
FLUX_EXPORT inline constexpr auto swap_at = detail::swap_at_fn{}; | |
FLUX_EXPORT inline constexpr auto front = detail::front_fn{}; | |
FLUX_EXPORT inline constexpr auto back = detail::back_fn{}; | |
} // namespace flux | |
#endif | |
namespace flux { | |
/* | |
* MARK: Iteration context | |
*/ | |
FLUX_EXPORT | |
enum class iteration_result : bool { incomplete = false, complete = true }; | |
FLUX_EXPORT inline constexpr bool loop_break = false; | |
FLUX_EXPORT inline constexpr bool loop_continue = true; | |
FLUX_EXPORT template <typename Ctx> | |
using context_element_t = typename std::remove_cvref_t<Ctx>::element_type; | |
FLUX_EXPORT template <typename Ctx> | |
concept iteration_context | |
= requires { typename Ctx::element_type; } && detail::can_reference<typename Ctx::element_type> | |
&& requires(Ctx& ctx, bool (*pred)(context_element_t<Ctx>)) { | |
{ ctx.run_while(flux::copy(pred)) } -> std::same_as<iteration_result>; | |
}; | |
FLUX_EXPORT | |
struct run_while_t { | |
template <iteration_context Ctx, typename Pred> | |
requires callable_mut<Pred, bool(context_element_t<Ctx>)> | |
constexpr auto operator()(Ctx& ctx, Pred&& pred) const -> iteration_result | |
{ | |
return ctx.run_while(FLUX_FWD(pred)); | |
} | |
}; | |
FLUX_EXPORT inline constexpr run_while_t run_while {}; | |
FLUX_EXPORT | |
struct step_t { | |
template <iteration_context Ctx, typename Fn> | |
requires callable_once<Fn, void(context_element_t<Ctx>)> | |
constexpr auto operator()(Ctx& ctx, Fn fn) const | |
{ | |
using E = context_element_t<Ctx>; | |
using R = callable_result_t<Fn, E>; | |
if constexpr (std::is_void_v<R>) { | |
bool called = false; | |
run_while(ctx, [&](auto&& elem) { | |
std::move(fn)(FLUX_FWD(elem)); | |
called = true; | |
return loop_break; | |
}); | |
return called; | |
} else { | |
// If element_type is an rvalue reference, call with a value instead | |
using T = std::conditional_t<std::is_rvalue_reference_v<E>, std::remove_cvref_t<E>, E>; | |
flux::optional<T> opt = nullopt; | |
run_while(ctx, [&](auto&& elem) { | |
opt.emplace(std::move(fn)(FLUX_FWD(elem))); | |
return loop_break; | |
}); | |
return opt; | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr step_t step{}; | |
FLUX_EXPORT | |
struct next_element_t { | |
template <iteration_context Ctx> | |
constexpr auto operator()(Ctx& ctx) const | |
-> std::conditional_t<std::is_rvalue_reference_v<context_element_t<Ctx>>, | |
flux::optional<std::remove_cvref_t<context_element_t<Ctx>>>, | |
flux::optional<context_element_t<Ctx>>> | |
{ | |
return step(ctx, std::identity{}); | |
} | |
}; | |
FLUX_EXPORT inline constexpr next_element_t next_element{}; | |
/* | |
* MARK: Iterable | |
*/ | |
FLUX_EXPORT template <typename> | |
struct iterable_traits { | |
using _flux_is_primary_template = std::true_type; | |
}; | |
namespace detail { | |
template <typename T> | |
concept has_member_iterable_traits = requires { typename T::flux_iterable_traits; } | |
&& std::is_class_v<typename T::flux_iterable_traits>; | |
} // namespace detail | |
template <typename T> | |
requires detail::has_member_iterable_traits<T> | |
struct iterable_traits<T> : T::flux_iterable_traits { }; | |
namespace detail { | |
template <typename It> | |
using iter_traits_t = iterable_traits<std::remove_cvref_t<It>>; | |
template <typename T> | |
concept has_iter_traits = !requires { typename iter_traits_t<T>::_flux_is_primary_template; }; | |
template <typename T> | |
concept has_valid_iter_traits = has_iter_traits<T> && requires(T& t) { | |
{ iter_traits_t<T>::iterate(t) } -> iteration_context; | |
}; | |
template <typename T> | |
concept has_member_iterate = requires(T& t) { | |
{ t.iterate() } -> iteration_context; | |
}; | |
template <typename T> | |
concept can_iterate = has_valid_iter_traits<T> || has_member_iterate<T> || sequence<T> | |
|| std::ranges::input_range<T>; | |
template <std::ranges::input_range R> | |
struct range_iteration_context : immovable { | |
private: | |
std::ranges::iterator_t<R> iter_; | |
std::ranges::sentinel_t<R> sent_; | |
bool increment_next_ = false; | |
public: | |
using element_type = std::ranges::range_reference_t<R>; | |
constexpr explicit range_iteration_context(R& rng) | |
: iter_(std::ranges::begin(rng)), | |
sent_(std::ranges::end(rng)) | |
{ | |
} | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
if (increment_next_ && iter_ != sent_) { | |
++iter_; | |
increment_next_ = false; | |
} | |
while (iter_ != sent_) { | |
if (!pred(*iter_)) { | |
increment_next_ = true; | |
return iteration_result::incomplete; | |
} | |
++iter_; | |
} | |
return iteration_result::complete; | |
} | |
}; | |
template <std::ranges::forward_range R> | |
struct range_iteration_context<R> : immovable { | |
private: | |
std::ranges::iterator_t<R> iter_; | |
std::ranges::sentinel_t<R> sent_; | |
public: | |
using element_type = std::ranges::range_reference_t<R>; | |
constexpr explicit range_iteration_context(R& rng) | |
: iter_(std::ranges::begin(rng)), | |
sent_(std::ranges::end(rng)) | |
{ | |
} | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
while (iter_ != sent_) { | |
if (!pred(*iter_++)) { | |
return iteration_result::incomplete; | |
} | |
} | |
return iteration_result::complete; | |
} | |
}; | |
template <sequence Seq> | |
struct sequence_iteration_context : immovable { | |
private: | |
Seq* ptr_; | |
cursor_t<Seq> cur_; | |
bool inc_next_ = false; | |
public: | |
using element_type = element_t<Seq>; | |
constexpr explicit sequence_iteration_context(Seq& seq) | |
: ptr_(std::addressof(seq)), | |
cur_(first(seq)) | |
{ | |
} | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
if (inc_next_ && !is_last(*ptr_, cur_)) { | |
inc(*ptr_, cur_); | |
inc_next_ = false; | |
} | |
while (!is_last(*ptr_, cur_)) { | |
if (!pred(read_at_unchecked(*ptr_, cur_))) { | |
inc_next_ = true; | |
return iteration_result::incomplete; | |
} | |
inc(*ptr_, cur_); | |
} | |
return iteration_result::complete; | |
} | |
}; | |
template <multipass_sequence Seq> | |
struct sequence_iteration_context<Seq> { | |
private: | |
Seq* ptr_; | |
cursor_t<Seq> cur_; | |
public: | |
using element_type = element_t<Seq>; | |
constexpr explicit sequence_iteration_context(Seq& seq) | |
: ptr_(std::addressof(seq)), | |
cur_(first(seq)) | |
{ | |
} | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
while (!is_last(*ptr_, cur_)) { | |
if (!pred(read_at_unchecked(*ptr_, cur_))) { | |
inc(*ptr_, cur_); | |
return iteration_result::incomplete; | |
} | |
inc(*ptr_, cur_); | |
} | |
return iteration_result::complete; | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
struct iterate_t { | |
template <typename It> | |
requires detail::can_iterate<It> | |
constexpr auto operator()(It& it) const -> iteration_context auto | |
{ | |
if constexpr (detail::has_valid_iter_traits<It>) { | |
return detail::iter_traits_t<It>::iterate(it); | |
} else if constexpr (detail::has_member_iterate<It>) { | |
return it.iterate(); | |
} else if constexpr (sequence<It>) { | |
return detail::sequence_iteration_context<It>(it); | |
} else if constexpr (std::ranges::input_range<It>) { | |
return detail::range_iteration_context<It>(it); | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr iterate_t iterate {}; | |
FLUX_EXPORT template <typename I> | |
using iteration_context_t = decltype(iterate(std::declval<I&>())); | |
FLUX_EXPORT template <typename I> | |
using iterable_element_t = context_element_t<iteration_context_t<I>>; | |
namespace detail { | |
// Try to work out the value type of the iterable | |
// * If iterable_context_t<T>::value_type exists, use that | |
// * otherwise, if iterable_traits<T>::value_type exists, use that | |
// * otherwise, if T::value_type exists, use that, | |
// * otherwise, fall back to std::remove_cvref_t<iterable_element_t<T>> | |
template <typename T> | |
concept has_context_value_type = requires { typename iteration_context_t<T>::value_type; }; | |
template <typename T> | |
concept has_traits_value_type = requires { typename iter_traits_t<T>::value_type; }; | |
template <typename T> | |
concept has_member_value_type = requires { typename T::value_type; }; | |
template <typename T> | |
struct iterable_value_type { | |
using type = std::remove_cvref_t<iterable_element_t<T>>; | |
}; | |
template <typename T> | |
requires has_context_value_type<T> | |
struct iterable_value_type<T> { | |
using type = typename iteration_context_t<T>::value_type; | |
}; | |
template <typename T> | |
requires has_traits_value_type<T> && (!has_context_value_type<T>) | |
struct iterable_value_type<T> { | |
using type = typename iter_traits_t<T>::value_type; | |
}; | |
template <typename T> | |
requires has_member_value_type<T> && (!has_context_value_type<T> && !has_traits_value_type<T>) | |
struct iterable_value_type<T> { | |
using type = typename T::value_type; | |
}; | |
} // namespace detail | |
FLUX_EXPORT template <typename I> | |
using iterable_value_t = typename detail::iterable_value_type<I>::type; | |
FLUX_EXPORT template <typename It> | |
using iterable_common_element_t | |
= std::common_reference_t<iterable_element_t<It>, iterable_value_t<It>&>; | |
FLUX_EXPORT template <typename It> | |
using iterable_const_element_t | |
= std::common_reference_t<iterable_value_t<It> const&&, iterable_element_t<It>>; | |
FLUX_EXPORT template <typename It> | |
concept iterable = requires(It& it) { | |
{ iterate(it) } -> iteration_context; | |
} && requires { | |
typename iterable_value_t<It>; | |
} && std::is_object_v<iterable_value_t<It>> | |
#ifdef FLUX_HAVE_CPP23_TUPLE_COMMON_REF | |
&& std::common_reference_with<iterable_element_t<It>&&, iterable_value_t<It>&> | |
#endif | |
; | |
/* | |
* MARK: Sized iterable | |
*/ | |
namespace detail { | |
template <typename T> | |
concept has_iterable_traits_size = requires(T& t) { | |
{ iter_traits_t<T>::size(t) } -> num::integral; | |
}; | |
template <typename T> | |
concept has_member_size = has_member_iterate<T> && requires(T& t) { | |
{ t.size() } -> num::integral; | |
}; | |
template <typename T> | |
concept is_sized_iterable = has_iterable_traits_size<T> || has_member_size<T> || sized_sequence<T> | |
|| std::ranges::sized_range<T>; | |
} // namespace detail | |
struct iterable_size_fn_t { | |
template <typename It> | |
requires detail::is_sized_iterable<It> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it) const -> int_t | |
{ | |
if constexpr (detail::has_iterable_traits_size<It>) { | |
return num::cast<int_t>(detail::iter_traits_t<It>::size(it)); | |
} else if constexpr (detail::has_member_size<It>) { | |
return num::cast<int_t>(it.size()); | |
} else if constexpr (sized_sequence<It>) { | |
return sequence_traits<std::remove_cvref_t<It>>::size(it); | |
} else if constexpr (std::ranges::sized_range<It>) { | |
return num::cast<int_t>(std::ranges::ssize(it)); | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr iterable_size_fn_t iterable_size{}; | |
FLUX_EXPORT template <typename It> | |
concept sized_iterable = iterable<It> && requires(It&& it) { | |
{ iterable_size(it) } -> std::same_as<int_t>; | |
}; | |
/* | |
* MARK: Reverse iterable | |
*/ | |
namespace detail { | |
template <typename T> | |
concept has_reverse_iter_traits = requires(T& t) { | |
{ iter_traits_t<T>::reverse_iterate(t) } -> iteration_context; | |
}; | |
template <typename T> | |
concept has_member_reverse_iterate = has_member_iterate<T> && requires(T& t) { | |
{ t.reverse_iterate() } -> iteration_context; | |
}; | |
template <typename T> | |
concept can_reverse_iterate = has_reverse_iter_traits<T> || has_member_reverse_iterate<T> | |
|| (bidirectional_sequence<T> && bounded_sequence<T>) | |
|| (std::ranges::bidirectional_range<T> && std::ranges::common_range<T>); | |
template <std::ranges::bidirectional_range R> | |
requires std::ranges::common_range<R> | |
struct range_reverse_iteration_context : immovable { | |
private: | |
std::ranges::iterator_t<R> iter_; | |
std::ranges::iterator_t<R> start_; | |
public: | |
using element_type = std::ranges::range_reference_t<R>; | |
constexpr explicit range_reverse_iteration_context(R& rng) | |
: iter_(std::ranges::end(rng)), | |
start_(std::ranges::begin(rng)) | |
{ | |
} | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
while (iter_ != start_) { | |
if (!pred(*--iter_)) { | |
return iteration_result::incomplete; | |
} | |
} | |
return iteration_result::complete; | |
} | |
}; | |
template <bidirectional_sequence Seq> | |
requires bounded_sequence<Seq> | |
struct sequence_reverse_iteration_context : immovable { | |
private: | |
Seq* ptr_; | |
cursor_t<Seq> cur_; | |
cursor_t<Seq> start_; | |
public: | |
using element_type = element_t<Seq>; | |
constexpr explicit sequence_reverse_iteration_context(Seq& seq) | |
: ptr_(std::addressof(seq)), | |
cur_(last(seq)), | |
start_(first(seq)) | |
{ | |
} | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
while (cur_ != start_) { | |
dec(*ptr_, cur_); | |
if (!pred(read_at_unchecked(*ptr_, cur_))) { | |
return iteration_result::incomplete; | |
} | |
} | |
return iteration_result::complete; | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT struct reverse_iterate_t { | |
template <typename It> | |
requires detail::can_reverse_iterate<It> | |
constexpr auto operator()(It& it) const -> iteration_context auto | |
{ | |
if constexpr (detail::has_reverse_iter_traits<It>) { | |
return iterable_traits<It>::reverse_iterate(it); | |
} else if constexpr (detail::has_member_reverse_iterate<It>) { | |
return it.reverse_iterate(); | |
} else if constexpr (bidirectional_sequence<It> && bounded_sequence<It>) { | |
return detail::sequence_reverse_iteration_context<It>(it); | |
} else if constexpr (std::ranges::bidirectional_range<It> | |
&& std::ranges::common_range<It>) { | |
return detail::range_reverse_iteration_context<It>(it); | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr reverse_iterate_t reverse_iterate {}; | |
FLUX_EXPORT template <typename I> | |
using reverse_iteration_context_t = decltype(reverse_iterate(std::declval<I&>())); | |
FLUX_EXPORT template <typename It> | |
concept reverse_iterable = iterable<It> && requires(It& it) { | |
{ reverse_iterate(it) } -> iteration_context; | |
} && std::same_as<iterable_element_t<It>, context_element_t<reverse_iteration_context_t<It>>>; | |
/* | |
* MARK: Other concepts | |
*/ | |
FLUX_EXPORT template <typename I> | |
concept adaptable_iterable = iterable<std::decay_t<I>> | |
&& (detail::movable_rvalue<I> || detail::trivially_copyable_lvalue<I>); | |
} // namespace flux | |
#endif // FLUX_CORE_ITERABLE_CONCEPTS_HPP_INCLUDED | |
#include <ranges> | |
namespace flux { | |
namespace detail { | |
template <typename It> | |
struct iterable_range : std::ranges::view_interface<iterable_range<It>> { | |
private: | |
It iterable_; | |
iteration_context_t<It> ctx_ = iterate(iterable_); | |
using opt_t = decltype(next_element(ctx_)); | |
opt_t next_ = next_element(ctx_); | |
struct iterator { | |
private: | |
iterable_range* parent_ = nullptr; | |
public: | |
using reference = iterable_element_t<It>; | |
using value_type = iterable_value_t<It>; | |
using difference_type = int_t; | |
iterator() = default; | |
explicit iterator(iterable_range& parent) : parent_(std::addressof(parent)) { } | |
iterator(iterator&&) = default; | |
iterator& operator=(iterator&&) = default; | |
constexpr auto operator*() const -> reference | |
{ | |
return static_cast<reference>(parent_->next_.value()); | |
} | |
constexpr auto operator++() -> iterator& | |
{ | |
parent_->next_ = next_element(parent_->ctx_); | |
return *this; | |
} | |
constexpr auto operator++(int) -> void { ++*this; } | |
constexpr auto operator==(std::default_sentinel_t) const -> bool | |
{ | |
return !static_cast<bool>(parent_->next_); | |
} | |
}; | |
public: | |
explicit constexpr iterable_range(decays_to<It> auto&& it) : iterable_(FLUX_FWD(it)) { } | |
constexpr auto begin() -> iterator { return iterator(*this); } | |
static constexpr auto end() -> std::default_sentinel_t { return {}; } | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
struct as_range_t { | |
template <iterable It> | |
constexpr auto operator()(It&& it) const -> std::ranges::input_range decltype(auto) | |
{ | |
if constexpr (std::ranges::input_range<It>) { | |
return FLUX_FWD(it); | |
} else { | |
if constexpr (std::is_lvalue_reference_v<It>) { | |
return detail::iterable_range<std::reference_wrapper<It>>(std::ref(it)); | |
} else { | |
return detail::iterable_range<It>(it); | |
} | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr as_range_t as_range{}; | |
} // namespace flux | |
#endif // FLUX_CORE_AS_RANGE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_DEFAULT_IMPLS_HPP_INCLUDED | |
#define FLUX_CORE_DEFAULT_IMPLS_HPP_INCLUDED | |
#include <functional> | |
#include <ranges> | |
namespace flux { | |
/* | |
* Default implementation for C arrays of known bound | |
*/ | |
template <typename T, index_t N> | |
struct sequence_traits<T[N]> : default_sequence_traits { | |
static constexpr auto first(auto const&) -> index_t { return index_t{0}; } | |
static constexpr bool is_last(auto const&, index_t idx) { return idx >= N; } | |
static constexpr auto read_at(auto& self, index_t idx) -> decltype(auto) | |
{ | |
indexed_bounds_check(idx, N); | |
return self[idx]; | |
} | |
static constexpr auto read_at_unchecked(auto& self, index_t idx) -> decltype(auto) | |
{ | |
return self[idx]; | |
} | |
static constexpr auto inc(auto const&, index_t& idx) | |
{ | |
FLUX_DEBUG_ASSERT(idx < N); | |
idx = num::add(idx, int_t {1}); | |
} | |
static constexpr auto last(auto const&) -> index_t { return N; } | |
static constexpr auto dec(auto const&, index_t& idx) | |
{ | |
FLUX_DEBUG_ASSERT(idx > 0); | |
idx = num::sub(idx, int_t {1}); | |
} | |
static constexpr auto inc(auto const&, index_t& idx, int_t offset) | |
{ | |
FLUX_DEBUG_ASSERT(num::add(idx, offset) <= N); | |
FLUX_DEBUG_ASSERT(num::add(idx, offset) >= 0); | |
idx = num::add(idx, offset); | |
} | |
static constexpr auto distance(auto const&, index_t from, index_t to) -> int_t | |
{ | |
return num::sub(to, from); | |
} | |
static constexpr auto data(auto& self) -> auto* { return self; } | |
static constexpr auto size(auto const&) -> int_t { return N; } | |
static constexpr auto for_each_while(auto& self, auto&& pred) -> index_t | |
{ | |
index_t idx = 0; | |
while (idx < N) { | |
if (!std::invoke(pred, self[idx])) { | |
break; | |
} | |
++idx; | |
} | |
return idx; | |
} | |
}; | |
/* | |
* Default implementation for std::reference_wrapper<T> | |
*/ | |
template <sequence Seq> | |
struct sequence_traits<std::reference_wrapper<Seq>> : default_sequence_traits { | |
using self_t = std::reference_wrapper<Seq>; | |
using value_type = value_t<Seq>; | |
static constexpr bool disable_multipass = !multipass_sequence<Seq>; | |
static constexpr auto first(self_t self) -> cursor_t<Seq> | |
{ | |
return flux::first(self.get()); | |
} | |
static constexpr bool is_last(self_t self, cursor_t<Seq> const& cur) | |
{ | |
return flux::is_last(self.get(), cur); | |
} | |
static constexpr auto read_at(self_t self, cursor_t<Seq> const& cur) | |
-> decltype(auto) | |
{ | |
return flux::read_at(self.get(), cur); | |
} | |
static constexpr auto read_at_unchecked(self_t self, cursor_t<Seq> const& cur) | |
-> decltype(auto) | |
{ | |
return flux::read_at_unchecked(self.get(), cur); | |
} | |
static constexpr auto inc(self_t self, cursor_t<Seq>& cur) | |
-> cursor_t<Seq>& | |
{ | |
return flux::inc(self.get(), cur); | |
} | |
static constexpr auto dec(self_t self, cursor_t<Seq>& cur) | |
-> cursor_t<Seq>& | |
requires bidirectional_sequence<Seq> | |
{ | |
return flux::dec(self.get(), cur); | |
} | |
static constexpr auto inc(self_t self, cursor_t<Seq>& cur, int_t offset) -> cursor_t<Seq>& | |
requires random_access_sequence<Seq> | |
{ | |
return flux::inc(self.get(), cur, offset); | |
} | |
static constexpr auto distance(self_t self, cursor_t<Seq> const& from, cursor_t<Seq> const& to) | |
-> int_t | |
requires random_access_sequence<Seq> | |
{ | |
return flux::distance(self.get(), from, to); | |
} | |
static constexpr auto data(self_t self) | |
requires contiguous_sequence<Seq> | |
{ | |
return flux::data(self.get()); | |
} | |
static constexpr auto last(self_t self) -> cursor_t<Seq> | |
requires bounded_sequence<Seq> | |
{ | |
return flux::last(self.get()); | |
} | |
static constexpr auto size(self_t self) -> int_t | |
requires sized_sequence<Seq> | |
{ | |
return flux::size(self.get()); | |
} | |
static constexpr auto move_at(self_t self, cursor_t<Seq> const& cur) | |
-> rvalue_element_t<Seq> | |
{ | |
return flux::move_at(self.get(), cur); | |
} | |
}; | |
// Default implementation for contiguous, sized ranges | |
template <typename R> | |
requires (!detail::derived_from_inline_sequence_base<R> && | |
std::ranges::contiguous_range<R> && | |
std::ranges::sized_range<R> && | |
std::ranges::contiguous_range<R const> && | |
std::ranges::sized_range<R const>) | |
struct sequence_traits<R> : default_sequence_traits { | |
using value_type = std::ranges::range_value_t<R>; | |
static constexpr auto first(auto&) -> index_t { return index_t{0}; } | |
static constexpr auto is_last(auto& self, index_t idx) | |
{ | |
return idx >= size(self); | |
} | |
static constexpr auto inc(auto& self, index_t& idx) | |
{ | |
FLUX_DEBUG_ASSERT(idx < size(self)); | |
idx = num::add(idx, int_t {1}); | |
} | |
static constexpr auto read_at(auto& self, index_t idx) -> decltype(auto) | |
{ | |
indexed_bounds_check(idx, size(self)); | |
return data(self)[idx]; | |
} | |
static constexpr auto read_at_unchecked(auto& self, index_t idx) -> decltype(auto) | |
{ | |
return data(self)[idx]; | |
} | |
static constexpr auto dec(auto&, index_t& idx) | |
{ | |
FLUX_DEBUG_ASSERT(idx > 0); | |
idx = num::sub(idx, int_t {1}); | |
} | |
static constexpr auto last(auto& self) -> index_t { return size(self); } | |
static constexpr auto inc(auto& self, index_t& idx, int_t offset) | |
{ | |
FLUX_DEBUG_ASSERT(num::add(idx, offset) <= size(self)); | |
FLUX_DEBUG_ASSERT(num::add(idx, offset) >= 0); | |
idx = num::add(idx, offset); | |
} | |
static constexpr auto distance(auto&, index_t from, index_t to) -> int_t | |
{ | |
return num::sub(to, from); | |
} | |
static constexpr auto size(auto& self) -> int_t | |
{ | |
return num::cast<int_t>(std::ranges::ssize(self)); | |
} | |
static constexpr auto data(auto& self) -> auto* | |
{ | |
return std::ranges::data(self); | |
} | |
static constexpr auto for_each_while(auto& self, auto&& pred) -> index_t | |
{ | |
auto iter = std::ranges::begin(self); | |
auto const end = std::ranges::end(self); | |
while (iter != end) { | |
if (!std::invoke(pred, *iter)) { | |
break; | |
} | |
++iter; | |
} | |
return num::cast<index_t>(iter - std::ranges::begin(self)); | |
} | |
}; | |
} // namespace flux | |
#endif // FLUX_CORE_DEFAULT_IMPLS_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_FUNCTIONAL_HPP_INCLUDED | |
#define FLUX_CORE_FUNCTIONAL_HPP_INCLUDED | |
#include <functional> | |
#include <type_traits> | |
namespace flux { | |
FLUX_EXPORT | |
template <typename Fn, typename Proj = std::identity> | |
struct proj { | |
Fn fn; | |
Proj prj{}; | |
template <typename... Args> | |
constexpr auto operator()(Args&&... args) | |
noexcept(noexcept(std::invoke(fn, std::invoke(prj, FLUX_FWD(args))...))) | |
-> decltype(std::invoke(fn, std::invoke(prj, FLUX_FWD(args))...)) | |
{ | |
return std::invoke(fn, std::invoke(prj, FLUX_FWD(args))...); | |
} | |
template <typename... Args> | |
constexpr auto operator()(Args&&... args) const | |
noexcept(noexcept(std::invoke(fn, std::invoke(prj, FLUX_FWD(args))...))) | |
-> decltype(std::invoke(fn, std::invoke(prj, FLUX_FWD(args))...)) | |
{ | |
return std::invoke(fn, std::invoke(prj, FLUX_FWD(args))...); | |
} | |
}; | |
template <typename F, typename P = std::identity> | |
proj(F, P = {}) -> proj<F, P>; | |
FLUX_EXPORT | |
template <typename Fn, typename Lhs = std::identity, typename Rhs = std::identity> | |
struct proj2 { | |
Fn fn; | |
Lhs lhs{}; | |
Rhs rhs{}; | |
template <typename Arg1, typename Arg2> | |
constexpr auto operator()(Arg1&& arg1, Arg2&& arg2) | |
noexcept(noexcept(std::invoke(fn, std::invoke(lhs, FLUX_FWD(arg1)), | |
std::invoke(rhs, FLUX_FWD(arg2))))) | |
-> decltype(std::invoke(fn, std::invoke(lhs, FLUX_FWD(arg1)), | |
std::invoke(rhs, FLUX_FWD(arg2)))) | |
{ | |
return std::invoke(fn, std::invoke(lhs, FLUX_FWD(arg1)), | |
std::invoke(rhs, FLUX_FWD(arg2))); | |
} | |
template <typename Arg1, typename Arg2> | |
constexpr auto operator()(Arg1&& arg1, Arg2&& arg2) const | |
noexcept(noexcept(std::invoke(fn, std::invoke(lhs, FLUX_FWD(arg1)), | |
std::invoke(rhs, FLUX_FWD(arg2))))) | |
-> decltype(std::invoke(fn, std::invoke(lhs, FLUX_FWD(arg1)), | |
std::invoke(rhs, FLUX_FWD(arg2)))) | |
{ | |
return std::invoke(fn, std::invoke(lhs, FLUX_FWD(arg1)), | |
std::invoke(rhs, FLUX_FWD(arg2))); | |
} | |
}; | |
template <typename F, typename L = std::identity, typename R = std::identity> | |
proj2(F, L = {}, R = {}) -> proj2<F, L, R>; | |
namespace detail { | |
template <typename Func> | |
struct lazy_apply { | |
Func func_; | |
template <typename Tuple> | |
constexpr auto operator()(Tuple&& tuple) & | |
noexcept(noexcept(std::apply(func_, FLUX_FWD(tuple)))) | |
-> decltype(std::apply(func_, FLUX_FWD(tuple))) | |
{ | |
return std::apply(func_, FLUX_FWD(tuple)); | |
} | |
template <typename Tuple> | |
constexpr auto operator()(Tuple&& tuple) const& | |
noexcept(noexcept(std::apply(func_, FLUX_FWD(tuple)))) | |
-> decltype(std::apply(func_, FLUX_FWD(tuple))) | |
{ | |
return std::apply(func_, FLUX_FWD(tuple)); | |
} | |
template <typename Tuple> | |
constexpr auto operator()(Tuple&& tuple) && | |
noexcept(noexcept(std::apply(std::move(func_), FLUX_FWD(tuple)))) | |
-> decltype(std::apply(std::move(func_), FLUX_FWD(tuple))) | |
{ | |
return std::apply(std::move(func_), FLUX_FWD(tuple)); | |
} | |
template <typename Tuple> | |
constexpr auto operator()(Tuple&& tuple) const&& | |
noexcept(noexcept(std::apply(std::move(func_), FLUX_FWD(tuple)))) | |
-> decltype(std::apply(std::move(func_), FLUX_FWD(tuple))) | |
{ | |
return std::apply(std::move(func_), FLUX_FWD(tuple)); | |
} | |
}; | |
struct unpack_fn { | |
template <typename Func> | |
constexpr auto operator()(Func&& func) const | |
-> lazy_apply<std::decay_t<Func>> | |
{ | |
return lazy_apply<std::decay_t<Func>>{.func_ = FLUX_FWD(func)}; | |
} | |
}; | |
struct flip_fn { | |
template <typename Fn> | |
struct flipped { | |
Fn fn; | |
template <typename T, typename U, typename... Args> | |
requires std::invocable<Fn&, U, T, Args...> | |
constexpr auto operator()(T&& t, U&& u, Args&&... args) & | |
-> decltype(auto) | |
{ | |
return std::invoke(fn, FLUX_FWD(u), FLUX_FWD(t), FLUX_FWD(args)...); | |
} | |
template <typename T, typename U, typename... Args> | |
requires std::invocable<Fn const&, U, T, Args...> | |
constexpr auto operator()(T&& t, U&& u, Args&&... args) const& | |
-> decltype(auto) | |
{ | |
return std::invoke(fn, FLUX_FWD(u), FLUX_FWD(t), FLUX_FWD(args)...); | |
} | |
template <typename T, typename U, typename... Args> | |
requires std::invocable<Fn, U, T, Args...> | |
constexpr auto operator()(T&& t, U&& u, Args&&... args) && | |
-> decltype(auto) | |
{ | |
return std::invoke(std::move(fn), FLUX_FWD(u), FLUX_FWD(t), FLUX_FWD(args)...); | |
} | |
template <typename T, typename U, typename... Args> | |
requires std::invocable<Fn const, U, T, Args...> | |
constexpr auto operator()(T&& t, U&& u, Args&&... args) const && | |
-> decltype(auto) | |
{ | |
return std::invoke(std::move(fn), FLUX_FWD(u), FLUX_FWD(t), FLUX_FWD(args)...); | |
} | |
}; | |
template <typename Fn> | |
[[nodiscard]] | |
constexpr auto operator()(Fn func) const | |
{ | |
return flipped<Fn>{std::move(func)}; | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto unpack = detail::unpack_fn{}; | |
FLUX_EXPORT inline constexpr auto flip = detail::flip_fn{}; | |
namespace pred { | |
namespace detail { | |
template <typename Lambda> | |
struct predicate : Lambda {}; | |
template <typename L> | |
predicate(L) -> predicate<L>; | |
template <typename Op> | |
inline constexpr auto cmp = [](auto&& val) { | |
return predicate{[val = FLUX_FWD(val)](auto const& other) { | |
return Op{}(other, val); | |
}}; | |
}; | |
} // namespace detail | |
/// Given a predicate, returns a new predicate with the condition reversed | |
FLUX_EXPORT inline constexpr auto not_ = [](auto&& pred) { | |
return detail::predicate([p = FLUX_FWD(pred)] (auto const&... args) { | |
return !std::invoke(p, FLUX_FWD(args)...); | |
}); | |
}; | |
/// Returns a new predicate which is satisifed only if both the given predicates | |
/// return `true`. | |
/// | |
/// The returned predicate is short-circuiting: if the first predicate returns | |
/// `false`, the second will not be evaluated. | |
FLUX_EXPORT inline constexpr auto both = [](auto&& p, auto&& and_) { | |
return detail::predicate{[p1 = FLUX_FWD(p), p2 = FLUX_FWD(and_)] (auto const&... args) { | |
return std::invoke(p1, args...) && std::invoke(p2, args...); | |
}}; | |
}; | |
/// Returns a new predicate which is satisfied only if either of the given | |
/// predicates return `true`. | |
/// | |
/// The returned predicate is short-circuiting: if the first predicate returns | |
/// `true`, the second will not be evaluated | |
FLUX_EXPORT inline constexpr auto either = [](auto&& p, auto&& or_) { | |
return detail::predicate{[p1 = FLUX_FWD(p), p2 = FLUX_FWD(or_)] (auto const&... args) { | |
return std::invoke(p1, args...) || std::invoke(p2, args...); | |
}}; | |
}; | |
namespace detail { | |
FLUX_EXPORT | |
template <typename P> | |
constexpr auto operator!(detail::predicate<P> pred) | |
{ | |
return not_(std::move(pred)); | |
} | |
FLUX_EXPORT | |
template <typename L, typename R> | |
constexpr auto operator&&(detail::predicate<L> lhs, detail::predicate<R> rhs) | |
{ | |
return both(std::move(lhs), std::move(rhs)); | |
} | |
FLUX_EXPORT | |
template <typename L, typename R> | |
constexpr auto operator||(detail::predicate<L> lhs, detail::predicate<R> rhs) | |
{ | |
return either(std::move(lhs), std::move(rhs)); | |
} | |
} // namespace detail | |
/// Returns a new predicate with is satified only if both of the given | |
/// predicates return `false`. | |
/// | |
/// The returned predicate is short-circuiting: if the first predicate returns | |
/// `true`, the second will not be evaluated. | |
FLUX_EXPORT inline constexpr auto neither = [](auto&& p1, auto&& nor) { | |
return not_(either(FLUX_FWD(p1), FLUX_FWD(nor))); | |
}; | |
FLUX_EXPORT inline constexpr auto eq = detail::cmp<std::ranges::equal_to>; | |
FLUX_EXPORT inline constexpr auto neq = detail::cmp<std::ranges::not_equal_to>; | |
FLUX_EXPORT inline constexpr auto lt = detail::cmp<std::ranges::less>; | |
FLUX_EXPORT inline constexpr auto gt = detail::cmp<std::ranges::greater>; | |
FLUX_EXPORT inline constexpr auto leq = detail::cmp<std::ranges::less_equal>; | |
FLUX_EXPORT inline constexpr auto geq = detail::cmp<std::ranges::greater_equal>; | |
/// A predicate which always returns true | |
FLUX_EXPORT inline constexpr auto true_ = detail::predicate{[](auto const&...) -> bool { return true; }}; | |
/// A predicate which always returns false | |
FLUX_EXPORT inline constexpr auto false_ = detail::predicate{[](auto const&...) -> bool { return false; }}; | |
/// Identity predicate, returns the boolean value given to it | |
FLUX_EXPORT inline constexpr auto id = detail::predicate{[](bool b) -> bool { return b; }}; | |
/// Returns true if the given value is greater than a zero of the same type. | |
FLUX_EXPORT inline constexpr auto positive = detail::predicate{[](auto const& val) -> bool { | |
return val > decltype(val){0}; | |
}}; | |
/// Returns true if the given value is less than a zero of the same type. | |
FLUX_EXPORT inline constexpr auto negative = detail::predicate{[](auto const& val) -> bool { | |
return val < decltype(val){0}; | |
}}; | |
/// Returns true if the given value is not equal to a zero of the same type. | |
FLUX_EXPORT inline constexpr auto nonzero = detail::predicate{[](auto const& val) -> bool { | |
return val != decltype(val){0}; | |
}}; | |
/// Given a sequence of values, constructs a predicate which returns true | |
/// if its argument compares equal to one of the values | |
FLUX_EXPORT inline constexpr auto in = [](auto const&... vals) requires (sizeof...(vals) > 0) | |
{ | |
return detail::predicate{[vals...](auto const& arg) -> bool { | |
return ((arg == vals) || ...); | |
}}; | |
}; | |
FLUX_EXPORT inline constexpr auto even = detail::predicate([](auto const& val) -> bool { | |
return val % decltype(val){2} == decltype(val){0}; | |
}); | |
FLUX_EXPORT inline constexpr auto odd = detail::predicate([](auto const& val) -> bool { | |
return val % decltype(val){2} != decltype(val){0}; | |
}); | |
} // namespace pred | |
namespace cmp { | |
namespace detail { | |
struct compare_floating_point_unchecked_fn { | |
template <std::floating_point T> | |
[[nodiscard]] | |
constexpr auto operator()(T a, T b) const noexcept | |
-> std::weak_ordering | |
{ | |
return a < b ? std::weak_ordering::less | |
: a > b ? std::weak_ordering::greater | |
: std::weak_ordering::equivalent; | |
} | |
}; | |
struct min_fn { | |
template <typename T, typename U, typename Cmp = std::compare_three_way> | |
requires same_decayed<T, U> && | |
std::common_reference_with<T, U> && | |
ordering_invocable<Cmp&, T&, U&, std::weak_ordering> | |
[[nodiscard]] | |
constexpr auto operator()(T&& a, U&& b, Cmp cmp = Cmp{}) const | |
-> std::common_reference_t<T, U> | |
{ | |
return std::invoke(cmp, b, a) < 0 ? FLUX_FWD(b) : FLUX_FWD(a); | |
} | |
}; | |
struct max_fn { | |
template <typename T, typename U, typename Cmp = std::compare_three_way> | |
requires same_decayed<T, U> && | |
std::common_reference_with<T, U> && | |
ordering_invocable<Cmp&, T&, U&, std::weak_ordering> | |
[[nodiscard]] | |
constexpr auto operator()(T&& a, U&& b, Cmp cmp = Cmp{}) const | |
-> std::common_reference_t<T, U> | |
{ | |
return !(std::invoke(cmp, b, a) < 0) ? FLUX_FWD(b) : FLUX_FWD(a); | |
} | |
}; | |
struct partial_min_fn { | |
template <typename T, typename U> | |
requires same_decayed<T, U> && | |
std::common_reference_with<T, U> && | |
ordering_invocable<std::compare_three_way&, T&, U&> | |
[[nodiscard]] | |
constexpr auto operator()(T&& a, U&& b) const | |
-> std::common_reference_t<T, U> | |
{ | |
return (b < a) ? FLUX_FWD(b) : FLUX_FWD(a); | |
} | |
template <typename T, typename U, typename Cmp> | |
requires same_decayed<T, U> && | |
std::common_reference_with<T, U> && | |
ordering_invocable<Cmp&, T&, U&> | |
[[nodiscard]] | |
constexpr auto operator()(T&& a, U&& b, Cmp cmp) const | |
-> std::common_reference_t<T, U> | |
{ | |
return std::invoke(cmp, b, a) < 0 ? FLUX_FWD(b) : FLUX_FWD(a); | |
} | |
}; | |
struct partial_max_fn { | |
template <typename T, typename U> | |
requires same_decayed<T, U> && | |
std::common_reference_with<T, U> && | |
ordering_invocable<std::compare_three_way&, T&, U&> | |
[[nodiscard]] | |
constexpr auto operator()(T&& a, U&& b) const | |
-> std::common_reference_t<T, U> | |
{ | |
return !(b < a) ? FLUX_FWD(b) : FLUX_FWD(a); | |
} | |
template <typename T, typename U, typename Cmp> | |
requires same_decayed<T, U> && | |
std::common_reference_with<T, U> && | |
ordering_invocable<Cmp&, T&, U&> | |
[[nodiscard]] | |
constexpr auto operator()(T&& a, U&& b, Cmp cmp) const | |
-> std::common_reference_t<T, U> | |
{ | |
return !(std::invoke(cmp, b, a) < 0) ? FLUX_FWD(b) : FLUX_FWD(a); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto compare = std::compare_three_way{}; | |
FLUX_EXPORT inline constexpr auto reverse_compare = flip(compare); | |
FLUX_EXPORT inline constexpr auto compare_floating_point_unchecked | |
= detail::compare_floating_point_unchecked_fn{}; | |
FLUX_EXPORT inline constexpr auto min = detail::min_fn{}; | |
FLUX_EXPORT inline constexpr auto max = detail::max_fn{}; | |
FLUX_EXPORT inline constexpr auto partial_min = detail::partial_min_fn{}; | |
FLUX_EXPORT inline constexpr auto partial_max = detail::partial_max_fn{}; | |
} // namespace cmp | |
} // namespace flux | |
#endif | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_INLINE_SEQUENCE_BASE_HPP_INCLUDED | |
#define FLUX_CORE_INLINE_SEQUENCE_BASE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_OPERATION_REQUIREMENTS_HPP_INCLUDED | |
#define FLUX_CORE_OPERATION_REQUIREMENTS_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
template <typename Seq, typename Func, typename Init> | |
using fold_result_t = std::decay_t<std::invoke_result_t<Func&, Init, iterable_element_t<Seq>>>; | |
namespace detail { | |
template <typename Seq, typename Func, typename Init, typename R = fold_result_t<Seq, Func, Init>> | |
concept foldable_ | |
= std::invocable<Func&, R, iterable_element_t<Seq>> && std::convertible_to<Init, R> | |
&& std::assignable_from<R&, std::invoke_result_t<Func&, R, iterable_element_t<Seq>>>; | |
template <typename Func, typename E, int_t N> | |
struct repeated_invocable_helper { | |
template <std::size_t I> | |
using repeater = E; | |
static inline constexpr bool value = []<std::size_t... Is> (std::index_sequence<Is...>) consteval { | |
return std::regular_invocable<Func, repeater<Is>...>; | |
}(std::make_index_sequence<N>{}); | |
}; | |
template <typename Func, typename E, int_t N> | |
concept repeated_invocable = repeated_invocable_helper<Func, E, N>::value; | |
template <typename InnerSeq, typename Pattern> | |
concept flatten_with_compatible | |
= std::common_reference_with<iterable_element_t<InnerSeq>, iterable_element_t<Pattern>> | |
&& std::common_with<iterable_value_t<InnerSeq>, iterable_value_t<Pattern>>; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename It, typename Func, typename Init> | |
concept foldable = iterable<It> && std::invocable<Func&, Init, iterable_element_t<It>> | |
&& detail::foldable_<It, Func, Init>; | |
FLUX_EXPORT | |
template <typename Fn, typename It1, typename It2 = It1> | |
concept weak_ordering_for = iterable<It1> && iterable<It2> | |
&& ordering_invocable<Fn&, iterable_element_t<It1>, iterable_element_t<It2>, std::weak_ordering> | |
&& ordering_invocable<Fn&, iterable_value_t<It1>&, iterable_value_t<It2>&, std::weak_ordering> | |
&& ordering_invocable<Fn&, iterable_value_t<It1>&, iterable_value_t<It2>&, std::weak_ordering> | |
&& ordering_invocable<Fn&, iterable_common_element_t<It1>, iterable_common_element_t<It2>, | |
std::weak_ordering>; | |
} // namespace flux | |
#endif // FLUX_CORE_OPERATION_REQUIREMENTS_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
template <cursor Cur> | |
struct bounds { | |
FLUX_NO_UNIQUE_ADDRESS Cur from; | |
FLUX_NO_UNIQUE_ADDRESS Cur to; | |
friend bool operator==(bounds const&, bounds const&) = default; | |
}; | |
template <cursor Cur> | |
bounds(Cur, Cur) -> bounds<Cur>; | |
FLUX_EXPORT | |
template <sequence Seq> | |
using bounds_t = bounds<cursor_t<Seq>>; | |
template <typename Derived> | |
struct inline_sequence_base { | |
private: | |
constexpr auto derived() -> Derived& { return static_cast<Derived&>(*this); } | |
constexpr auto derived() const -> Derived const& { return static_cast<Derived const&>(*this); } | |
public: | |
/* | |
* Basic iteration functions | |
*/ | |
/// Returns a cursor pointing to the first element of the sequence | |
[[nodiscard]] | |
constexpr auto first() { return flux::first(derived()); } | |
/// Returns true if `cur` points to the end of the sequence | |
/// | |
/// @param cur The cursor to test | |
template <std::same_as<Derived> D = Derived> | |
[[nodiscard]] | |
constexpr bool is_last(cursor_t<D> const& cur) { return flux::is_last(derived(), cur); } | |
/// Increments the given cursor | |
/// | |
/// @param cur the cursor to increment | |
template <std::same_as<Derived> D = Derived> | |
constexpr auto& inc(cursor_t<D>& cur) { return flux::inc(derived(), cur); } | |
/// Returns the element at the given cursor | |
template <std::same_as<Derived> D = Derived> | |
[[nodiscard]] | |
constexpr decltype(auto) read_at(cursor_t<D> const& cur) { return flux::read_at(derived(), cur); } | |
/// Returns an rvalue version of the element at the given cursor | |
template <std::same_as<Derived> D = Derived> | |
[[nodiscard]] | |
constexpr decltype(auto) move_at(cursor_t<D> const& cur) { return flux::move_at(derived(), cur); } | |
/// Returns the element at the given cursor | |
template <std::same_as<Derived> D = Derived> | |
[[nodiscard]] | |
constexpr decltype(auto) operator[](cursor_t<D> const& cur) { return flux::read_at(derived(), cur); } | |
/// Returns an cursor pointing to one past the last element of the sequence | |
[[nodiscard]] | |
constexpr auto last() requires bounded_sequence<Derived> { return flux::last(derived()); } | |
/// Decrements the given cursor | |
template <std::same_as<Derived> D = Derived> | |
requires bidirectional_sequence<Derived> | |
constexpr auto& dec(cursor_t<D>& cur) { return flux::dec(derived(), cur); } | |
/// Increments the given cursor by `offset` places | |
template <std::same_as<Derived> D = Derived> | |
requires random_access_sequence<Derived> | |
constexpr auto& inc(cursor_t<D>& cur, int_t offset) | |
{ | |
return flux::inc(derived(), cur, offset); | |
} | |
/// Returns the number of times `from` must be incremented to reach `to` | |
/// | |
/// For a random-access sequence, returns the result in constant time | |
template <std::same_as<Derived> D = Derived> | |
requires multipass_sequence<Derived> | |
[[nodiscard]] | |
constexpr auto distance(cursor_t<D> const& from, cursor_t<D> const& to) | |
{ | |
return flux::distance(derived(), from, to); | |
} | |
[[nodiscard]] | |
constexpr auto data() requires contiguous_sequence<Derived> | |
{ | |
return flux::data(derived()); | |
} | |
[[nodiscard]] | |
constexpr auto data() const requires contiguous_sequence<Derived const> | |
{ | |
return flux::data(derived()); | |
} | |
/// Returns the number of elements in the sequence | |
[[nodiscard]] | |
constexpr auto size() requires sized_sequence<Derived> { return flux::size(derived()); } | |
[[nodiscard]] | |
constexpr auto size() const requires sized_sequence<Derived const> { return flux::size(derived()); } | |
/// Returns the number of elements in the sequence as a size_t | |
[[nodiscard]] | |
constexpr auto usize() requires sized_sequence<Derived> { return flux::usize(derived()); } | |
[[nodiscard]] | |
constexpr auto usize() const requires sized_sequence<Derived const> { return flux::usize(derived()); } | |
template <typename Pred> | |
requires std::invocable<Pred&, element_t<Derived>> && | |
detail::boolean_testable<std::invoke_result_t<Pred&, element_t<Derived>>> | |
constexpr auto for_each_while(Pred pred) | |
{ | |
return flux::seq_for_each_while(derived(), std::ref(pred)); | |
} | |
/// Returns true if the sequence contains no elements | |
[[nodiscard]] | |
constexpr auto is_empty() | |
requires (multipass_sequence<Derived> || sized_sequence<Derived>) | |
{ | |
return flux::is_empty(derived()); | |
} | |
template <std::same_as<Derived> D = Derived> | |
[[nodiscard]] | |
constexpr auto next(cursor_t<D> cur) { return flux::next(derived(), cur); } | |
template <std::same_as<Derived> D = Derived> | |
requires bidirectional_sequence<Derived> | |
[[nodiscard]] | |
constexpr auto prev(cursor_t<D> cur) { return flux::prev(derived(), cur); } | |
[[nodiscard]] | |
constexpr auto front() requires multipass_sequence<Derived> | |
{ | |
return flux::front(derived()); | |
} | |
[[nodiscard]] | |
constexpr auto front() const requires multipass_sequence<Derived const> | |
{ | |
return flux::front(derived()); | |
} | |
[[nodiscard]] | |
constexpr auto back() | |
requires bidirectional_sequence<Derived> && bounded_sequence<Derived> | |
{ | |
return flux::back(derived()); | |
} | |
[[nodiscard]] | |
constexpr auto back() const | |
requires bidirectional_sequence<Derived const> && bounded_sequence<Derived const> | |
{ | |
return flux::back(derived()); | |
} | |
template <typename Func, typename... Args> | |
requires std::invocable<Func, Derived&, Args...> | |
constexpr auto _(Func&& func, Args&&... args) & -> decltype(auto) | |
{ | |
return std::invoke(FLUX_FWD(func), derived(), FLUX_FWD(args)...); | |
} | |
template <typename Func, typename... Args> | |
requires std::invocable<Func, Derived const&, Args...> | |
constexpr auto _(Func&& func, Args&&... args) const& -> decltype(auto) | |
{ | |
return std::invoke(FLUX_FWD(func), derived(), FLUX_FWD(args)...); | |
} | |
template <typename Func, typename... Args> | |
requires std::invocable<Func, Derived, Args...> | |
constexpr auto _(Func&& func, Args&&... args) && -> decltype(auto) | |
{ | |
return std::invoke(FLUX_FWD(func), std::move(derived()), FLUX_FWD(args)...); | |
} | |
template <typename Func, typename... Args> | |
requires std::invocable<Func, Derived const, Args...> | |
constexpr auto _(Func&& func, Args&&... args) const&& -> decltype(auto) | |
{ | |
return std::invoke(FLUX_FWD(func), std::move(derived()), FLUX_FWD(args)...); | |
} | |
constexpr auto ref() const& requires const_iterable_sequence<Derived>; | |
auto ref() const&& -> void = delete; | |
constexpr auto mut_ref() &; | |
/* | |
* Iterator support | |
*/ | |
constexpr auto begin() & | |
requires sequence<Derived>; | |
constexpr auto begin() const& requires sequence<Derived const>; | |
constexpr auto end() & | |
requires sequence<Derived>; | |
constexpr auto end() const& requires sequence<Derived const>; | |
/* | |
* Adaptors | |
*/ | |
template <int_t N> | |
[[nodiscard]] | |
constexpr auto adjacent() && | |
requires multipass_sequence<Derived>; | |
template <typename Pred> | |
requires multipass_sequence<Derived> && | |
std::predicate<Pred&, element_t<Derived>, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto adjacent_filter(Pred pred) &&; | |
template <int_t N, typename Func> | |
requires multipass_sequence<Derived> | |
[[nodiscard]] | |
constexpr auto adjacent_map(Func func) &&; | |
[[nodiscard]] | |
constexpr auto cache_last() && | |
requires bounded_sequence<Derived> || | |
(multipass_sequence<Derived> && not infinite_sequence<Derived>); | |
[[nodiscard]] | |
constexpr auto chunk(num::integral auto chunk_sz) &&; | |
template <typename Pred> | |
requires multipass_sequence<Derived> && | |
std::predicate<Pred&, element_t<Derived>, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto chunk_by(Pred pred) &&; | |
[[nodiscard]] | |
constexpr auto cursors() && requires multipass_sequence<Derived>; | |
[[nodiscard]] | |
constexpr auto cycle() && | |
requires infinite_sequence<Derived> || multipass_sequence<Derived>; | |
[[nodiscard]] | |
constexpr auto cycle(num::integral auto count) && requires multipass_sequence<Derived>; | |
[[nodiscard]] | |
constexpr auto dedup() && | |
requires multipass_sequence<Derived> && | |
std::equality_comparable<element_t<Derived>>; | |
[[nodiscard]] | |
constexpr auto drop(num::integral auto count) &&; | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto drop_while(Pred pred) &&; | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto filter(Pred pred) &&; | |
template <typename Func> | |
requires std::invocable<Func&, element_t<Derived>> && | |
detail::optional_like<std::invoke_result_t<Func&, element_t<Derived>>> | |
[[nodiscard]] | |
constexpr auto filter_map(Func func) &&; | |
[[nodiscard]] | |
constexpr auto filter_deref() && requires detail::optional_like<value_t<Derived>>; | |
[[nodiscard]] | |
constexpr auto flatten() && requires sequence<element_t<Derived>>; | |
template <adaptable_sequence Pattern> | |
requires sequence<element_t<Derived>> && | |
multipass_sequence<Pattern> && | |
detail::flatten_with_compatible<element_t<Derived>, Pattern> | |
[[nodiscard]] | |
constexpr auto flatten_with(Pattern&& pattern) &&; | |
template <typename Value> | |
requires sequence<element_t<Derived>> && | |
std::constructible_from<value_t<element_t<Derived>>, Value&&> | |
[[nodiscard]] | |
constexpr auto flatten_with(Value value) &&; | |
template <typename Func> | |
requires std::invocable<Func&, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto map(Func func) &&; | |
template <adaptable_sequence Mask> | |
requires detail::boolean_testable<element_t<Mask>> | |
[[nodiscard]] | |
constexpr auto mask(Mask&& mask_) &&; | |
[[nodiscard]] | |
constexpr auto pairwise() && requires multipass_sequence<Derived>; | |
template <typename Func> | |
requires multipass_sequence<Derived> | |
[[nodiscard]] | |
constexpr auto pairwise_map(Func func) &&; | |
template <typename Func, typename Init> | |
requires foldable<Derived, Func, Init> | |
[[nodiscard]] | |
constexpr auto prescan(Func func, Init init) &&; | |
[[nodiscard]] | |
constexpr auto read_only() &&; | |
[[nodiscard]] | |
constexpr auto reverse() && | |
requires bidirectional_sequence<Derived> && bounded_sequence<Derived>; | |
template <typename D = Derived, typename Func, typename Init = value_t<D>> | |
requires foldable<Derived, Func, Init> | |
[[nodiscard]] | |
constexpr auto scan(Func func, Init init = Init{}) &&; | |
template <typename Func> | |
requires foldable<Derived, Func, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto scan_first(Func func) &&; | |
[[nodiscard]] | |
constexpr auto slide(num::integral auto win_sz) && requires multipass_sequence<Derived>; | |
template <typename Pattern> | |
requires multipass_sequence<Derived> && | |
multipass_sequence<Pattern> && | |
std::equality_comparable_with<element_t<Derived>, element_t<Pattern>> | |
[[nodiscard]] | |
constexpr auto split(Pattern&& pattern) &&; | |
template <typename Delim> | |
requires multipass_sequence<Derived> && | |
std::equality_comparable_with<element_t<Derived>, Delim const&> | |
[[nodiscard]] | |
constexpr auto split(Delim&& delim) &&; | |
template <typename Pred> | |
requires multipass_sequence<Derived> && | |
std::predicate<Pred const&, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto split(Pred pred) &&; | |
template <typename Pattern> | |
[[nodiscard]] | |
constexpr auto split_string(Pattern&& pattern) &&; | |
[[nodiscard]] | |
constexpr auto stride(num::integral auto by) &&; | |
[[nodiscard]] | |
constexpr auto take(num::integral auto count) &&; | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto take_while(Pred pred) &&; | |
/* | |
* Algorithms | |
*/ | |
/// Returns `true` if every element of the sequence satisfies the predicate | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto all(Pred pred); | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto any(Pred pred); | |
template <typename Value> | |
requires std::equality_comparable_with<element_t<Derived>, Value const&> | |
constexpr auto contains(Value const& value) -> bool; | |
/// Returns the number of elements in the sequence | |
constexpr auto count(); | |
/// Returns the number of elements in the sequence which are equal to `value` | |
template <typename Value> | |
requires std::equality_comparable_with<element_t<Derived>, Value const&> | |
constexpr auto count_eq(Value const& value); | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<Derived>> | |
constexpr auto count_if(Pred pred); | |
template <sequence Needle, typename Cmp = std::ranges::equal_to> | |
requires std::predicate<Cmp&, element_t<Derived>, element_t<Needle>> && | |
(multipass_sequence<Derived> || sized_sequence<Derived>) && | |
(multipass_sequence<Needle> || sized_sequence<Needle>) | |
constexpr auto ends_with(Needle&& needle, Cmp cmp = {}) -> bool; | |
template <typename Value> | |
requires writable_sequence_of<Derived, Value const&> | |
constexpr auto fill(Value const& value) -> void; | |
/// Returns a cursor pointing to the first occurrence of `value` in the sequence | |
template <typename Value> | |
requires std::equality_comparable_with<element_t<Derived>, Value const&> | |
[[nodiscard]] | |
constexpr auto find(Value const&); | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto find_if(Pred pred); | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto find_if_not(Pred pred); | |
template <typename Cmp = std::compare_three_way> | |
requires weak_ordering_for<Cmp, Derived> | |
[[nodiscard]] | |
constexpr auto find_max(Cmp cmp = Cmp{}); | |
template <typename Cmp = std::compare_three_way> | |
requires weak_ordering_for<Cmp, Derived> | |
[[nodiscard]] | |
constexpr auto find_min(Cmp cmp = Cmp{}); | |
template <typename Cmp = std::compare_three_way> | |
requires weak_ordering_for<Cmp, Derived> | |
[[nodiscard]] | |
constexpr auto find_minmax(Cmp cmp = Cmp{}); | |
template <typename D = Derived, typename Func, typename Init> | |
requires foldable<Derived, Func, Init> | |
[[nodiscard]] | |
constexpr auto fold(Func func, Init init) -> fold_result_t<D, Func, Init>; | |
template <typename D = Derived, typename Func> | |
requires std::invocable<Func&, value_t<D>, element_t<D>> && | |
std::assignable_from<value_t<D>&, std::invoke_result_t<Func&, value_t<D>, element_t<D>>> | |
[[nodiscard]] | |
constexpr auto fold_first(Func func); | |
template <typename Func> | |
requires std::invocable<Func&, element_t<Derived>> | |
constexpr auto for_each(Func func) -> Func; | |
constexpr auto inplace_reverse() | |
requires bounded_sequence<Derived> && | |
detail::element_swappable_with<Derived, Derived>; | |
template <typename Cmp = std::compare_three_way> | |
requires weak_ordering_for<Cmp, Derived> | |
constexpr auto max(Cmp cmp = Cmp{}); | |
template <typename Cmp = std::compare_three_way> | |
requires weak_ordering_for<Cmp, Derived> | |
constexpr auto min(Cmp cmp = Cmp{}); | |
template <typename Cmp = std::compare_three_way> | |
requires weak_ordering_for<Cmp, Derived> | |
constexpr auto minmax(Cmp cmp = Cmp{}); | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<Derived>> | |
[[nodiscard]] | |
constexpr auto none(Pred pred); | |
template <typename Iter> | |
requires std::weakly_incrementable<Iter> && | |
std::indirectly_writable<Iter, element_t<Derived>> | |
constexpr auto output_to(Iter iter) -> Iter; | |
constexpr auto sum() | |
requires foldable<Derived, std::plus<>, value_t<Derived>> && | |
std::default_initializable<value_t<Derived>>; | |
template <typename Cmp = std::compare_three_way> | |
requires random_access_sequence<Derived> && | |
bounded_sequence<Derived> && | |
detail::element_swappable_with<Derived, Derived> && | |
weak_ordering_for<Cmp, Derived> | |
constexpr void sort(Cmp cmp = {}); | |
constexpr auto product() | |
requires foldable<Derived, std::multiplies<>, value_t<Derived>> && | |
requires { value_t<Derived>(1); }; | |
template <sequence Needle, typename Cmp = std::ranges::equal_to> | |
requires std::predicate<Cmp&, element_t<Derived>, element_t<Needle>> | |
constexpr auto starts_with(Needle&& needle, Cmp cmp = Cmp{}) -> bool; | |
template <typename Container, typename... Args> | |
constexpr auto to(Args&&... args) -> Container; | |
template <template <typename...> typename Container, typename... Args> | |
constexpr auto to(Args&&... args); | |
auto write_to(std::ostream& os) -> std::ostream&; | |
}; | |
} // namespace flux | |
#endif // FLUX_CORE_SEQUENCE_IFACE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_REF_HPP_INCLUDED | |
#define FLUX_CORE_REF_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
struct passthrough_traits_base : default_sequence_traits { | |
static constexpr auto first(auto& self) | |
-> decltype(flux::first(self.base())) | |
{ | |
return flux::first(self.base()); | |
} | |
template <typename Self> | |
static constexpr auto is_last(Self& self, auto const& cur) | |
-> decltype(flux::is_last(self.base(), cur)) | |
{ | |
return flux::is_last(self.base(), cur); | |
} | |
template <typename Self> | |
static constexpr auto read_at(Self& self, auto const& cur) | |
-> decltype(flux::read_at(self.base(), cur)) | |
{ | |
return flux::read_at(self.base(), cur); | |
} | |
template <typename Self> | |
static constexpr auto inc(Self& self, auto& cur) | |
-> decltype(flux::inc(self.base(), cur)) | |
{ | |
return flux::inc(self.base(), cur); | |
} | |
template <typename Self> | |
static constexpr auto dec(Self& self, auto& cur) | |
-> decltype(flux::dec(self.base(), cur)) | |
{ | |
return flux::dec(self.base(), cur); | |
} | |
template <typename Self> | |
static constexpr auto inc(Self& self, auto& cur, int_t dist) | |
-> decltype(flux::inc(self.base(), cur, dist)) | |
{ | |
return flux::inc(self.base(), cur, dist); | |
} | |
template <typename Self> | |
static constexpr auto distance(Self& self, auto const& from, auto const& to) | |
-> decltype(flux::distance(self.base(), from, to)) | |
requires random_access_sequence<decltype(self.base())> | |
{ | |
return flux::distance(self.base(), from, to); | |
} | |
static constexpr auto data(auto& self) | |
-> decltype(flux::data(self.base())) | |
{ | |
return flux::data(self.base()); | |
} | |
template <typename Self> | |
static constexpr auto size(Self& self) -> decltype(flux::size(self.base())) | |
{ | |
return flux::size(self.base()); | |
} | |
static constexpr auto last(auto& self) -> decltype(flux::last(self.base())) | |
{ | |
return flux::last(self.base()); | |
} | |
template <typename Self> | |
static constexpr auto move_at(Self& self, auto const& cur) | |
-> decltype(flux::move_at(self.base(), cur)) | |
{ | |
return flux::move_at(self.base(), cur); | |
} | |
template <typename Self> | |
static constexpr auto read_at_unchecked(Self& self, auto const& cur) | |
-> decltype(flux::read_at_unchecked(self.base(), cur)) | |
{ | |
return flux::read_at_unchecked(self.base(), cur); | |
} | |
template <typename Self> | |
static constexpr auto move_at_unchecked(Self& self, auto const& cur) | |
-> decltype(flux::move_at_unchecked(self.base(), cur)) | |
{ | |
return flux::move_at_unchecked(self.base(), cur); | |
} | |
template <typename Self> | |
static constexpr auto for_each_while(Self& self, auto&& pred) | |
-> decltype(flux::seq_for_each_while(self.base(), FLUX_FWD(pred))) | |
{ | |
return flux::seq_for_each_while(self.base(), FLUX_FWD(pred)); | |
} | |
}; | |
template <sequence Base> | |
struct ref_adaptor : inline_sequence_base<ref_adaptor<Base>> { | |
private: | |
Base* base_; | |
static void test_func(Base&) noexcept; | |
static void test_func(Base&&) = delete; | |
public: | |
// This seems thoroughly overcomplicated, but it's the formulation | |
// std::reference_wrapper and ranges::ref_view use to avoid binding rvalues | |
// when Base is a const type, while also avoiding implicit conversions | |
template <typename Seq> | |
requires (!std::same_as<std::decay_t<Seq>, ref_adaptor> && | |
std::convertible_to<Seq, Base&> && | |
requires { test_func(std::declval<Seq>()); }) | |
constexpr ref_adaptor(Seq&& seq) | |
noexcept(noexcept(test_func(std::declval<Seq>()))) | |
: base_(std::addressof(static_cast<Base&>(FLUX_FWD(seq)))) | |
{} | |
// We are always movable | |
ref_adaptor(ref_adaptor&&) = default; | |
ref_adaptor& operator=(ref_adaptor&&) = default; | |
~ref_adaptor() = default; | |
// ...but only copyable when `Base` is const | |
ref_adaptor(ref_adaptor const&) requires std::is_const_v<Base> = default; | |
ref_adaptor& operator=(ref_adaptor const&) requires std::is_const_v<Base> = default; | |
constexpr Base& base() const noexcept { return *base_; } | |
constexpr auto iterate() const { return flux::iterate(*base_); } | |
constexpr auto reverse_iterate() const | |
requires reverse_iterable<Base> | |
{ | |
return flux::reverse_iterate(*base_); | |
} | |
struct flux_sequence_traits : passthrough_traits_base { | |
using value_type = value_t<Base>; | |
}; | |
}; | |
template <typename> | |
inline constexpr bool is_ref_adaptor = false; | |
template <typename T> | |
inline constexpr bool is_ref_adaptor<ref_adaptor<T>> = true; | |
struct mut_ref_fn { | |
template <sequence Seq> | |
requires (!std::is_const_v<Seq>) | |
[[nodiscard]] | |
constexpr auto operator()(Seq& seq) const | |
{ | |
if constexpr (is_ref_adaptor<Seq>) { | |
return seq; | |
} else { | |
return ref_adaptor<Seq>(seq); | |
} | |
} | |
}; | |
struct ref_fn { | |
template <const_iterable_sequence Seq> | |
requires (!is_ref_adaptor<Seq>) | |
[[nodiscard]] | |
constexpr auto operator()(Seq const& seq) const | |
{ | |
return ref_adaptor<Seq const>(seq); | |
} | |
template <const_iterable_sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(ref_adaptor<Seq> ref) const | |
{ | |
return ref_adaptor<Seq const>(ref.base()); | |
} | |
template <typename T> | |
auto operator()(T const&&) const -> void = delete; | |
}; | |
template <sequence Base> | |
requires std::movable<Base> | |
struct owning_adaptor : inline_sequence_base<owning_adaptor<Base>> { | |
private: | |
Base base_; | |
public: | |
constexpr explicit owning_adaptor(decays_to<Base> auto&& base) | |
: base_(FLUX_FWD(base)) | |
{} | |
constexpr Base& base() & noexcept { return base_; } | |
constexpr Base const& base() const& noexcept { return base_; } | |
constexpr Base&& base() && noexcept { return std::move(base_); } | |
constexpr Base const&& base() const&& noexcept { return std::move(base_); } | |
struct flux_sequence_traits : passthrough_traits_base { | |
using value_type = value_t<Base>; | |
}; | |
}; | |
struct from_fn { | |
template <adaptable_sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const | |
{ | |
if constexpr (derived_from_inline_sequence_base<Seq>) { | |
return FLUX_FWD(seq); | |
} else { | |
return owning_adaptor<std::decay_t<Seq>>(FLUX_FWD(seq)); | |
} | |
} | |
}; | |
struct from_fwd_ref_fn { | |
template <sequence Seq> | |
requires adaptable_sequence<Seq> || std::is_lvalue_reference_v<Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const | |
{ | |
if constexpr (std::is_lvalue_reference_v<Seq>) { | |
if constexpr (std::is_const_v<std::remove_reference_t<Seq>>) { | |
return ref_fn{}(seq); | |
} else { | |
return mut_ref_fn{}(seq); | |
} | |
} else { | |
return from_fn{}(seq); | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto mut_ref = detail::mut_ref_fn{}; | |
FLUX_EXPORT inline constexpr auto ref = detail::ref_fn{}; | |
FLUX_EXPORT inline constexpr auto from = detail::from_fn{}; | |
FLUX_EXPORT inline constexpr auto from_fwd_ref = detail::from_fwd_ref_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::ref() const& | |
requires const_iterable_sequence<D> | |
{ | |
return flux::ref(derived()); | |
} | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::mut_ref() & | |
{ | |
return flux::mut_ref(derived()); | |
} | |
} // namespace flux | |
#endif // FLUX_CORE_REF_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_CORE_SEQUENCE_ITERATOR_HPP_INCLUDED | |
#define FLUX_CORE_SEQUENCE_ITERATOR_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <sequence Base> | |
consteval auto get_iterator_tag() | |
{ | |
if constexpr (contiguous_sequence<Base>) { | |
return std::contiguous_iterator_tag{}; | |
} else if constexpr (random_access_sequence<Base>) { | |
return std::random_access_iterator_tag{}; | |
} else if constexpr (bidirectional_sequence<Base>) { | |
return std::bidirectional_iterator_tag{}; | |
} else if constexpr (multipass_sequence<Base>) { | |
return std::forward_iterator_tag{}; | |
} else { | |
return std::input_iterator_tag{}; | |
} | |
} | |
template <sequence S> | |
struct sequence_iterator { | |
private: | |
S* seq_ = nullptr; | |
cursor_t<S> cur_{}; | |
template <sequence SS> | |
friend struct sequence_iterator; | |
public: | |
using value_type = value_t<S>; | |
using difference_type = int_t; | |
using element_type = value_t<S>; // Yes, really | |
using iterator_concept = decltype(get_iterator_tag<S>()); | |
sequence_iterator() requires std::default_initializable<cursor_t<S>> = default; | |
constexpr sequence_iterator(S& base, cursor_t<S> cur) | |
: seq_(std::addressof(base)), | |
cur_(std::move(cur)) | |
{} | |
template <typename SS = S> | |
requires std::is_const_v<SS> | |
constexpr sequence_iterator(sequence_iterator<std::remove_const_t<SS>> other) | |
: seq_(other.seq_), | |
cur_(std::move(other.cur_)) | |
{} | |
constexpr auto operator*() const -> element_t<S> | |
{ | |
return flux::read_at(*seq_, cur_); | |
} | |
constexpr auto operator++() -> sequence_iterator& | |
{ | |
flux::inc(*seq_, cur_); | |
return *this; | |
} | |
constexpr void operator++(int) { flux::inc(*seq_, cur_); } | |
constexpr auto operator++(int) -> sequence_iterator | |
requires multipass_sequence<S> | |
{ | |
auto temp = *this; | |
++*this; | |
return temp; | |
} | |
constexpr auto operator--() -> sequence_iterator& | |
requires bidirectional_sequence<S> | |
{ | |
flux::dec(*seq_, cur_); | |
return *this; | |
} | |
constexpr auto operator--(int) -> sequence_iterator | |
requires bidirectional_sequence<S> | |
{ | |
auto temp = *this; | |
--*this; | |
return temp; | |
} | |
constexpr auto operator+=(difference_type n) -> sequence_iterator& | |
requires random_access_sequence<S> | |
{ | |
flux::inc(*seq_, cur_, n); | |
return *this; | |
} | |
constexpr auto operator-=(difference_type n) -> sequence_iterator& | |
requires random_access_sequence<S> | |
{ | |
flux::inc(*seq_, cur_, num::neg(n)); | |
return *this; | |
} | |
constexpr auto operator[](difference_type n) const -> element_t<S> | |
requires random_access_sequence<S> | |
{ | |
auto i = flux::first(*seq_); | |
flux::inc(*seq_, i, n); | |
return flux::read_at(*seq_, i); | |
} | |
constexpr auto operator->() const -> std::add_pointer_t<element_t<S>> | |
requires contiguous_sequence<S> | |
{ | |
return flux::data(*seq_) + flux::distance(*seq_, flux::first(*seq_), cur_); | |
} | |
friend constexpr bool operator==(sequence_iterator const& self, std::default_sentinel_t) | |
{ | |
return flux::is_last(*self.seq_, self.cur_); | |
} | |
friend bool operator==(sequence_iterator const&, sequence_iterator const&) | |
requires multipass_sequence<S> | |
= default; | |
friend std::strong_ordering operator<=>(sequence_iterator const&, sequence_iterator const&) | |
requires random_access_sequence<S> | |
= default; | |
friend constexpr auto operator+(sequence_iterator self, difference_type n) | |
-> sequence_iterator | |
requires random_access_sequence<S> | |
{ | |
flux::inc(*self.seq_, self.cur_, n); | |
return self; | |
} | |
friend constexpr auto operator+(difference_type n, sequence_iterator self) | |
-> sequence_iterator | |
requires random_access_sequence<S> | |
{ | |
flux::inc(*self.seq_, self.cur_, n); | |
return self; | |
} | |
friend constexpr auto operator-(sequence_iterator self, difference_type n) | |
-> sequence_iterator | |
requires random_access_sequence<S> | |
{ | |
flux::inc(*self.seq_, self.cur_, num::neg(n)); | |
return self; | |
} | |
friend constexpr auto operator-(sequence_iterator const& lhs, sequence_iterator const& rhs) | |
-> difference_type | |
requires random_access_sequence<S> | |
{ | |
FLUX_ASSERT(lhs.seq_ == rhs.seq_); | |
return flux::distance(*lhs.seq_, rhs.cur_, lhs.cur_); | |
} | |
friend constexpr auto iter_move(sequence_iterator const& self) | |
-> rvalue_element_t<S> | |
{ | |
return flux::move_at(*self.seq_, self.cur_); | |
} | |
friend constexpr void iter_swap(sequence_iterator const& lhs, sequence_iterator const& rhs) | |
requires element_swappable_with<S, S> | |
{ | |
flux::swap_with(*lhs.seq_, lhs.cur_, *rhs.seq_, rhs.cur_); | |
} | |
}; | |
struct begin_fn { | |
template <sequence S> | |
constexpr auto operator()(S& seq) const | |
{ | |
return sequence_iterator<S>(seq, flux::first(seq)); | |
} | |
}; | |
struct end_fn { | |
template <sequence S> | |
constexpr auto operator()(S& seq) const | |
{ | |
// Ranges requires sentinels to be copy-constructible | |
if constexpr (bounded_sequence<S> && std::copy_constructible<cursor_t<S>>) { | |
return sequence_iterator(seq, flux::last(seq)); | |
} else { | |
return std::default_sentinel; | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto begin = detail::begin_fn{}; | |
FLUX_EXPORT inline constexpr auto end = detail::end_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::begin() & | |
requires sequence<D> | |
{ | |
return flux::begin(derived()); | |
} | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::begin() const& | |
requires sequence<D const> | |
{ | |
return flux::begin(derived()); | |
}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::end() & | |
requires sequence<D> | |
{ | |
return flux::end(derived()); | |
} | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::end() const& | |
requires sequence<D const> | |
{ | |
return flux::end(derived()); | |
}; | |
} // namespace flux | |
// Every sequence is a range: furthermore, it is a view if it is either | |
// trivially copyable, or not copyable at all | |
// See P2415 for the logic behind this | |
template <flux::detail::derived_from_inline_sequence_base Seq> | |
inline constexpr bool std::ranges::enable_view<Seq> = | |
std::is_trivially_copyable_v<Seq> || !std::copyable<Seq>; | |
#endif // FLUX_CORE_SEQUENCE_ITERATOR_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_OP_SLICE_HPP_INCLUDED | |
#define FLUX_OP_SLICE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <cursor Cur, bool Bounded> | |
struct slice_data { | |
Cur first; | |
Cur last; | |
}; | |
template <cursor Cur> | |
struct slice_data<Cur, false> { | |
Cur first; | |
}; | |
template <sequence Base, bool Bounded> | |
requires (!Bounded || regular_cursor<cursor_t<Base>>) | |
struct subsequence : inline_sequence_base<subsequence<Base, Bounded>> | |
{ | |
private: | |
Base* base_; | |
FLUX_NO_UNIQUE_ADDRESS slice_data<cursor_t<Base>, Bounded> data_; | |
friend struct sequence_traits<subsequence>; | |
public: | |
constexpr subsequence(Base& base, cursor_t<Base>&& from, | |
cursor_t<Base>&& to) | |
requires Bounded | |
: base_(std::addressof(base)), | |
data_{std::move(from), std::move(to)} | |
{} | |
constexpr subsequence(Base& base, cursor_t<Base>&& from) | |
requires (!Bounded) | |
: base_(std::addressof(base)), | |
data_{std::move(from)} | |
{} | |
constexpr auto base() const -> Base& { return *base_; }; | |
}; | |
template <sequence Seq> | |
subsequence(Seq&, cursor_t<Seq>, cursor_t<Seq>) -> subsequence<Seq, true>; | |
template <sequence Seq> | |
subsequence(Seq&, cursor_t<Seq>) -> subsequence<Seq, false>; | |
template <typename Seq> | |
concept has_overloaded_slice = | |
requires (Seq& seq, cursor_t<Seq> cur) { | |
{ traits_t<Seq>::slice(seq, std::move(cur)) } -> sequence; | |
{ traits_t<Seq>::slice(seq, std::move(cur), std::move(cur)) } -> sequence; | |
}; | |
struct slice_fn { | |
template <sequence Seq> | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> from, | |
cursor_t<Seq> to) const | |
-> sequence auto | |
{ | |
if constexpr (has_overloaded_slice<Seq>) { | |
return traits_t<Seq>::slice(seq, std::move(from), std::move(to)); | |
} else { | |
return subsequence(seq, std::move(from), std::move(to)); | |
} | |
} | |
template <sequence Seq> | |
constexpr auto operator()(Seq& seq, cursor_t<Seq> from, last_fn) const | |
-> sequence auto | |
{ | |
if constexpr (has_overloaded_slice<Seq>) { | |
return traits_t<Seq>::slice(seq, std::move(from)); | |
} else { | |
return subsequence(seq, std::move(from)); | |
} | |
} | |
}; | |
} // namespace detail | |
using detail::subsequence; | |
template <typename Base, bool Bounded> | |
struct sequence_traits<subsequence<Base, Bounded>> | |
: detail::passthrough_traits_base | |
{ | |
using value_type = value_t<Base>; | |
using self_t = subsequence<Base, Bounded>; | |
static constexpr auto first(self_t& self) -> cursor_t<Base> | |
{ | |
if constexpr (std::copy_constructible<decltype(self.data_.first)>) { | |
return self.data_.first; | |
} else { | |
return std::move(self.data_.first); | |
} | |
} | |
static constexpr bool is_last(self_t& self, cursor_t<Base> const& cur) { | |
if constexpr (Bounded) { | |
return cur == self.data_.last; | |
} else { | |
return flux::is_last(*self.base_, cur); | |
} | |
} | |
static constexpr auto last(self_t& self) -> cursor_t<Base> | |
requires (Bounded || bounded_sequence<Base>) | |
{ | |
if constexpr (Bounded) { | |
return self.data_.last; | |
} else { | |
return flux::last(*self.base_); | |
} | |
} | |
static constexpr auto data(self_t& self) | |
requires contiguous_sequence<Base> | |
{ | |
return flux::data(*self.base_) + | |
flux::distance(*self.base_, flux::first(*self.base_), self.data_.first); | |
} | |
using default_sequence_traits::size; | |
using default_sequence_traits::for_each_while; | |
}; | |
FLUX_EXPORT inline constexpr auto slice = detail::slice_fn{}; | |
#if 0 | |
template <typename Derived> | |
template <std::same_as<Derived> D> | |
constexpr auto inline_sequence_base<Derived>::slice(cursor_t<D> from, cursor_t<D> to) & | |
{ | |
return flux::slice(derived(), std::move(from), std::move(to)); | |
} | |
template <typename Derived> | |
template <std::same_as<Derived> D> | |
constexpr auto inline_sequence_base<Derived>::slice_from(cursor_t<D> from) & | |
{ | |
return flux::slice(derived(), std::move(from)); | |
} | |
#endif | |
} // namespace flux | |
#endif // namespace FLUX_OP_SLICE_HPP_INCLUDED | |
#endif // FLUX_CORE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_REVERSE_HPP_INCLUDED | |
#define FLUX_ADAPTOR_REVERSE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <reverse_iterable Base> | |
struct reverse_adaptor : inline_sequence_base<reverse_adaptor<Base>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
public: | |
constexpr explicit reverse_adaptor(decays_to<Base> auto&& base) | |
: base_(FLUX_FWD(base)) | |
{} | |
[[nodiscard]] constexpr auto base() const& -> Base const& { return base_; } | |
[[nodiscard]] constexpr auto base() && -> Base&& { return std::move(base_); } | |
struct flux_iterable_traits { | |
static constexpr auto iterate(auto& self) | |
requires reverse_iterable<decltype((self.base_))> | |
{ | |
return flux::reverse_iterate(self.base_); | |
} | |
static constexpr auto reverse_iterate(auto& self) | |
requires iterable<decltype((self.base_))> | |
{ | |
return flux::iterate(self.base_); | |
} | |
static constexpr auto size(auto& self) | |
requires sized_iterable<decltype((self.base_))> | |
{ | |
return flux::iterable_size(self.base_); | |
} | |
}; | |
struct flux_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> base_cur; | |
friend bool operator==(cursor_type const&, cursor_type const&) | |
requires std::equality_comparable<cursor_t<Base>> | |
= default; | |
friend auto operator<=>(cursor_type const& lhs, cursor_type const& rhs) | |
-> std::strong_ordering | |
requires std::three_way_comparable<cursor_t<Base>, std::strong_ordering> | |
{ | |
return rhs <=> lhs; | |
} | |
}; | |
public: | |
using value_type = value_t<Base>; | |
static constexpr auto first(auto& self) -> cursor_type | |
{ | |
return cursor_type(flux::last(self.base_)); | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
{ | |
return cursor_type(flux::first(self.base_)); | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) -> bool | |
{ | |
return cur.base_cur == flux::first(self.base_); | |
} | |
template <typename Self> | |
static constexpr auto read_at(Self& self, cursor_type const& cur) | |
-> element_t<decltype((self.base_))> | |
{ | |
return flux::read_at(self.base_, flux::prev(self.base_, cur.base_cur)); | |
} | |
template <typename Self> | |
static constexpr auto read_at_unchecked(Self& self, cursor_type const& cur) | |
-> element_t<decltype((self.base_))> | |
{ | |
return flux::read_at_unchecked(self.base_, flux::prev(self.base_, cur.base_cur)); | |
} | |
template <typename Self> | |
static constexpr auto move_at(Self& self, cursor_type const& cur) | |
-> rvalue_element_t<decltype((self.base_))> | |
{ | |
return flux::move_at(self.base_, flux::prev(self.base_, cur.base_cur)); | |
} | |
template <typename Self> | |
static constexpr auto move_at_unchecked(Self& self, cursor_type const& cur) | |
-> rvalue_element_t<decltype((self.base_))> | |
{ | |
return flux::move_at_unchecked(self.base_, flux::prev(self.base_, cur.base_cur)); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) -> void | |
{ | |
flux::dec(self.base_, cur.base_cur); | |
} | |
static constexpr auto dec(auto& self, cursor_type& cur) -> void | |
{ | |
flux::inc(self.base_, cur.base_cur); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur, int_t dist) -> void | |
{ | |
flux::inc(self.base_, cur.base_cur, num::neg(dist)); | |
} | |
static constexpr auto distance(auto& self, cursor_type const& from, cursor_type const& to) | |
-> int_t | |
{ | |
return flux::distance(self.base_, to.base_cur, from.base_cur); | |
} | |
static constexpr auto size(auto& self) -> int_t | |
requires sized_sequence<decltype((self.base_))> | |
{ | |
return flux::size(self.base_); | |
} | |
static constexpr auto for_each_while(auto& self, auto&& pred) -> cursor_type | |
requires bidirectional_sequence<decltype((self.base_))> && | |
bounded_sequence<decltype((self.base_))> | |
{ | |
auto cur = flux::last(self.base_); | |
const auto end = flux::first(self.base_); | |
while (cur != end) { | |
flux::dec(self.base_, cur); | |
if (!std::invoke(pred, flux::read_at(self.base_, cur))) { | |
flux::inc(self.base_, cur); | |
break; | |
} | |
} | |
return cursor_type(cur); | |
} | |
}; | |
}; | |
template <typename> | |
inline constexpr bool is_reverse_adaptor = false; | |
template <typename Base> | |
inline constexpr bool is_reverse_adaptor<reverse_adaptor<Base>> = true; | |
struct reverse_fn { | |
template <adaptable_iterable It> | |
requires reverse_iterable<It> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it) const -> reverse_iterable auto | |
{ | |
if constexpr (is_reverse_adaptor<std::decay_t<It>>) { | |
return FLUX_FWD(it).base(); | |
} else { | |
return reverse_adaptor<std::decay_t<It>>(FLUX_FWD(it)); | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto reverse = detail::reverse_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::reverse() && | |
requires bidirectional_sequence<D> && bounded_sequence<D> | |
{ | |
return flux::reverse(std::move(derived())); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_REVERSE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_ZIP_HPP_INCLUDED | |
#define FLUX_ADAPTOR_ZIP_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_EMPTY_HPP_INCLUDED | |
#define FLUX_SEQUENCE_EMPTY_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename T> | |
struct empty_sequence : inline_sequence_base<empty_sequence<T>> { | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
friend auto operator==(cursor_type, cursor_type) -> bool = default; | |
friend auto operator<=>(cursor_type, cursor_type) = default; | |
}; | |
public: | |
static constexpr auto first(empty_sequence) -> cursor_type { return {}; } | |
static constexpr auto last(empty_sequence) -> cursor_type { return {}; } | |
static constexpr auto is_last(empty_sequence, cursor_type) -> bool { return true; } | |
static constexpr auto inc(empty_sequence, cursor_type& cur, int_t = 0) -> cursor_type& | |
{ | |
return cur; | |
} | |
static constexpr auto dec(empty_sequence, cursor_type& cur) -> cursor_type& | |
{ | |
return cur; | |
} | |
static constexpr auto distance(empty_sequence, cursor_type, cursor_type) | |
-> std::ptrdiff_t | |
{ | |
return 0; | |
} | |
static constexpr auto size(empty_sequence) -> std::ptrdiff_t { return 0; } | |
static constexpr auto data(empty_sequence) -> std::add_pointer_t<T> requires std::is_object_v<T> { return nullptr; } | |
[[noreturn]] | |
static constexpr auto read_at(empty_sequence, cursor_type) -> T& | |
{ | |
runtime_error("Attempted read of flux::empty"); | |
} | |
}; | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename T> | |
inline constexpr auto empty = detail::empty_sequence<T>{}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_EMPTY_HPP_INCLUDED | |
#include <algorithm> // for std::min({ilist...}) | |
namespace flux { | |
namespace detail { | |
template <typename... Ts> | |
struct pair_or_tuple { | |
using type = std::tuple<Ts...>; | |
}; | |
template <typename T, typename U> | |
struct pair_or_tuple<T, U> { | |
using type = std::pair<T, U>; | |
}; | |
template <typename... Ts> | |
using pair_or_tuple_t = typename pair_or_tuple<Ts...>::type; | |
} | |
template <typename... Bases> | |
struct zip_traits_base : default_sequence_traits { | |
private: | |
template <typename From, typename To> | |
using const_like_t = std::conditional_t<std::is_const_v<From>, To const, To>; | |
protected: | |
template <typename... Ts> | |
using tuple_t = detail::pair_or_tuple_t<Ts...>; | |
template <std::size_t I> | |
static constexpr decltype(auto) read1_(auto fn, auto& self, auto const& cur) | |
{ | |
return fn(std::get<I>(self.bases_), std::get<I>(cur)); | |
} | |
public: | |
static constexpr bool is_infinite = (infinite_sequence<Bases> && ...); | |
template <typename Self> | |
requires (sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto first(Self& self) | |
{ | |
return std::apply([](auto&&... args) { | |
return tuple_t<decltype(flux::first(FLUX_FWD(args)))...>(flux::first(FLUX_FWD(args))...); | |
}, self.bases_); | |
} | |
template <typename Self> | |
requires (sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr bool is_last(Self& self, cursor_t<Self> const& cur) | |
{ | |
return [&self, &cur]<std::size_t... I>(std::index_sequence<I...>) { | |
return (flux::is_last(std::get<I>(self.bases_), std::get<I>(cur)) || ...); | |
}(std::index_sequence_for<Bases...>{}); | |
} | |
template <typename Self> | |
requires (sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto& inc(Self& self, cursor_t<Self>& cur) | |
{ | |
[&]<std::size_t... I>(std::index_sequence<I...>) { | |
(flux::inc(std::get<I>(self.bases_), std::get<I>(cur)), ...); | |
}(std::index_sequence_for<Bases...>{}); | |
return cur; | |
} | |
template <typename Self> | |
requires (bidirectional_sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto& dec(Self& self, cursor_t<Self>& cur) | |
{ | |
[&]<std::size_t... I>(std::index_sequence<I...>) { | |
(flux::dec(std::get<I>(self.bases_), std::get<I>(cur)), ...); | |
}(std::index_sequence_for<Bases...>{}); | |
return cur; | |
} | |
template <typename Self> | |
requires(random_access_sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto& inc(Self& self, cursor_t<Self>& cur, int_t offset) | |
{ | |
[&]<std::size_t... I>(std::index_sequence<I...>) { | |
(flux::inc(std::get<I>(self.bases_), std::get<I>(cur), offset), ...); | |
}(std::index_sequence_for<Bases...>{}); | |
return cur; | |
} | |
template <typename Self> | |
requires (random_access_sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto distance(Self& self, cursor_t<Self> const& from, | |
cursor_t<Self> const& to) | |
{ | |
return [&]<std::size_t... I>(std::index_sequence<I...>) { | |
return std::min({flux::distance(std::get<I>(self.bases_), std::get<I>(from), std::get<I>(to))...}); | |
}(std::index_sequence_for<Bases...>{}); | |
} | |
template <typename Self> | |
requires (random_access_sequence<const_like_t<Self, Bases>> && ...) | |
&& (sized_sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto last(Self& self) | |
{ | |
auto cur = first(self); | |
return inc(self, cur, size(self)); | |
} | |
template <typename Self> | |
requires (sized_sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto size(Self& self) | |
{ | |
return std::apply([&](auto&... args) { | |
return std::min({flux::size(args)...}); | |
}, self.bases_); | |
} | |
}; | |
namespace detail { | |
template <iterable... Bases> | |
struct zip_adaptor : inline_sequence_base<zip_adaptor<Bases...>> { | |
private: | |
pair_or_tuple_t<Bases...> bases_; | |
friend struct sequence_traits<zip_adaptor>; | |
friend struct zip_traits_base<Bases...>; | |
template <typename... BaseCtx> | |
struct context_type : immovable { | |
std::tuple<BaseCtx...> base_ctxs; | |
using element_type = pair_or_tuple_t<context_element_t<BaseCtx>...>; | |
static constexpr auto run_while_impl(auto& pred, auto&... ctxs) -> iteration_result | |
{ | |
return [&pred, &ctxs..., ... opts = flux::next_element(ctxs)]() mutable { | |
while (true) { | |
if (!(opts.has_value() && ...)) { | |
return iteration_result::complete; | |
} | |
if (!pred(element_type(std::move(opts).value_unchecked()...))) { | |
return iteration_result::incomplete; | |
} | |
((opts = flux::next_element(ctxs)), ...); | |
} | |
}(); | |
} | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
return std::apply([&pred](auto&... ctxs) { return run_while_impl(pred, ctxs...); }, | |
base_ctxs); | |
} | |
}; | |
public: | |
using value_type = pair_or_tuple_t<iterable_value_t<Bases>...>; | |
constexpr explicit zip_adaptor(decays_to<Bases> auto&&... bases) | |
: bases_(FLUX_FWD(bases)...) { } | |
constexpr auto iterate() -> context_type<iteration_context_t<Bases>...> | |
{ | |
return context_type<iteration_context_t<Bases>...>{ | |
.base_ctxs = std::apply( | |
[&](auto&... bases) { | |
return std::tuple<iteration_context_t<Bases>...>( | |
emplace_from([&] { return flux::iterate(bases); })...); | |
}, | |
bases_)}; | |
} | |
constexpr auto iterate() const | |
requires(iterable<Bases const> && ...) | |
{ | |
return context_type<iteration_context_t<Bases const>...>{ | |
.base_ctxs = std::apply( | |
[&](auto&... bases) { | |
return std::tuple<iteration_context_t<Bases const>...>( | |
emplace_from([&] { return flux::iterate(bases); })...); | |
}, | |
bases_)}; | |
} | |
constexpr auto size() -> int_t | |
requires(sized_iterable<Bases> && ...) | |
{ | |
return std::apply([](auto&... bases) { return std::min({flux::iterable_size(bases)...}); }, | |
bases_); | |
} | |
constexpr auto size() const -> int_t | |
requires(sized_iterable<Bases const> && ...) | |
{ | |
return std::apply([](auto&... bases) { return std::min({flux::iterable_size(bases)...}); }, | |
bases_); | |
} | |
}; | |
struct zip_fn { | |
template <adaptable_iterable... Its> | |
[[nodiscard]] | |
constexpr auto operator()(Its&&... its) const | |
{ | |
if constexpr (sizeof...(Its) == 0) { | |
return empty<std::tuple<>>; | |
} else { | |
return zip_adaptor<std::decay_t<Its>...>(FLUX_FWD(its)...); | |
} | |
} | |
}; | |
template <typename Func, iterable... Bases> | |
struct zip_map_adaptor : inline_sequence_base<zip_map_adaptor<Func, Bases...>> { | |
private: | |
pair_or_tuple_t<Bases...> bases_; | |
FLUX_NO_UNIQUE_ADDRESS Func func_; | |
friend struct sequence_traits<zip_map_adaptor>; | |
friend struct zip_traits_base<Bases...>; | |
template <typename Fn, typename... BaseCtx> | |
struct context_type : immovable { | |
Fn fn; | |
std::tuple<BaseCtx...> base_ctxs; | |
using element_type = std::invoke_result_t<Fn&, context_element_t<BaseCtx>...>; | |
static constexpr auto run_while_impl(auto& fn, auto& pred, auto&... ctxs) | |
-> iteration_result | |
{ | |
return [&fn, &pred, &ctxs..., ... opts = flux::next_element(ctxs)]() mutable { | |
while (true) { | |
if (!(opts.has_value() && ...)) { | |
return iteration_result::complete; | |
} | |
if (!pred(std::invoke(fn, std::move(opts).value_unchecked()...))) { | |
return iteration_result::incomplete; | |
} | |
((opts = flux::next_element(ctxs)), ...); | |
} | |
}(); | |
} | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
return std::apply( | |
[&fn = fn, &pred](auto&... ctxs) { return run_while_impl(fn, pred, ctxs...); }, | |
base_ctxs); | |
} | |
}; | |
public: | |
constexpr explicit zip_map_adaptor(Func&& func, decays_to<Bases> auto&&... bases) | |
: bases_(FLUX_FWD(bases)...), func_(std::move(func)) | |
{} | |
constexpr auto iterate() -> context_type<copy_or_ref_t<Func>, iteration_context_t<Bases>...> | |
{ | |
return context_type<copy_or_ref_t<Func>, iteration_context_t<Bases>...>{ | |
.fn = copy_or_ref(func_), | |
.base_ctxs = std::apply( | |
[&](auto&... bases) { | |
return std::tuple<iteration_context_t<Bases>...>( | |
emplace_from([&] { return flux::iterate(bases); })...); | |
}, | |
bases_)}; | |
} | |
constexpr auto iterate() const | |
requires(iterable<Bases const> && ...) | |
&& std::regular_invocable<Func const&, iterable_element_t<Bases const>...> | |
{ | |
return context_type<copy_or_ref_t<Func const>, iteration_context_t<Bases const>...>{ | |
.fn = copy_or_ref(func_), | |
.base_ctxs = std::apply( | |
[&](auto&... bases) { | |
return std::tuple<iteration_context_t<Bases const>...>( | |
emplace_from([&] { return flux::iterate(bases); })...); | |
}, | |
bases_)}; | |
} | |
constexpr auto size() -> int_t | |
requires(sized_iterable<Bases> && ...) | |
{ | |
return std::apply([](auto&... bases) { return std::min({flux::iterable_size(bases)...}); }, | |
bases_); | |
} | |
constexpr auto size() const -> int_t | |
requires(sized_iterable<Bases const> && ...) | |
{ | |
return std::apply([](auto&... bases) { return std::min({flux::iterable_size(bases)...}); }, | |
bases_); | |
} | |
}; | |
struct zip_map_fn { | |
template <typename Func, adaptable_iterable... Its> | |
requires std::regular_invocable<Func&, iterable_element_t<Its>...> | |
[[nodiscard]] | |
constexpr auto operator()(Func func, Its&&... its) const | |
{ | |
if constexpr (sizeof...(Its) == 0) { | |
return empty<std::invoke_result_t<Func>>; | |
} else { | |
return zip_map_adaptor<Func, std::decay_t<Its>...>(std::move(func), FLUX_FWD(its)...); | |
} | |
} | |
}; | |
} // namespace detail | |
template <typename... Bases> | |
struct sequence_traits<detail::zip_adaptor<Bases...>> : zip_traits_base<Bases...> | |
{ | |
private: | |
using base = zip_traits_base<Bases...>; | |
template <typename... Ts> | |
using tuple_t = base::template tuple_t<Ts...>; | |
template <typename From, typename To> | |
using const_like_t = std::conditional_t<std::is_const_v<From>, To const, To>; | |
static constexpr auto read_(auto fn, auto& self, auto const& cur) | |
{ | |
return [&]<std::size_t... I>(std::index_sequence<I...>) { | |
return tuple_t<decltype(base::template read1_<I>(fn, self, cur))...> { | |
base::template read1_<I>(fn, self, cur)... | |
}; | |
}(std::index_sequence_for<Bases...>{}); | |
} | |
public: | |
using value_type = tuple_t<value_t<Bases>...>; | |
template <typename Self> | |
requires (sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto read_at(Self& self, cursor_t<Self> const& cur) | |
{ | |
return read_(flux::read_at, self, cur); | |
} | |
template <typename Self> | |
requires (sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto move_at(Self& self, cursor_t<Self> const& cur) | |
{ | |
return read_(flux::move_at, self, cur); | |
} | |
template <typename Self> | |
requires (sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto read_at_unchecked(Self& self, cursor_t<Self> const& cur) | |
{ | |
return read_(flux::read_at_unchecked, self, cur); | |
} | |
template <typename Self> | |
requires (sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr auto move_at_unchecked(Self& self, cursor_t<Self> const& cur) | |
{ | |
return read_(flux::move_at_unchecked, self, cur); | |
} | |
}; | |
template <typename Func, typename... Bases> | |
struct sequence_traits<detail::zip_map_adaptor<Func, Bases...>> : zip_traits_base<Bases...> | |
{ | |
private: | |
using base = zip_traits_base<Bases...>; | |
template <typename From, typename To> | |
using const_like_t = std::conditional_t<std::is_const_v<From>, To const, To>; | |
static constexpr decltype(auto) read_(auto fn, auto& self, auto const& cur) | |
{ | |
return [&]<std::size_t... I>(std::index_sequence<I...>) -> decltype(auto) { | |
return std::invoke(self.func_, | |
base::template read1_<I>(fn, self, cur)... | |
); | |
}(std::index_sequence_for<Bases...>{}); | |
} | |
public: | |
using value_type = std::remove_cvref_t<std::invoke_result_t<Func&, element_t<Bases>...>>; | |
template <typename Self> | |
requires (sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr decltype(auto) read_at(Self& self, cursor_t<Self> const& cur) | |
{ | |
return read_(flux::read_at, self, cur); | |
} | |
template <typename Self> | |
requires (sequence<const_like_t<Self, Bases>> && ...) | |
static constexpr decltype(auto) read_at_unchecked(Self& self, cursor_t<Self> const& cur) | |
{ | |
return read_(flux::read_at_unchecked, self, cur); | |
} | |
using default_sequence_traits::move_at; | |
using default_sequence_traits::move_at_unchecked; | |
}; | |
FLUX_EXPORT inline constexpr auto zip = detail::zip_fn{}; | |
FLUX_EXPORT inline constexpr auto zip_map = detail::zip_map_fn{}; | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_ZIP_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_IOTA_HPP_INCLUDED | |
#define FLUX_SEQUENCE_IOTA_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
// These concepts mirror the standard ones, except that iter_difference_t is not required | |
template <typename T> | |
concept incrementable = | |
std::regular<T> && | |
requires (T t) { | |
{ ++t } -> std::same_as<T&>; | |
{ t++ } -> std::same_as<T>; | |
}; | |
template <typename T> | |
concept decrementable = | |
incrementable<T> && | |
requires (T t) { | |
{ --t } -> std::same_as<T&>; | |
{ t-- } -> std::same_as<T>; | |
}; | |
template <typename T> | |
concept advancable = | |
decrementable<T> && | |
std::totally_ordered<T> && | |
std::weakly_incrementable<T> && // iter_difference_t exists | |
requires (T t, T const u, std::iter_difference_t<T> o) { | |
{ t += o } -> std::same_as<T&>; | |
{ t -= o } -> std::same_as<T&>; | |
T(u + o); | |
T(o + u); | |
T(u - o); | |
{ u - u } -> std::convertible_to<int_t>; | |
}; | |
struct iota_traits { | |
bool has_start; | |
bool has_end; | |
}; | |
template <incrementable T, iota_traits Traits> | |
struct iota_sequence_traits : default_sequence_traits { | |
using cursor_type = T; | |
static constexpr bool is_infinite = !Traits.has_end; | |
static constexpr auto first(auto& self) -> cursor_type | |
{ | |
if constexpr (Traits.has_start) { | |
return self.start_; | |
} else { | |
return cursor_type{}; | |
} | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) -> bool | |
{ | |
if constexpr (Traits.has_end) { | |
return cur == self.end_; | |
} else { | |
return false; | |
} | |
} | |
static constexpr auto inc(auto&, cursor_type& cur) -> cursor_type& | |
{ | |
return ++cur; | |
} | |
static constexpr auto read_at(auto&, cursor_type const& cur) -> T | |
{ | |
return cur; | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
requires (Traits.has_end) | |
{ | |
return self.end_; | |
} | |
static constexpr auto dec(auto&, cursor_type& cur) -> cursor_type& | |
requires decrementable<T> | |
{ | |
return --cur; | |
} | |
static constexpr auto inc(auto&, cursor_type& cur, int_t offset) -> cursor_type& | |
requires advancable<T> | |
{ | |
return cur += num::cast<std::iter_difference_t<T>>(offset); | |
} | |
static constexpr auto distance(auto&, cursor_type const& from, cursor_type const& to) | |
requires advancable<T> | |
{ | |
return from <= to ? num::cast<int_t>(to - from) : -num::cast<int_t>(from - to); | |
} | |
static constexpr auto size(auto& self) -> int_t | |
requires advancable<T> && (Traits.has_start && Traits.has_end) | |
{ | |
return num::cast<int_t>(self.end_ - self.start_); | |
} | |
}; | |
template <typename T> | |
struct basic_iota_sequence : inline_sequence_base<basic_iota_sequence<T>> { | |
using flux_sequence_traits = iota_sequence_traits<T, iota_traits{}>; | |
friend flux_sequence_traits; | |
}; | |
template <typename T> | |
struct iota_sequence : inline_sequence_base<iota_sequence<T>> { | |
private: | |
T start_; | |
static constexpr iota_traits traits{.has_start = true, .has_end = false}; | |
public: | |
inline constexpr explicit iota_sequence(T from) | |
: start_(std::move(from)) | |
{} | |
using flux_sequence_traits = iota_sequence_traits<T, traits>; | |
friend flux_sequence_traits; | |
}; | |
template <typename T> | |
struct bounded_iota_sequence : inline_sequence_base<bounded_iota_sequence<T>> { | |
T start_; | |
T end_; | |
static constexpr iota_traits traits{.has_start = true, .has_end = true}; | |
public: | |
inline constexpr bounded_iota_sequence(T from, T to) | |
: start_(std::move(from)), | |
end_(std::move(to)) | |
{} | |
using flux_sequence_traits = iota_sequence_traits<T, traits>; | |
friend flux_sequence_traits; | |
}; | |
struct iota_fn { | |
template <incrementable T> | |
constexpr auto operator()(T from) const | |
{ | |
return iota_sequence<T>(std::move(from)); | |
} | |
template <incrementable T> | |
constexpr auto operator()(T from, T to) const | |
{ | |
return bounded_iota_sequence<T>(std::move(from), std::move(to)); | |
} | |
}; | |
struct ints_fn { | |
inline constexpr auto operator()() const { return basic_iota_sequence<int_t>(); } | |
inline constexpr auto operator()(int_t from) const { return iota_sequence<int_t>(from); } | |
inline constexpr auto operator()(int_t from, int_t to) const | |
{ | |
return bounded_iota_sequence<int_t>(from, to); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto iota = detail::iota_fn{}; | |
FLUX_EXPORT inline constexpr auto ints = detail::ints_fn{}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_IOTA_HPP_INCLUDED | |
#include <array> | |
namespace flux { | |
namespace detail { | |
template <typename Base, int_t N> | |
struct adjacent_sequence_traits_base : default_sequence_traits { | |
protected: | |
struct cursor_type { | |
std::array<cursor_t<Base>, N> arr{}; | |
friend constexpr auto operator==(cursor_type const& lhs, cursor_type const& rhs) | |
-> bool | |
{ | |
return lhs.arr.back() == rhs.arr.back(); | |
} | |
friend constexpr auto operator<=>(cursor_type const& lhs, cursor_type const& rhs) | |
-> std::strong_ordering | |
requires ordered_cursor<cursor_t<Base>> | |
{ | |
return lhs.arr.back() <=> rhs.arr.back(); | |
} | |
}; | |
public: | |
static inline constexpr bool is_infinite = infinite_sequence<Base>; | |
static constexpr auto first(auto& self) -> cursor_type | |
{ | |
cursor_type out{flux::first(self.base_), }; | |
FLUX_FOR(auto i, flux::iota(std::size_t{1}, std::size_t{N})) { | |
out.arr[i] = out.arr[i - 1]; | |
if (!flux::is_last(self.base_, out.arr[i])) { | |
flux::inc(self.base_, out.arr[i]); | |
} | |
} | |
return out; | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.arr.back()); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) -> void | |
{ | |
std::apply([&](auto&... curs) { | |
(flux::inc(self.base_, curs), ...); | |
}, cur.arr); | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
requires (bidirectional_sequence<Base> && bounded_sequence<Base>) | |
{ | |
cursor_type out{}; | |
out.arr.back() = flux::last(self.base_); | |
auto const first = flux::first(self.base_); | |
FLUX_FOR(auto i, flux::iota(std::size_t{0}, std::size_t{N}-1).reverse()) { | |
out.arr[i] = out.arr[i + 1]; | |
if (out.arr[i] != first) { | |
flux::dec(self.base_, out.arr[i]); | |
} | |
} | |
return out; | |
} | |
static constexpr auto dec(auto& self, cursor_type& cur) -> void | |
requires bidirectional_sequence<Base> | |
{ | |
std::apply([&self](auto&... curs) { | |
(flux::dec(self.base_, curs), ...); | |
}, cur.arr); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur, int_t offset) -> void | |
requires random_access_sequence<Base> | |
{ | |
std::apply([&self, offset](auto&... curs) { | |
(flux::inc(self.base_, curs, offset), ...); | |
}, cur.arr); | |
} | |
static constexpr auto distance(auto& self, cursor_type const& from, cursor_type const& to) | |
-> int_t | |
requires random_access_sequence<Base> | |
{ | |
return flux::distance(self.base_, from.arr.back(), to.arr.back()); | |
} | |
static constexpr auto size(auto& self) -> int_t | |
requires sized_sequence<Base> | |
{ | |
auto s = (flux::size(self.base_) - N) + 1; | |
return (cmp::max)(s, int_t {0}); | |
} | |
}; | |
template <typename Base, int_t N> | |
struct adjacent_adaptor : inline_sequence_base<adjacent_adaptor<Base, N>> { | |
private: | |
Base base_; | |
friend struct adjacent_sequence_traits_base<Base, N>; | |
public: | |
constexpr explicit adjacent_adaptor(decays_to<Base> auto&& base) | |
: base_(FLUX_FWD(base)) | |
{} | |
struct flux_sequence_traits : adjacent_sequence_traits_base<Base, N> { | |
private: | |
using cursor_type = adjacent_sequence_traits_base<Base, N>::cursor_type; | |
template <auto& ReadFn> | |
static constexpr auto do_read(auto& self, cursor_type const& cur) | |
{ | |
return std::apply([&](auto const&... curs) { | |
return pair_or_tuple_t<decltype(ReadFn(self.base_, curs))...>( | |
ReadFn(self.base_, curs)...); | |
}, cur.arr); | |
} | |
template <std::size_t I> | |
using base_value_t = value_t<Base>; | |
template <std::size_t... Is> | |
static auto make_value_type(std::index_sequence<Is...>) -> pair_or_tuple_t<base_value_t<Is>...>; | |
public: | |
using value_type = decltype(make_value_type(std::make_index_sequence<N>{})); | |
static constexpr auto read_at(auto& self, cursor_type const& cur) | |
-> decltype(do_read<flux::read_at>(self, cur)) | |
{ | |
return do_read<flux::read_at>(self, cur); | |
} | |
static constexpr auto move_at(auto& self, cursor_type const& cur) | |
-> decltype(do_read<flux::move_at>( self, cur)) | |
{ | |
return do_read<flux::move_at>( self, cur); | |
} | |
static constexpr auto read_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(do_read<flux::read_at_unchecked>(self, cur)) | |
{ | |
return do_read<flux::read_at_unchecked>(self, cur); | |
} | |
static constexpr auto move_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(do_read<flux::move_at_unchecked>(self, cur)) | |
{ | |
return do_read<flux::move_at_unchecked>(self, cur); | |
} | |
}; | |
}; | |
template <typename Base, int_t N, typename Func> | |
struct adjacent_map_adaptor : inline_sequence_base<adjacent_map_adaptor<Base, N, Func>> { | |
private: | |
Base base_; | |
Func func_; | |
friend struct adjacent_sequence_traits_base<Base, N>; | |
public: | |
constexpr explicit adjacent_map_adaptor(decays_to<Base> auto&& base, Func&& func) | |
: base_(FLUX_FWD(base)), | |
func_(std::move(func)) | |
{} | |
struct flux_sequence_traits : adjacent_sequence_traits_base<Base, N> { | |
template <typename Self> | |
static constexpr auto read_at(Self& self, cursor_t<Self> const& cur) | |
-> decltype(auto) | |
requires repeated_invocable<decltype((self.func_)), element_t<decltype((self.base_))>, N> | |
{ | |
return std::apply([&](auto const&... curs) { | |
return std::invoke(self.func_, flux::read_at(self.base_, curs)...); | |
}, cur.arr); | |
} | |
}; | |
}; | |
template <int_t N> | |
struct adjacent_fn { | |
template <adaptable_sequence Seq> | |
requires multipass_sequence<Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const -> multipass_sequence auto | |
{ | |
return adjacent_adaptor<std::decay_t<Seq>, N>(FLUX_FWD(seq)); | |
} | |
}; | |
template <int_t N> | |
struct adjacent_map_fn { | |
template <adaptable_sequence Seq, typename Func> | |
requires multipass_sequence<Seq> && | |
repeated_invocable<Func, element_t<Seq>, N> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, Func func) const -> multipass_sequence auto | |
{ | |
return adjacent_map_adaptor<std::decay_t<Seq>, N, Func>( | |
FLUX_FWD(seq), std::move(func)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
template <int_t N> | |
requires(N > 0) | |
inline constexpr auto adjacent = detail::adjacent_fn<N> {}; | |
FLUX_EXPORT inline constexpr auto pairwise = adjacent<2>; | |
FLUX_EXPORT | |
template <int_t N> | |
requires(N > 0) | |
inline constexpr auto adjacent_map = detail::adjacent_map_fn<N> {}; | |
FLUX_EXPORT inline constexpr auto pairwise_map = adjacent_map<2>; | |
template <typename D> | |
template <int_t N> | |
constexpr auto inline_sequence_base<D>::adjacent() && | |
requires multipass_sequence<D> | |
{ | |
return flux::adjacent<N>(std::move(derived())); | |
} | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::pairwise() && | |
requires multipass_sequence<D> | |
{ | |
return flux::pairwise(std::move(derived())); | |
} | |
template <typename D> | |
template <int_t N, typename Func> | |
requires multipass_sequence<D> | |
constexpr auto inline_sequence_base<D>::adjacent_map(Func func) && | |
{ | |
return flux::adjacent_map<N>(std::move(derived()), std::move(func)); | |
} | |
template <typename D> | |
template <typename Func> | |
requires multipass_sequence<D> | |
constexpr auto inline_sequence_base<D>::pairwise_map(Func func) && | |
{ | |
return flux::pairwise_map(std::move(derived()), std::move(func)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_ADJACENT_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_ADJACENT_FILTER_HPP_INCLUDED | |
#define FLUX_ADAPTOR_ADJACENT_FILTER_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <multipass_sequence Base, typename Pred> | |
struct adjacent_filter_adaptor | |
: inline_sequence_base<adjacent_filter_adaptor<Base, Pred>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Pred pred_; | |
template <typename BaseCtx, typename FilterPred> | |
struct context_type : immovable { | |
BaseCtx base_ctx; | |
FilterPred filter_pred; | |
using opt_t = decltype(next_element(std::declval<BaseCtx&>())); | |
opt_t opt = nullopt; | |
using element_type = context_element_t<BaseCtx>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
if (!opt) { | |
opt = next_element(base_ctx); | |
if (!opt) { | |
return iteration_result::complete; | |
} | |
if (!pred(opt.value_unchecked())) { | |
return iteration_result::incomplete; | |
} | |
} | |
return base_ctx.run_while([&](auto&& elem) { | |
if (!filter_pred(opt.value_unchecked(), elem)) { | |
return loop_continue; | |
} else { | |
opt.emplace(elem); | |
return pred(elem); | |
} | |
}); | |
} | |
}; | |
public: | |
constexpr adjacent_filter_adaptor(decays_to<Base> auto&& base, Pred pred) | |
: base_(FLUX_FWD(base)), | |
pred_(std::move(pred)) | |
{} | |
constexpr auto iterate() | |
{ | |
return context_type<iteration_context_t<Base>, copy_or_ref_t<Pred>>{ | |
.base_ctx = flux::iterate(base_), .filter_pred = copy_or_ref(pred_)}; | |
} | |
constexpr auto iterate() const | |
requires multipass_sequence<Base const> | |
&& std::predicate<Pred const&, iterable_element_t<Base const>> | |
{ | |
return context_type<iteration_context_t<Base>, copy_or_ref_t<Pred>>{ | |
.base_ctx = flux::iterate(base_), .filter_pred = copy_or_ref(pred_)}; | |
} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> base_cur; | |
friend auto operator==(cursor_type const&, cursor_type const&) -> bool | |
= default; | |
}; | |
public: | |
using value_type = value_t<Base>; | |
static constexpr auto first(auto& self) -> cursor_type | |
{ | |
return cursor_type{flux::first(self.base_)}; | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.base_cur); | |
} | |
static constexpr auto read_at(auto& self, cursor_type const& cur) | |
-> decltype(flux::read_at(self.base_, cur.base_cur)) | |
{ | |
return flux::read_at(self.base_, cur.base_cur); | |
} | |
static constexpr auto read_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(flux::read_at_unchecked(self.base_, cur.base_cur)) | |
{ | |
return flux::read_at_unchecked(self.base_, cur.base_cur); | |
} | |
static constexpr auto move_at(auto& self, cursor_type const& cur) | |
-> decltype(flux::move_at(self.base_, cur.base_cur)) | |
{ | |
return flux::move_at(self.base_, cur.base_cur); | |
} | |
static constexpr auto move_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(flux::move_at_unchecked(self.base_, cur.base_cur)) | |
{ | |
return flux::move_at_unchecked(self.base_, cur.base_cur); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) -> void | |
{ | |
auto temp = cur.base_cur; | |
flux::inc(self.base_, cur.base_cur); | |
while (!flux::is_last(self.base_, cur.base_cur)) { | |
if (std::invoke(self.pred_, | |
flux::read_at(self.base_, temp), | |
flux::read_at(self.base_, cur.base_cur))) { | |
break; | |
} | |
flux::inc(self.base_, cur.base_cur); | |
} | |
} | |
static constexpr auto dec(auto& self, cursor_type& cur) -> void | |
requires bidirectional_sequence<Base> | |
{ | |
auto first = flux::first(self.base_); | |
FLUX_DEBUG_ASSERT(cur.base_cur != first); | |
flux::dec(self.base_, cur.base_cur); | |
while (cur.base_cur != first) { | |
auto temp = flux::prev(self.base_, cur.base_cur); | |
if (std::invoke(self.pred_, flux::read_at(self.base_, temp), | |
flux::read_at(self.base_, cur.base_cur))) { | |
break; | |
} | |
cur.base_cur = std::move(temp); | |
} | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
requires bounded_sequence<Base> | |
{ | |
return cursor_type{flux::last(self.base_)}; | |
} | |
}; | |
}; | |
struct adjacent_filter_fn { | |
template <adaptable_sequence Seq, typename Pred> | |
requires multipass_sequence<Seq> && | |
std::predicate<Pred&, element_t<Seq>, element_t<Seq>> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, Pred pred) const -> multipass_sequence auto | |
{ | |
return adjacent_filter_adaptor<std::decay_t<Seq>, Pred>( | |
FLUX_FWD(seq), std::move(pred)); | |
} | |
}; | |
struct dedup_fn { | |
template <adaptable_sequence Seq> | |
requires multipass_sequence<Seq> && | |
std::equality_comparable<element_t<Seq>> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const -> multipass_sequence auto | |
{ | |
return adjacent_filter_fn{}(FLUX_FWD(seq), std::ranges::not_equal_to{}); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto adjacent_filter = detail::adjacent_filter_fn{}; | |
FLUX_EXPORT inline constexpr auto dedup = detail::dedup_fn{}; | |
template <typename D> | |
template <typename Pred> | |
requires multipass_sequence<D> && | |
std::predicate<Pred&, element_t<D>, element_t<D>> | |
constexpr auto inline_sequence_base<D>::adjacent_filter(Pred pred) && | |
{ | |
return flux::adjacent_filter(std::move(derived()), std::move(pred)); | |
} | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::dedup() && | |
requires multipass_sequence<D> && | |
std::equality_comparable<element_t<D>> | |
{ | |
return flux::dedup(std::move(derived())); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_ADJACENT_FILTER_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_CACHE_LAST_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CACHE_LAST_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <sequence Base> | |
struct cache_last_adaptor : inline_sequence_base<cache_last_adaptor<Base>> | |
{ | |
private: | |
Base base_; | |
flux::optional<cursor_t<Base>> cached_last_{}; | |
friend struct passthrough_traits_base; | |
constexpr auto base() -> Base& { return base_; } | |
public: | |
constexpr explicit cache_last_adaptor(decays_to<Base> auto&& base) | |
: base_(FLUX_FWD(base)) | |
{} | |
struct flux_sequence_traits : detail::passthrough_traits_base { | |
using value_type = value_t<Base>; | |
using self_t = cache_last_adaptor; | |
static constexpr bool disable_multipass = !multipass_sequence<Base>; | |
static constexpr auto is_last(self_t& self, cursor_t<Base> const& cur) | |
{ | |
if (flux::is_last(self.base_, cur)) { | |
self.cached_last_ = flux::optional(cur); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
static constexpr auto last(self_t& self) | |
{ | |
if (!self.cached_last_) { | |
auto cur = flux::first(self); | |
while (!is_last(self, cur)) { | |
flux::inc(self.base_, cur); | |
} | |
FLUX_DEBUG_ASSERT(self.cached_last_.has_value()); | |
} | |
return self.cached_last_.value_unchecked(); | |
} | |
}; | |
}; | |
struct cache_last_fn { | |
template <adaptable_sequence Seq> | |
requires bounded_sequence<Seq> || | |
(multipass_sequence<Seq> && not infinite_sequence<Seq>) | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const | |
{ | |
if constexpr (bounded_sequence<Seq>) { | |
return FLUX_FWD(seq); | |
} else { | |
return cache_last_adaptor<std::decay_t<Seq>>(FLUX_FWD(seq)); | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto cache_last = detail::cache_last_fn{}; | |
template <typename Derived> | |
constexpr auto inline_sequence_base<Derived>::cache_last() && | |
requires bounded_sequence<Derived> || | |
(multipass_sequence<Derived> && not infinite_sequence<Derived>) | |
{ | |
return flux::cache_last(std::move(derived())); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_CACHE_LAST_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail dot com) | |
// Copyright (c) 2023 NVIDIA Corporation (reply-to: [email protected]) | |
// | |
// 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 FLUX_ADAPTOR_CARTESIAN_BASE_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CARTESIAN_BASE_HPP_INCLUDED | |
namespace flux::detail { | |
inline constexpr auto checked_pow = | |
[]<std::signed_integral T, std::unsigned_integral U>(T base, U exponent, | |
std::source_location loc = std::source_location::current()) | |
-> T | |
{ | |
T res{1}; | |
for(U i{0}; i < exponent; i++) { | |
res = num::checked_mul(res, base, loc); | |
} | |
return res; | |
}; | |
enum class cartesian_kind { product, power }; | |
enum class read_kind { tuple, map }; | |
template <typename B0, typename...> | |
inline constexpr bool cartesian_is_bounded = bounded_sequence<B0>; | |
template <typename T, std::size_t RepeatCount> | |
struct tuple_repeated { | |
template <std::size_t I> | |
using repeater = T; | |
template <std::size_t... Is> | |
static auto make_tuple(std::index_sequence<Is...>) -> std::tuple<repeater<Is>...>; | |
using type = decltype(make_tuple(std::make_index_sequence<RepeatCount>{})); | |
}; | |
template <typename T, std::size_t RepeatCount> | |
using tuple_repeated_t = tuple_repeated<T, RepeatCount>::type; | |
template<std::size_t Arity, cartesian_kind CartesianKind, read_kind ReadKind, typename... Bases> | |
struct cartesian_traits_types { | |
}; | |
template<std::size_t Arity, typename Base> | |
struct cartesian_traits_types<Arity, cartesian_kind::power, read_kind::tuple, Base> { | |
using value_type = tuple_repeated_t<value_t<Base>, Arity>; | |
}; | |
template<std::size_t Arity, typename... Bases> | |
struct cartesian_traits_types<Arity, cartesian_kind::product, read_kind::tuple, Bases...> { | |
using value_type = std::tuple<value_t<Bases>...>; | |
}; | |
template <std::size_t Arity, cartesian_kind CartesianKind, read_kind ReadKind, typename... Bases> | |
struct cartesian_traits_base_impl : default_sequence_traits { | |
private: | |
template<std::size_t I, typename Self> | |
static constexpr auto& get_base(Self& self) | |
requires (CartesianKind == cartesian_kind::power) | |
{ | |
return self.base_; | |
} | |
template<std::size_t I, typename Self> | |
static constexpr auto& get_base(Self& self) | |
requires (CartesianKind == cartesian_kind::product) | |
{ | |
return std::get<I>(self.bases_); | |
} | |
template <std::size_t I, typename Self> | |
static constexpr auto inc_impl(Self& self, cursor_t<Self>& cur) -> cursor_t<Self>& | |
{ | |
flux::inc(get_base<I>(self), std::get<I>(cur)); | |
if constexpr (I > 0) { | |
if (flux::is_last(get_base<I>(self), std::get<I>(cur))) { | |
std::get<I>(cur) = flux::first(get_base<I>(self)); | |
inc_impl<I-1>(self, cur); | |
} | |
} | |
return cur; | |
} | |
template <std::size_t I, typename Self> | |
static constexpr auto dec_impl(Self& self, cursor_t<Self>& cur) -> cursor_t<Self>& | |
{ | |
if (std::get<I>(cur) == flux::first(get_base<I>(self))) { | |
std::get<I>(cur) = flux::last(get_base<I>(self)); | |
if constexpr (I > 0) { | |
dec_impl<I-1>(self, cur); | |
} | |
} | |
flux::dec(get_base<I>(self), std::get<I>(cur)); | |
return cur; | |
} | |
template <std::size_t I, typename Self> | |
static constexpr auto ra_inc_impl(Self& self, cursor_t<Self>& cur, int_t offset) | |
-> cursor_t<Self>& | |
{ | |
if (offset == 0) { | |
return cur; | |
} | |
auto& base = get_base<I>(self); | |
const auto this_index = flux::distance(base, flux::first(base), std::get<I>(cur)); | |
auto new_index = num::add(this_index, offset); | |
auto this_size = flux::size(base); | |
// If the new index overflows the maximum or underflows zero, calculate the carryover and fix it. | |
if (new_index < 0 || new_index >= this_size) { | |
offset = num::div(new_index, this_size); | |
new_index = num::mod(new_index, this_size); | |
// Correct for negative index which may happen when underflowing. | |
if (new_index < 0) { | |
new_index = num::add(new_index, this_size); | |
offset = num::sub(offset, flux::int_t(1)); | |
} | |
// Call the next level down if necessary. | |
if constexpr (I > 0) { | |
if (offset != 0) { | |
ra_inc_impl<I-1>(self, cur, offset); | |
} | |
} | |
} | |
flux::inc(base, std::get<I>(cur), num::sub(new_index, this_index)); | |
return cur; | |
} | |
template <std::size_t I, typename Self> | |
static constexpr auto distance_impl(Self& self, cursor_t<Self> const& from, | |
cursor_t<Self> const& to) -> int_t | |
{ | |
if constexpr (I == 0) { | |
return flux::distance(get_base<0>(self), std::get<0>(from), std::get<0>(to)); | |
} else { | |
auto prev_dist = distance_impl<I-1>(self, from, to); | |
auto our_sz = flux::size(get_base<I>(self)); | |
auto our_dist = flux::distance(get_base<I>(self), std::get<I>(from), std::get<I>(to)); | |
return prev_dist * our_sz + our_dist; | |
} | |
} | |
template <std::size_t I, typename Self, typename Fn> | |
static constexpr auto read1_(Fn fn, Self& self, cursor_t<Self> const& cur) | |
-> decltype(auto) | |
{ | |
return fn(get_base<I>(self), std::get<I>(cur)); | |
} | |
template <typename Fn, typename Self> | |
static constexpr auto read_(Fn& fn, Self& self, cursor_t<Self> const& cur) | |
-> decltype(auto) | |
requires (ReadKind == read_kind::tuple) | |
{ | |
return [&]<std::size_t... N>(std::index_sequence<N...>) { | |
return std::tuple<decltype(read1_<N>(fn, self, cur))...>(read1_<N>(fn, self, cur)...); | |
}(std::make_index_sequence<Arity>{}); | |
} | |
template <std::size_t I, typename Self, typename Function, | |
typename... PartialElements> | |
static constexpr void for_each_while_impl(Self& self, | |
bool& keep_going, | |
cursor_t<Self>& cur, | |
Function&& func, | |
PartialElements&&... partial_elements) | |
{ | |
// We need to iterate right to left. | |
if constexpr (I == Arity - 1) { | |
std::get<I>(cur) = flux::seq_for_each_while(get_base<I>(self), [&](auto&& elem) { | |
keep_going = std::invoke( | |
func, element_t<Self>(FLUX_FWD(partial_elements)..., FLUX_FWD(elem))); | |
return keep_going; | |
}); | |
} else { | |
std::get<I>(cur) = flux::seq_for_each_while(get_base<I>(self), [&](auto&& elem) { | |
for_each_while_impl<I + 1>(self, keep_going, cur, func, | |
FLUX_FWD(partial_elements)..., FLUX_FWD(elem)); | |
return keep_going; | |
}); | |
} | |
} | |
protected: | |
using types = cartesian_traits_types<Arity, CartesianKind, ReadKind, Bases...>; | |
public: | |
template <typename Self> | |
static constexpr auto first(Self& self) | |
requires (CartesianKind == cartesian_kind::product) | |
{ | |
return std::apply([](auto&&... args) { | |
return std::tuple(flux::first(FLUX_FWD(args))...); | |
}, self.bases_); | |
} | |
template <typename Self> | |
static constexpr auto first(Self& self) | |
requires (CartesianKind == cartesian_kind::power) | |
{ | |
auto base_cur = flux::first(self.base_); | |
return [&base_cur]<std::size_t... Is>(std::index_sequence<Is...>) { | |
static_assert(sizeof...(Bases) == 1); | |
std::array<cursor_t<Bases>..., Arity> cur = {(static_cast<void>(Is), base_cur)...}; | |
return cur; | |
}(std::make_index_sequence<Arity>{}); | |
} | |
template <typename Self> | |
static constexpr auto size(Self& self) -> int_t | |
requires(CartesianKind == cartesian_kind::product && (sized_sequence<Bases> && ...)) | |
{ | |
return std::apply( | |
[](auto& base0, auto&... bases) { | |
int_t sz = flux::size(base0); | |
((sz = num::mul(sz, flux::size(bases))), ...); | |
return sz; | |
}, | |
self.bases_); | |
} | |
template <typename Self> | |
static constexpr auto size(Self& self) -> int_t | |
requires(CartesianKind == cartesian_kind::power && (sized_sequence<Bases> && ...)) | |
{ | |
return checked_pow(flux::size(self.base_), Arity); | |
} | |
template <typename Self> | |
static constexpr auto is_last(Self& self, cursor_t<Self> const& cur) -> bool | |
{ | |
return [&]<std::size_t... N>(std::index_sequence<N...>) { | |
return (flux::is_last(get_base<N>(self), std::get<N>(cur)) || ...); | |
}(std::make_index_sequence<Arity>{}); | |
} | |
template <typename Self> | |
static constexpr auto dec(Self& self, cursor_t<Self>& cur) -> cursor_t<Self>& | |
requires ((bidirectional_sequence<Bases> && ...) && | |
(bounded_sequence<Bases> && ...)) | |
{ | |
return dec_impl<Arity - 1>(self, cur); | |
} | |
template <typename Self> | |
static constexpr auto inc(Self& self, cursor_t<Self>& cur, int_t offset) -> cursor_t<Self>& | |
requires((random_access_sequence<Bases> && ...) && (sized_sequence<Bases> && ...)) | |
{ | |
return ra_inc_impl<Arity - 1>(self, cur, offset); | |
} | |
template <typename Self> | |
static constexpr auto inc(Self& self, cursor_t<Self>& cur) -> cursor_t<Self>& | |
{ | |
return inc_impl<Arity - 1>(self, cur); | |
} | |
template <typename Self> | |
static constexpr auto last(Self& self) -> cursor_t<Self> | |
requires cartesian_is_bounded<Bases...> | |
{ | |
if constexpr (CartesianKind == cartesian_kind::product) { | |
auto cur = first(self); | |
bool any_is_empty = std::apply([](auto& /*ignored*/, auto&... bases) { | |
return (flux::is_empty(bases) || ...); | |
}, self.bases_); | |
if (!any_is_empty) { | |
std::get<0>(cur) = flux::last(get_base<0>(self)); | |
} | |
return cur; | |
} else { | |
auto cur = first(self); | |
std::get<0>(cur) = flux::last(get_base<0>(self)); | |
return cur; | |
} | |
} | |
template <typename Self> | |
static constexpr auto distance(Self& self, cursor_t<Self> const& from, cursor_t<Self> const& to) | |
-> int_t | |
requires((random_access_sequence<Bases> && ...) && (sized_sequence<Bases> && ...)) | |
{ | |
return distance_impl<Arity - 1>(self, from, to); | |
} | |
template <typename Self> | |
static constexpr auto read_at(Self& self, cursor_t<Self> const& cur) | |
requires (ReadKind == read_kind::tuple) | |
{ | |
return read_(flux::read_at, self, cur); | |
} | |
template <typename Self> | |
static constexpr auto read_at(Self& self, cursor_t<Self> const& cur) | |
-> decltype(auto) | |
requires (ReadKind == read_kind::map) | |
{ | |
return [&]<std::size_t... N>(std::index_sequence<N...>) -> decltype(auto) { | |
return std::invoke(self.func_, flux::read_at(get_base<N>(self), std::get<N>(cur))...); | |
}(std::make_index_sequence<Arity>{}); | |
} | |
template <typename Self> | |
static constexpr auto read_at_unchecked(Self& self, cursor_t<Self> const& cur) | |
-> decltype(auto) | |
requires (ReadKind == read_kind::map) | |
{ | |
return [&]<std::size_t... N>(std::index_sequence<N...>) -> decltype(auto) { | |
return std::invoke(self.func_, flux::read_at_unchecked(get_base<N>(self), std::get<N>(cur))...); | |
}(std::make_index_sequence<Arity>{}); | |
} | |
template <typename Self> | |
static constexpr auto move_at(Self& self, cursor_t<Self> const& cur) | |
-> decltype(auto) | |
requires (ReadKind == read_kind::map) | |
{ | |
return default_sequence_traits::move_at(self, cur); | |
} | |
template <typename Self> | |
static constexpr auto move_at_unchecked(Self& self, cursor_t<Self> const& cur) | |
-> decltype(auto) | |
requires (ReadKind == read_kind::map) | |
{ | |
return default_sequence_traits::move_at_unchecked(self, cur); | |
} | |
template <typename Self> | |
static constexpr auto move_at(Self& self, cursor_t<Self> const& cur) | |
requires (ReadKind == read_kind::tuple) | |
{ | |
return read_(flux::move_at, self, cur); | |
} | |
template <typename Self> | |
static constexpr auto read_at_unchecked(Self& self, cursor_t<Self> const& cur) | |
requires (ReadKind == read_kind::tuple) | |
{ | |
return read_(flux::read_at_unchecked, self, cur); | |
} | |
template <typename Self> | |
static constexpr auto move_at_unchecked(Self& self, cursor_t<Self> const& cur) | |
requires (ReadKind == read_kind::tuple) | |
{ | |
return read_(flux::move_at_unchecked, self, cur); | |
} | |
template <typename Self, typename Function> | |
static constexpr auto for_each_while(Self& self, Function&& func) | |
-> cursor_t<Self> | |
requires (ReadKind == read_kind::tuple) | |
{ | |
bool keep_going = true; | |
cursor_t<Self> cur; | |
for_each_while_impl<0>(self, keep_going, cur, FLUX_FWD(func)); | |
return cur; | |
} | |
template <typename Self, typename Function> | |
static constexpr auto for_each_while(Self& self, Function&& func) -> cursor_t<Self> | |
requires (ReadKind == read_kind::map) | |
{ | |
return default_sequence_traits::for_each_while(self, FLUX_FWD(func)); | |
} | |
}; | |
template <std::size_t Arity, cartesian_kind CartesianKind, read_kind ReadKind, typename... Bases> | |
struct cartesian_traits_base : cartesian_traits_base_impl<Arity, CartesianKind, ReadKind, Bases...> { | |
using impl = cartesian_traits_base_impl<Arity, CartesianKind, ReadKind, Bases...>; | |
}; | |
template <std::size_t Arity, cartesian_kind CartesianKind, typename... Bases> | |
struct cartesian_traits_base<Arity, CartesianKind, read_kind::tuple, Bases...> : cartesian_traits_base_impl<Arity, CartesianKind, read_kind::tuple, Bases...> { | |
using impl = cartesian_traits_base_impl<Arity, CartesianKind, read_kind::tuple, Bases...>; | |
using value_type = typename impl::types::value_type; | |
}; | |
} | |
#endif //FLUX_ADAPTOR_CARTESIAN_BASE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail dot com) | |
// Copyright (c) 2023 NVIDIA Corporation (reply-to: [email protected]) | |
// | |
// 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 FLUX_ADAPTOR_CARTESIAN_POWER_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CARTESIAN_POWER_HPP_INCLUDED | |
#include <tuple> | |
namespace flux { | |
namespace detail { | |
template <std::size_t PowN, sequence Base> | |
struct cartesian_power_adaptor | |
: inline_sequence_base<cartesian_power_adaptor<PowN, Base>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
public: | |
constexpr explicit cartesian_power_adaptor(decays_to<Base> auto&& base) | |
: base_(FLUX_FWD(base)) | |
{} | |
using flux_sequence_traits = cartesian_traits_base< | |
PowN, | |
cartesian_kind::power, | |
read_kind::tuple, | |
Base | |
>; | |
friend flux_sequence_traits::impl; | |
}; | |
template<std::size_t PowN> | |
struct cartesian_power_fn { | |
template <adaptable_sequence Seq> | |
requires multipass_sequence<Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const | |
{ | |
if constexpr(PowN == 0) { | |
return empty<std::tuple<>>; | |
} else { | |
return cartesian_power_adaptor<PowN, std::decay_t<Seq>>( | |
FLUX_FWD(seq)); | |
} | |
} | |
}; | |
} // end namespace detail | |
FLUX_EXPORT | |
template <int_t N> | |
requires(N >= 0) | |
inline constexpr auto cartesian_power = detail::cartesian_power_fn<N> {}; | |
} // end namespace flux | |
#endif // FLUX_ADAPTOR_CARTESIAN_POWER_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_CARTESIAN_POWER_MAP_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CARTESIAN_POWER_MAP_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <sequence Base, std::size_t PowN, typename Func> | |
struct cartesian_power_map_adaptor | |
: inline_sequence_base<cartesian_power_map_adaptor<Base, PowN, Func>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Func func_; | |
public: | |
constexpr explicit cartesian_power_map_adaptor(decays_to<Base> auto&& base, decays_to<Func> auto&& func) | |
: base_(FLUX_FWD(base)), | |
func_(FLUX_FWD(func)) | |
{} | |
using flux_sequence_traits = cartesian_traits_base< | |
PowN, | |
cartesian_kind::power, | |
read_kind::map, | |
Base | |
>; | |
friend flux_sequence_traits::impl; | |
}; | |
template <std::size_t PowN> | |
struct cartesian_power_map_fn | |
{ | |
template <adaptable_sequence Seq, typename Func> | |
requires multipass_sequence<Seq> && | |
detail::repeated_invocable<Func&, element_t<Seq>, PowN> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, Func func) const | |
{ | |
if constexpr(PowN == 0) { | |
return empty<std::invoke_result_t<Func>>; | |
} else { | |
return cartesian_power_map_adaptor<std::decay_t<Seq>, PowN, Func>( | |
FLUX_FWD(seq), std::move(func)); | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT | |
template <int_t N> | |
requires(N >= 0) | |
inline constexpr auto cartesian_power_map = detail::cartesian_power_map_fn<N> {}; | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_CARTESIAN_POWER_MAP_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail dot com) | |
// Copyright (c) 2023 NVIDIA Corporation (reply-to: [email protected]) | |
// | |
// 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 FLUX_ADAPTOR_CARTESIAN_PRODUCT_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CARTESIAN_PRODUCT_HPP_INCLUDED | |
#include <tuple> | |
namespace flux { | |
namespace detail { | |
template <sequence... Bases> | |
struct cartesian_product_adaptor | |
: inline_sequence_base<cartesian_product_adaptor<Bases...>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS std::tuple<Bases...> bases_; | |
public: | |
constexpr explicit cartesian_product_adaptor(decays_to<Bases> auto&&... bases) | |
: bases_(FLUX_FWD(bases)...) | |
{} | |
using flux_sequence_traits = cartesian_traits_base< | |
sizeof...(Bases), | |
cartesian_kind::product, | |
read_kind::tuple, | |
Bases... | |
>; | |
friend flux_sequence_traits::impl; | |
}; | |
struct cartesian_product_fn { | |
template <adaptable_sequence Seq0, adaptable_sequence... Seqs> | |
requires (multipass_sequence<Seqs> && ...) | |
[[nodiscard]] | |
constexpr auto operator()(Seq0&& seq0, Seqs&&... seqs) const | |
{ | |
return cartesian_product_adaptor<std::decay_t<Seq0>, std::decay_t<Seqs>...>( | |
FLUX_FWD(seq0), FLUX_FWD(seqs)...); | |
} | |
}; | |
} // end namespace detail | |
FLUX_EXPORT inline constexpr auto cartesian_product = detail::cartesian_product_fn{}; | |
} // end namespace flux | |
#endif // FLUX_ADAPTOR_CARTESIAN_PRODUCT_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_CARTESIAN_PRODUCT_MAP_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CARTESIAN_PRODUCT_MAP_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename Func, sequence... Bases> | |
struct cartesian_product_map_adaptor | |
: inline_sequence_base<cartesian_product_map_adaptor<Func, Bases...>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS std::tuple<Bases...> bases_; | |
FLUX_NO_UNIQUE_ADDRESS Func func_; | |
public: | |
constexpr explicit cartesian_product_map_adaptor(decays_to<Func> auto&& func, decays_to<Bases> auto&&... bases) | |
: bases_(FLUX_FWD(bases)...), | |
func_(FLUX_FWD(func)) | |
{} | |
using flux_sequence_traits = cartesian_traits_base< | |
sizeof...(Bases), | |
cartesian_kind::product, | |
read_kind::map, | |
Bases... | |
>; | |
friend flux_sequence_traits::impl; | |
}; | |
struct cartesian_product_map_fn | |
{ | |
template <typename Func, adaptable_sequence Seq0, adaptable_sequence... Seqs> | |
requires (multipass_sequence<Seqs> && ...) && | |
std::regular_invocable<Func&, element_t<Seq0>, element_t<Seqs>...> | |
[[nodiscard]] | |
constexpr auto operator()(Func func, Seq0&& seq0, Seqs&&... seqs) const | |
{ | |
return cartesian_product_map_adaptor<Func, std::decay_t<Seq0>, std::decay_t<Seqs>...>( | |
std::move(func), FLUX_FWD(seq0), FLUX_FWD(seqs)...); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto cartesian_product_map = detail::cartesian_product_map_fn{}; | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_CARTESIAN_PRODUCT_MAP_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_CHAIN_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CHAIN_HPP_INCLUDED | |
#include <tuple> | |
#include <variant> | |
namespace flux { | |
namespace detail { | |
template <typename... Bases> | |
struct chain_adaptor : inline_sequence_base<chain_adaptor<Bases...>> { | |
private: | |
std::tuple<Bases...> bases_; | |
friend struct sequence_traits<chain_adaptor>; | |
template <typename Parent, typename... BaseContexts> | |
struct iteration_context { | |
Parent* parent; | |
std::variant<BaseContexts...> base_context; | |
using element_type = std::common_reference_t<typename BaseContexts::element_type...>; | |
template <std::size_t I> | |
constexpr auto run_while_impl(auto&& pred) -> iteration_result | |
{ | |
if constexpr (I < sizeof...(Bases) - 1) { | |
if (base_context.index() == I) { | |
auto& ctx = std::get<I>(base_context); | |
auto result = flux::run_while(ctx, pred); | |
if (result == iteration_result::incomplete) { | |
return result; | |
} else { | |
base_context.template emplace<I + 1>(emplace_from{ | |
[&] { return flux::iterate(std::get<I + 1>(parent->bases_)); }}); | |
return run_while_impl<I + 1>(pred); | |
} | |
} else { | |
return run_while_impl<I + 1>(pred); | |
} | |
} else { | |
if (base_context.index() == I) { | |
return std::get<I>(base_context).run_while(pred); | |
} else { | |
unreachable(); | |
} | |
} | |
} | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
auto call_with_common_ref | |
= [&pred](auto&& elem) { return pred(static_cast<element_type>(FLUX_FWD(elem))); }; | |
return run_while_impl<0>(call_with_common_ref); | |
} | |
}; | |
public: | |
explicit constexpr chain_adaptor(decays_to<Bases> auto&&... bases) | |
: bases_(FLUX_FWD(bases)...) | |
{ | |
} | |
[[nodiscard]] | |
constexpr auto iterate() | |
{ | |
return iteration_context<chain_adaptor, iteration_context_t<Bases>...>{ | |
.parent = this, | |
.base_context = std::variant<iteration_context_t<Bases>...>( | |
std::in_place_index<0>, | |
emplace_from([&] { return flux::iterate(std::get<0>(bases_)); }))}; | |
} | |
[[nodiscard]] | |
constexpr auto iterate() const | |
requires(iterable<Bases const> && ...) | |
{ | |
return iteration_context<chain_adaptor const, iteration_context_t<Bases const>...>{ | |
.parent = this, | |
.base_context = std::variant<iteration_context_t<Bases const>...>( | |
std::in_place_index<0>, | |
emplace_from([&] { return flux::iterate(std::get<0>(bases_)); }))}; | |
} | |
[[nodiscard]] constexpr auto size() -> int_t | |
requires(sized_iterable<Bases> && ...) | |
{ | |
return std::apply([](auto&... bases) { return (flux::iterable_size(bases) + ...); }, | |
bases_); | |
} | |
[[nodiscard]] constexpr auto size() const -> int_t | |
requires(sized_iterable<Bases const> && ...) | |
{ | |
return std::apply([](auto&... bases) { return (flux::iterable_size(bases) + ...); }, | |
bases_); | |
} | |
}; | |
template <typename... Ts> | |
concept all_have_common_ref = | |
requires { typename std::common_reference_t<Ts...>; } && | |
(std::convertible_to<Ts, std::common_reference_t<Ts...>> && ...); | |
template <typename... Seqs> | |
concept chainable = all_have_common_ref<iterable_element_t<Seqs>...> | |
&& requires { typename std::common_type_t<iterable_value_t<Seqs>...>; }; | |
struct chain_fn { | |
template <adaptable_iterable... Its> | |
requires(sizeof...(Its) >= 1) && chainable<Its...> | |
[[nodiscard]] | |
constexpr auto operator()(Its&&... its) const | |
{ | |
if constexpr (sizeof...(Its) == 1) { | |
return std::forward<Its...>(its...); | |
} else { | |
return chain_adaptor<std::decay_t<Its>...>(FLUX_FWD(its)...); | |
} | |
} | |
}; | |
} // namespace detail | |
template <sequence... Bases> | |
struct sequence_traits<detail::chain_adaptor<Bases...>> : default_sequence_traits { | |
using value_type = std::common_type_t<value_t<Bases>...>; | |
static constexpr bool disable_multipass = !(multipass_sequence<Bases> && ...); | |
static constexpr bool is_infinite = (infinite_sequence<Bases> || ...); | |
private: | |
static constexpr std::size_t End = sizeof...(Bases) - 1; | |
template <typename From, typename To> | |
using const_like_t = std::conditional_t<std::is_const_v<From>, To const, To>; | |
using cursor_type = std::variant<cursor_t<Bases>...>; | |
template <std::size_t N, typename Self> | |
static constexpr auto first_impl(Self& self) -> cursor_type | |
{ | |
auto& base = std::get<N>(self.bases_); | |
auto cur = flux::first(base); | |
if constexpr (N < End) { | |
if (!flux::is_last(base, cur)) { | |
return cursor_type(std::in_place_index<N>, std::move(cur)); | |
} else { | |
return first_impl<N+1>(self); | |
} | |
} else { | |
return cursor_type(std::in_place_index<N>, std::move(cur)); | |
} | |
} | |
template <std::size_t N, typename Self> | |
static constexpr auto inc_impl(Self& self, cursor_type& cur) -> void | |
{ | |
if constexpr (N < End) { | |
if (cur.index() == N) { | |
auto& base = std::get<N>(self.bases_); | |
auto& base_cur = std::get<N>(cur); | |
flux::inc(base, base_cur); | |
if (flux::is_last(base, base_cur)) { | |
cur = first_impl<N + 1>(self); | |
} | |
} else { | |
inc_impl<N+1>(self, cur); | |
} | |
} else { | |
flux::inc(std::get<N>(self.bases_), std::get<N>(cur)); | |
} | |
} | |
template <std::size_t N, typename Self> | |
static constexpr auto dec_impl(Self& self, cursor_type& cur) | |
{ | |
if constexpr (N > 0) { | |
if (cur.index() == N) { | |
auto& base = std::get<N>(self.bases_); | |
auto& base_cur = std::get<N>(cur); | |
if (base_cur == flux::first(base)) { | |
cur = cursor_type(std::in_place_index<N-1>, | |
flux::last(std::get<N-1>(self.bases_))); | |
dec_impl<N-1>(self, cur); | |
} else { | |
flux::dec(base, base_cur); | |
} | |
} else { | |
dec_impl<N-1>(self, cur); | |
} | |
} else { | |
flux::dec(std::get<0>(self.bases_), std::get<0>(cur)); | |
} | |
} | |
template <std::size_t N, typename Self> | |
static constexpr auto read_impl(Self& self, cursor_type const& cur) | |
-> std::common_reference_t<element_t<const_like_t<Self, Bases>>...> | |
{ | |
if constexpr (N < End) { | |
if (cur.index() == N) { | |
return flux::read_at(std::get<N>(self.bases_), std::get<N>(cur)); | |
} else { | |
return read_impl<N+1>(self, cur); | |
} | |
} else { | |
return flux::read_at(std::get<N>(self.bases_), std::get<N>(cur)); | |
} | |
} | |
template <std::size_t N, typename Self> | |
static constexpr auto move_impl(Self& self, cursor_type const& cur) | |
-> std::common_reference_t<rvalue_element_t<const_like_t<Self, Bases>>...> | |
{ | |
if constexpr (N < End) { | |
if (cur.index() == N) { | |
return flux::move_at(std::get<N>(self.bases_), std::get<N>(cur)); | |
} else { | |
return move_impl<N+1>(self, cur); | |
} | |
} else { | |
return flux::move_at(std::get<N>(self.bases_), std::get<N>(cur)); | |
} | |
} | |
template <std::size_t N, typename Self> | |
static constexpr auto for_each_while_impl(Self& self, auto& pred) | |
-> cursor_type | |
{ | |
if constexpr (N < End) { | |
auto& base = std::get<N>(self.bases_); | |
auto base_cur = flux::seq_for_each_while(base, pred); | |
if (!flux::is_last(base, base_cur)) { | |
return cursor_type(std::in_place_index<N>, std::move(base_cur)); | |
} else { | |
return for_each_while_impl<N+1>(self, pred); | |
} | |
} else { | |
return cursor_type(std::in_place_index<N>, | |
flux::seq_for_each_while(std::get<N>(self.bases_), pred)); | |
} | |
} | |
template <std::size_t N, typename Self> | |
static constexpr auto distance_impl(Self& self, | |
cursor_type const& from, | |
cursor_type const& to) | |
{ | |
if constexpr (N < End) { | |
if (N < from.index()) { | |
return distance_impl<N+1>(self, from, to); | |
} | |
FLUX_DEBUG_ASSERT(N == from.index()); | |
if (N == to.index()) { | |
return flux::distance(std::get<N>(self.bases_), | |
std::get<N>(from), std::get<N>(to)); | |
} else { | |
auto dist_to_end = flux::distance(std::get<N>(self.bases_), | |
std::get<N>(from), | |
flux::last(std::get<N>(self.bases_))); | |
auto remaining = distance_impl<N+1>(self, first_impl<N+1>(self), to); | |
return dist_to_end + remaining; | |
} | |
} else { | |
FLUX_DEBUG_ASSERT(N == from.index() && N == to.index()); | |
return flux::distance(std::get<N>(self.bases_), std::get<N>(from), std::get<N>(to)); | |
} | |
} | |
template <std::size_t N, typename Self> | |
static constexpr auto inc_ra_impl(Self& self, cursor_type& cur, int_t offset) -> cursor_type& | |
{ | |
if constexpr (N < End) { | |
if (N < cur.index()) { | |
return inc_ra_impl<N+1>(self, cur, offset); | |
} | |
FLUX_DEBUG_ASSERT(cur.index() == N); | |
auto& base = std::get<N>(self.bases_); | |
auto& base_cur = std::get<N>(cur); | |
auto dist = flux::distance(base, base_cur, flux::last(base)); | |
if (offset < dist) { | |
flux::inc(base, base_cur, offset); | |
return cur; | |
} else { | |
cur = first_impl<N+1>(self); | |
offset -= dist; | |
return inc_ra_impl<N+1>(self, cur, offset); | |
} | |
} else { | |
FLUX_DEBUG_ASSERT(cur.index() == N); | |
flux::inc(std::get<N>(self.bases_), std::get<N>(cur), offset); | |
return cur; | |
} | |
} | |
public: | |
template <typename Self> | |
static constexpr auto first(Self& self) -> cursor_type | |
{ | |
return first_impl<0>(self); | |
} | |
template <typename Self> | |
static constexpr auto is_last(Self& self, cursor_type const& cur) -> bool | |
{ | |
return cur.index() == End && | |
flux::is_last(std::get<End>(self.bases_), std::get<End>(cur)); | |
} | |
template <typename Self> | |
static constexpr auto read_at(Self& self, cursor_type const& cur) | |
-> std::common_reference_t<element_t<const_like_t<Self, Bases>>...> | |
{ | |
return read_impl<0>(self, cur); | |
} | |
template <typename Self> | |
static constexpr auto move_at(Self& self, cursor_type const& cur) | |
-> std::common_reference_t<rvalue_element_t<const_like_t<Self, Bases>>...> | |
{ | |
return move_impl<0>(self, cur); | |
} | |
template <typename Self> | |
static constexpr auto inc(Self& self, cursor_type& cur) -> void | |
{ | |
inc_impl<0>(self, cur); | |
} | |
template <typename Self> | |
static constexpr auto dec(Self& self, cursor_type& cur) -> void | |
requires (bidirectional_sequence<const_like_t<Self, Bases>> && ...) && | |
(bounded_sequence<const_like_t<Self,Bases>> &&...) | |
{ | |
dec_impl<End>(self, cur); | |
} | |
template <typename Self> | |
static constexpr auto last(Self& self) -> cursor_type | |
requires bounded_sequence<decltype(std::get<End>(self.bases_))> | |
{ | |
return cursor_type(std::in_place_index<End>, flux::last(std::get<End>(self.bases_))); | |
} | |
template <typename Self> | |
static constexpr auto size(Self& self) | |
requires (sized_sequence<const_like_t<Self, Bases>> && ...) | |
{ | |
return std::apply([](auto&... bases) { return (flux::size(bases) + ...); }, | |
self.bases_); | |
} | |
template <typename Self> | |
static constexpr auto for_each_while(Self& self, auto&& pred) | |
{ | |
return for_each_while_impl<0>(self, pred); | |
} | |
template <typename Self> | |
static constexpr auto distance(Self& self, cursor_type const& from, cursor_type const& to) | |
-> int_t | |
requires(random_access_sequence<const_like_t<Self, Bases>> && ...) | |
&& (bounded_sequence<const_like_t<Self, Bases>> && ...) | |
{ | |
if (from.index() <= to.index()) { | |
return distance_impl<0>(self, from, to); | |
} else { | |
return -distance_impl<0>(self, to, from); | |
} | |
} | |
template <typename Self> | |
static constexpr auto inc(Self& self, cursor_type& cur, int_t offset) | |
requires(random_access_sequence<const_like_t<Self, Bases>> && ...) | |
&& (bounded_sequence<const_like_t<Self, Bases>> && ...) | |
{ | |
inc_ra_impl<0>(self, cur, offset); | |
} | |
}; | |
FLUX_EXPORT inline constexpr auto chain = detail::chain_fn{}; | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_CHAIN_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_CHUNK_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CHUNK_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_STRIDE_HPP_INCLUDED | |
#define FLUX_ADAPTOR_STRIDE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
// This is a Flux-ified version of ranges::advance. | |
inline constexpr struct advance_fn { | |
template <sequence Seq> | |
constexpr auto operator()(Seq& seq, cursor_t<Seq>& cur, int_t offset) const -> int_t | |
{ | |
if (offset > 0) { | |
int_t counter = 0; | |
while (offset-- > 0 && !flux::is_last(seq, cur)) { | |
flux::inc(seq, cur); | |
++counter; | |
} | |
return counter; | |
} else if (offset < 0) { | |
if constexpr (bidirectional_sequence<Seq>) { | |
auto const fst = flux::first(seq); | |
while (offset < 0) { | |
if (flux::dec(seq, cur) == fst) { | |
break; | |
} | |
++offset; | |
} | |
return offset; | |
} else { | |
runtime_error("advance() called with negative offset and non-bidirectional sequence"); | |
} | |
} else { | |
return 0; | |
} | |
} | |
template <random_access_sequence Seq> | |
requires bounded_sequence<Seq> | |
constexpr auto operator()(Seq& seq, cursor_t<Seq>& cur, int_t offset) const -> int_t | |
{ | |
if (offset > 0) { | |
auto dist = (cmp::min)(flux::distance(seq, cur, flux::last(seq)), offset); | |
flux::inc(seq, cur, dist); | |
return num::sub(offset, dist); | |
} else if (offset < 0) { | |
auto dist = num::neg((cmp::min)(flux::distance(seq, flux::first(seq), cur), | |
num::neg(offset))); | |
flux::inc(seq, cur, dist); | |
return num::sub(offset, dist); | |
} else { | |
return 0; | |
} | |
} | |
} advance; | |
template <typename BaseCtx> | |
struct stride_iteration_context : immovable { | |
BaseCtx base_ctx; | |
int_t stride; | |
int_t skip = 0; | |
using element_type = context_element_t<BaseCtx>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
return base_ctx.run_while([&](auto&& elem) { | |
if (skip > 0) { | |
--skip; | |
return loop_continue; | |
} else { | |
skip = stride - 1; | |
return std::invoke(pred, FLUX_FWD(elem)); | |
} | |
}); | |
} | |
}; | |
template <typename Base> | |
struct stride_adaptor : inline_sequence_base<stride_adaptor<Base>> { | |
private: | |
Base base_; | |
int_t stride_; | |
friend struct sequence_traits<stride_adaptor>; | |
public: | |
constexpr stride_adaptor(decays_to<Base> auto&& base, int_t stride) | |
: base_(FLUX_FWD(base)), | |
stride_(stride) | |
{} | |
constexpr auto base() & -> Base& { return base_; } | |
constexpr auto base() const& -> Base const& { return base_; } | |
[[nodiscard]] constexpr auto iterate() | |
{ | |
return stride_iteration_context<iteration_context_t<Base>>{.base_ctx = flux::iterate(base_), | |
.stride = stride_}; | |
} | |
[[nodiscard]] constexpr auto iterate() const | |
requires iterable<Base const> | |
{ | |
return stride_iteration_context<iteration_context_t<Base const>>{ | |
.base_ctx = flux::iterate(base_), .stride = stride_}; | |
} | |
[[nodiscard]] constexpr auto reverse_iterate() | |
requires reverse_iterable<Base> && sized_iterable<Base> | |
{ | |
int_t initial_skip | |
= stride_ - 1 - (stride_ - flux::iterable_size(base_) % stride_) % stride_; | |
return stride_iteration_context<reverse_iteration_context_t<Base>>{ | |
.base_ctx = flux::reverse_iterate(base_), .stride = stride_, .skip = initial_skip}; | |
} | |
[[nodiscard]] constexpr auto reverse_iterate() const | |
requires reverse_iterable<Base const> && sized_iterable<Base const> | |
{ | |
int_t initial_skip | |
= stride_ - 1 - (stride_ - flux::iterable_size(base_) % stride_) % stride_; | |
return stride_iteration_context<reverse_iteration_context_t<Base const>>{ | |
.base_ctx = flux::reverse_iterate(base_), .stride = stride_, .skip = initial_skip}; | |
} | |
[[nodiscard]] constexpr auto size() -> int_t | |
requires sized_iterable<Base> | |
{ | |
int_t sz = flux::iterable_size(base_); | |
return sz / stride_ + (sz % stride_ == 0 ? 0 : 1); | |
} | |
[[nodiscard]] constexpr auto size() const -> int_t | |
requires sized_iterable<Base const> | |
{ | |
int_t sz = flux::iterable_size(base_); | |
return sz / stride_ + (sz % stride_ == 0 ? 0 : 1); | |
} | |
}; | |
} // namespace detail | |
template <sequence Base> | |
struct sequence_traits<detail::stride_adaptor<Base>> : detail::passthrough_traits_base { | |
using value_type = value_t<Base>; | |
static inline constexpr bool is_infinite = infinite_sequence<Base>; | |
static constexpr auto inc(auto& self, cursor_t<Base>& cur) -> void | |
{ | |
detail::advance(self.base(), cur, self.stride_); | |
} | |
// This version of stride is never bidir | |
static void dec(...) = delete; | |
static constexpr auto size(auto& self) -> int_t | |
requires sized_sequence<Base> | |
{ | |
auto s = flux::size(self.base_); | |
return s / self.stride_ + (s % self.stride_ == 0 ? 0 : 1); | |
} | |
static constexpr auto for_each_while(auto& self, auto&& pred) -> cursor_t<Base> | |
requires sequence<decltype((self.base_))> | |
{ | |
int_t n = self.stride_; | |
return flux::seq_for_each_while(self.base_, [&n, &pred, s = self.stride_](auto&& elem) { | |
if (++n < s) { | |
return true; | |
} else { | |
n = 0; | |
return std::invoke(pred, FLUX_FWD(elem)); | |
} | |
}); | |
} | |
}; | |
template <bidirectional_sequence Base> | |
struct sequence_traits<detail::stride_adaptor<Base>> : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> cur{}; | |
int_t missing = 0; | |
friend constexpr auto operator==(cursor_type const& lhs, cursor_type const& rhs) -> bool | |
{ | |
return lhs.cur == rhs.cur; | |
} | |
friend constexpr auto operator<=>(cursor_type const& lhs, cursor_type const& rhs) | |
-> std::strong_ordering | |
requires ordered_cursor<cursor_t<Base>> | |
{ | |
return lhs.cur <=> rhs.cur; | |
} | |
}; | |
public: | |
using value_type = value_t<Base>; | |
static constexpr bool is_infinite = infinite_sequence<Base>; | |
static constexpr auto first(auto& self) -> cursor_type | |
{ | |
return cursor_type{.cur = flux::first(self.base_), .missing = 0}; | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.cur); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) -> void | |
{ | |
cur.missing = detail::advance(self.base_, cur.cur, self.stride_); | |
} | |
static constexpr auto read_at(auto& self, cursor_type const& cur) | |
-> decltype(flux::read_at(self.base_, cur.cur)) | |
{ | |
return flux::read_at(self.base_, cur.cur); | |
} | |
static constexpr auto move_at(auto& self, cursor_type const& cur) | |
-> decltype(flux::move_at(self.base_, cur.cur)) | |
{ | |
return flux::move_at(self.base_, cur.cur); | |
} | |
static constexpr auto read_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(flux::read_at_unchecked(self.base_, cur.cur)) | |
{ | |
return flux::read_at_unchecked(self.base_, cur.cur); | |
} | |
static constexpr auto move_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(flux::move_at_unchecked(self.base_, cur.cur)) | |
{ | |
return flux::move_at_unchecked(self.base_, cur.cur); | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
requires bounded_sequence<Base> && sized_sequence<Base> | |
{ | |
int_t missing = (self.stride_ - flux::size(self.base_) % self.stride_) % self.stride_; | |
return cursor_type{.cur = flux::last(self.base_), .missing = missing}; | |
} | |
static constexpr auto dec(auto& self, cursor_type& cur) -> void | |
requires bidirectional_sequence<Base> | |
{ | |
detail::advance(self.base_, cur.cur, cur.missing - self.stride_); | |
cur.missing = 0; | |
} | |
static constexpr auto size(auto& self) -> int_t | |
requires sized_sequence<Base> | |
{ | |
auto s = flux::size(self.base_); | |
return s / self.stride_ + (s % self.stride_ == 0 ? 0 : 1); | |
} | |
static constexpr auto distance(auto& self, cursor_type const& from, cursor_type const& to) | |
-> int_t | |
requires random_access_sequence<Base> | |
{ | |
return (flux::distance(self.base_, from.cur, to.cur) - from.missing + to.missing) | |
/ self.stride_; | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur, int_t offset) -> void | |
requires random_access_sequence<Base> | |
{ | |
if (offset > 0) { | |
cur.missing = num::mod( | |
detail::advance(self.base_, cur.cur, num::mul(offset, self.stride_)), self.stride_); | |
} else if (offset < 0) { | |
detail::advance(self.base_, cur.cur, | |
num::add(num::mul(offset, self.stride_), cur.missing)); | |
cur.missing = 0; | |
} | |
} | |
static constexpr auto for_each_while(auto& self, auto&& pred) -> cursor_type | |
requires sequence<decltype((self.base_))> | |
{ | |
int_t n = self.stride_; | |
auto c = flux::seq_for_each_while(self.base_, [&n, &pred, s = self.stride_](auto&& elem) { | |
if (++n < s) { | |
return true; | |
} else { | |
n = 0; | |
return std::invoke(pred, FLUX_FWD(elem)); | |
} | |
}); | |
return cursor_type{std::move(c), (n + 1) % self.stride_}; | |
} | |
}; | |
namespace detail { | |
struct stride_fn { | |
template <adaptable_iterable It> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, num::integral auto by) const | |
{ | |
FLUX_ASSERT(by > 0); | |
return stride_adaptor<std::decay_t<It>>(FLUX_FWD(it), num::checked_cast<int_t>(by)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto stride = detail::stride_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::stride(num::integral auto by) && | |
{ | |
return flux::stride(std::move(derived()), by); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_STRIDE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_TAKE_HPP_INCLUDED | |
#define FLUX_ADAPTOR_TAKE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_DROP_HPP_INCLUDED | |
#define FLUX_ADAPTOR_DROP_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename BaseCtx> | |
struct drop_iteration_context : immovable { | |
BaseCtx base_ctx; | |
int_t remaining; | |
using element_type = context_element_t<BaseCtx>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
return base_ctx.run_while([&](auto&& elem) { | |
if (remaining > 0) { | |
--remaining; | |
return loop_continue; | |
} else { | |
return pred(FLUX_FWD(elem)); | |
} | |
}); | |
} | |
}; | |
template <typename BaseCtx> | |
struct take_iteration_context : immovable { | |
BaseCtx base_ctx; | |
int_t remaining; | |
using element_type = context_element_t<BaseCtx>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
if (remaining > 0) { | |
return base_ctx.run_while([&](auto&& elem) { | |
--remaining; | |
return pred(FLUX_FWD(elem)) && (remaining > 0); | |
}); | |
} else { | |
return iteration_result::complete; | |
} | |
} | |
}; | |
template <typename Base> | |
struct drop_adaptor : inline_sequence_base<drop_adaptor<Base>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
int_t count_; | |
public: | |
constexpr drop_adaptor(decays_to<Base> auto&& base, int_t count) | |
: base_(FLUX_FWD(base)), | |
count_(count) | |
{ | |
} | |
constexpr Base& base() & { return base_; } | |
constexpr Base const& base() const& { return base_; } | |
[[nodiscard]] constexpr auto iterate() | |
{ | |
return drop_iteration_context<iteration_context_t<Base>>{.base_ctx = flux::iterate(base_), | |
.remaining = count_}; | |
} | |
[[nodiscard]] constexpr auto iterate() const | |
requires iterable<Base const> | |
{ | |
return drop_iteration_context<iteration_context_t<Base const>>{ | |
.base_ctx = flux::iterate(base_), .remaining = count_}; | |
} | |
[[nodiscard]] constexpr auto reverse_iterate() | |
requires reverse_iterable<Base> && sized_iterable<Base> | |
{ | |
return take_iteration_context<reverse_iteration_context_t<Base>>{ | |
.base_ctx = flux::reverse_iterate(base_), .remaining = size()}; | |
} | |
[[nodiscard]] constexpr auto reverse_iterate() const | |
requires reverse_iterable<Base const> && sized_iterable<Base const> | |
{ | |
return take_iteration_context<reverse_iteration_context_t<Base const>>{ | |
.base_ctx = flux::reverse_iterate(base_), .remaining = size()}; | |
} | |
[[nodiscard]] constexpr auto size() -> int_t | |
requires sized_iterable<Base> | |
{ | |
auto sz = flux::iterable_size(base_); | |
return sz > count_ ? sz - count_ : 0; | |
} | |
[[nodiscard]] constexpr auto size() const -> int_t | |
requires sized_iterable<Base const> | |
{ | |
auto sz = flux::iterable_size(base_); | |
return sz > count_ ? sz - count_ : 0; | |
} | |
struct flux_sequence_traits : passthrough_traits_base { | |
using value_type = value_t<Base>; | |
static constexpr bool disable_multipass = !multipass_sequence<Base>; | |
static constexpr auto first(auto& self) -> cursor_t<Base> | |
{ | |
auto cur = flux::first(self.base_); | |
detail::advance(self.base_, cur, self.count_); | |
return cur; | |
} | |
static constexpr auto size(auto& self) | |
requires sized_sequence<Base> | |
{ | |
return (cmp::max)(num::sub(flux::size(self.base()), self.count_), int_t{0}); | |
} | |
static constexpr auto data(auto& self) | |
requires contiguous_sequence<Base> && sized_sequence<Base> | |
{ | |
return flux::data(self.base()) + (cmp::min)(self.count_, flux::size(self.base_)); | |
} | |
static constexpr auto for_each_while(auto& self, auto&& pred) -> cursor_t<Base> | |
{ | |
return default_sequence_traits::for_each_while(self, FLUX_FWD(pred)); | |
} | |
}; | |
}; | |
struct drop_fn { | |
template <adaptable_iterable It> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, num::integral auto count) const | |
{ | |
auto count_ = num::checked_cast<int_t>(count); | |
if (count_ < 0) { | |
runtime_error("Negative argument passed to drop()"); | |
} | |
return drop_adaptor<std::decay_t<It>>(FLUX_FWD(it), count_); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto drop = detail::drop_fn{}; | |
template <typename Derived> | |
constexpr auto inline_sequence_base<Derived>::drop(num::integral auto count) && | |
{ | |
return flux::drop(std::move(derived()), count); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_DROP_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename Base> | |
struct take_adaptor : inline_sequence_base<take_adaptor<Base>> { | |
private: | |
Base base_; | |
int_t count_; | |
public: | |
constexpr take_adaptor(decays_to<Base> auto&& base, int_t count) | |
: base_(FLUX_FWD(base)), | |
count_(count) | |
{} | |
[[nodiscard]] constexpr auto base() const& -> Base const& { return base_; } | |
[[nodiscard]] constexpr auto base() && -> Base&& { return std::move(base_); } | |
[[nodiscard]] constexpr auto iterate() | |
{ | |
return take_iteration_context<iteration_context_t<Base>>{.base_ctx = flux::iterate(base_), | |
.remaining = count_}; | |
} | |
[[nodiscard]] constexpr auto iterate() const | |
requires iterable<Base const> | |
{ | |
return take_iteration_context<iteration_context_t<Base const>>{ | |
.base_ctx = flux::iterate(base_), .remaining = count_}; | |
} | |
[[nodiscard]] constexpr auto reverse_iterate() | |
requires reverse_iterable<Base> && sized_iterable<Base> | |
{ | |
int_t sz = flux::iterable_size(base_); | |
return drop_iteration_context<reverse_iteration_context_t<Base>>{ | |
.base_ctx = flux::reverse_iterate(base_), .remaining = sz > count_ ? sz - count_ : 0}; | |
} | |
[[nodiscard]] constexpr auto reverse_iterate() const | |
requires reverse_iterable<Base const> && sized_iterable<Base const> | |
{ | |
int_t sz = flux::iterable_size(base_); | |
return drop_iteration_context<reverse_iteration_context_t<Base const>>{ | |
.base_ctx = flux::reverse_iterate(base_), .remaining = sz > count_ ? sz - count_ : 0}; | |
} | |
[[nodiscard]] constexpr auto size() -> int_t | |
requires sized_iterable<Base> | |
{ | |
return (cmp::min)(flux::iterable_size(base_), count_); | |
} | |
[[nodiscard]] constexpr auto size() const -> int_t | |
requires sized_iterable<Base const> | |
{ | |
return (cmp::min)(flux::iterable_size(base_), count_); | |
} | |
struct flux_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> base_cur; | |
int_t length; | |
friend bool operator==(cursor_type const&, cursor_type const&) = default; | |
friend auto operator<=>(cursor_type const& lhs, cursor_type const& rhs) = default; | |
}; | |
public: | |
using value_type = value_t<Base>; | |
static constexpr auto first(auto& self) -> cursor_type | |
{ | |
return cursor_type{.base_cur = flux::first(self.base_), .length = self.count_}; | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) -> bool | |
{ | |
return cur.length <= 0 || flux::is_last(self.base_, cur.base_cur); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) | |
{ | |
flux::inc(self.base_, cur.base_cur); | |
cur.length = num::sub(cur.length, int_t{1}); | |
} | |
static constexpr auto read_at(auto& self, cursor_type const& cur) | |
-> decltype(flux::read_at(self.base_, cur.base_cur)) | |
{ | |
return flux::read_at(self.base_, cur.base_cur); | |
} | |
static constexpr auto move_at(auto& self, cursor_type const& cur) | |
-> decltype(flux::move_at(self.base_, cur.base_cur)) | |
{ | |
return flux::move_at(self.base_, cur.base_cur); | |
} | |
static constexpr auto read_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(flux::read_at_unchecked(self.base_, cur.base_cur)) | |
{ | |
return flux::read_at_unchecked(self.base_, cur.base_cur); | |
} | |
static constexpr auto move_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(flux::move_at_unchecked(self.base_, cur.base_cur)) | |
{ | |
return flux::move_at_unchecked(self.base_, cur.base_cur); | |
} | |
static constexpr auto dec(auto& self, cursor_type& cur) | |
requires bidirectional_sequence<Base> | |
{ | |
flux::dec(self.base_, cur.base_cur); | |
cur.length = num::add(cur.length, int_t{1}); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur, int_t offset) | |
requires random_access_sequence<Base> | |
{ | |
flux::inc(self.base_, cur.base_cur, offset); | |
cur.length = num::sub(cur.length, offset); | |
} | |
static constexpr auto distance(auto& self, cursor_type const& from, cursor_type const& to) | |
-> int_t | |
requires random_access_sequence<Base> | |
{ | |
return (cmp::min)(flux::distance(self.base_, from.base_cur, to.base_cur), | |
num::sub(from.length, to.length)); | |
} | |
static constexpr auto data(auto& self) -> decltype(flux::data(self.base_)) | |
requires contiguous_sequence<Base> | |
{ | |
return flux::data(self.base_); | |
} | |
static constexpr auto size(auto& self) | |
requires sized_sequence<Base> || infinite_sequence<Base> | |
{ | |
if constexpr (infinite_sequence<Base>) { | |
return self.count_; | |
} else { | |
return (cmp::min)(flux::size(self.base_), self.count_); | |
} | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
requires(random_access_sequence<Base> && sized_sequence<Base>) | |
|| infinite_sequence<Base> | |
{ | |
return cursor_type{.base_cur | |
= flux::next(self.base_, flux::first(self.base_), size(self)), | |
.length = 0}; | |
} | |
static constexpr auto for_each_while(auto& self, auto&& pred) -> cursor_type | |
{ | |
int_t len = self.count_; | |
auto cur = flux::seq_for_each_while(self.base_, [&](auto&& elem) { | |
return (len-- > 0) && std::invoke(pred, FLUX_FWD(elem)); | |
}); | |
return cursor_type{.base_cur = std::move(cur), .length = ++len}; | |
} | |
}; | |
}; | |
struct take_fn { | |
template <adaptable_iterable It> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, num::integral auto count) const | |
{ | |
auto count_ = num::checked_cast<int_t>(count); | |
if (count_ < 0) { | |
runtime_error("Negative argument passed to take()"); | |
} | |
return take_adaptor<std::decay_t<It>>(FLUX_FWD(it), count_); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto take = detail::take_fn{}; | |
template <typename Derived> | |
constexpr auto inline_sequence_base<Derived>::take(num::integral auto count) && | |
{ | |
return flux::take(std::move(derived()), count); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_TAKE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename BaseCtx> | |
struct chunk_iteration_context : immovable { | |
BaseCtx base_ctx; | |
using OptElem = decltype(next_element(base_ctx)); | |
int_t chunk_sz; | |
OptElem elem = nullopt; | |
int_t remaining = 0; | |
bool base_done = false; | |
struct inner_iterable { | |
chunk_iteration_context* outer_ctx; | |
struct inner_context_type : immovable { | |
chunk_iteration_context* outer_ctx; | |
using element_type = context_element_t<BaseCtx>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
while (true) { | |
if (outer_ctx->remaining == 0) { | |
return iteration_result::complete; | |
} | |
if (!outer_ctx->elem) { | |
if (!outer_ctx->read_next()) { | |
return iteration_result::complete; | |
} | |
} | |
--outer_ctx->remaining; | |
auto r = pred(*std::move(outer_ctx->elem)); | |
outer_ctx->elem.reset(); | |
if (!r) { | |
return iteration_result::incomplete; | |
} | |
} | |
} | |
}; | |
constexpr auto iterate() const -> inner_context_type | |
{ | |
return inner_context_type{.outer_ctx = this->outer_ctx}; | |
} | |
}; | |
constexpr bool read_next() | |
{ | |
elem = flux::next_element(base_ctx); | |
return elem.has_value(); | |
} | |
using element_type = inner_iterable; | |
constexpr auto run_while(auto&& outer_pred) -> iteration_result | |
{ | |
while (true) { | |
while (remaining-- > 0) { | |
if (!read_next()) { | |
return iteration_result::complete; | |
} | |
} | |
if (!read_next()) { | |
return iteration_result::complete; | |
} | |
remaining = chunk_sz; | |
if (!outer_pred(inner_iterable{.outer_ctx = this})) { | |
return iteration_result::incomplete; | |
} | |
} | |
} | |
}; | |
template <typename Base> | |
struct chunk_adaptor : inline_sequence_base<chunk_adaptor<Base>> { | |
private: | |
Base base_; | |
int_t chunk_sz_; | |
public: | |
constexpr chunk_adaptor(decays_to<Base> auto&& base, int_t chunk_sz) | |
: base_(FLUX_FWD(base)), | |
chunk_sz_(chunk_sz) | |
{ | |
} | |
constexpr auto iterate() | |
{ | |
return detail::chunk_iteration_context<iteration_context_t<Base>>{ | |
.base_ctx = flux::iterate(base_), .chunk_sz = chunk_sz_}; | |
} | |
constexpr auto iterate() const | |
requires iterable<Base const> | |
{ | |
return detail::chunk_iteration_context<iteration_context_t<Base const>>{ | |
.base_ctx = flux::iterate(base_), .chunk_sz = chunk_sz_}; | |
} | |
constexpr auto size() -> int_t | |
requires sized_iterable<Base> | |
{ | |
auto sz = flux::iterable_size(base_); | |
return sz / chunk_sz_ + (sz % chunk_sz_ == 0 ? 0 : 1); | |
} | |
constexpr auto size() const -> int_t | |
requires sized_iterable<Base const> | |
{ | |
auto sz = flux::iterable_size(base_); | |
return sz / chunk_sz_ + (sz % chunk_sz_ == 0 ? 0 : 1); | |
} | |
}; | |
template <sequence Base> | |
struct chunk_adaptor<Base> : inline_sequence_base<chunk_adaptor<Base>> { | |
private: | |
Base base_; | |
int_t chunk_sz_; | |
optional<cursor_t<Base>> cur_ = nullopt; | |
int_t rem_ = chunk_sz_; | |
public: | |
constexpr chunk_adaptor(decays_to<Base> auto&& base, int_t chunk_sz) | |
: base_(FLUX_FWD(base)), | |
chunk_sz_(chunk_sz) | |
{} | |
chunk_adaptor(chunk_adaptor&&) = default; | |
chunk_adaptor& operator=(chunk_adaptor&&) = default; | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct outer_cursor { | |
outer_cursor(outer_cursor&&) = default; | |
outer_cursor& operator=(outer_cursor&&) = default; | |
friend struct flux_sequence_traits; | |
private: | |
explicit outer_cursor() = default; | |
}; | |
using self_t = chunk_adaptor; | |
public: | |
struct inner_sequence : inline_sequence_base<inner_sequence> { | |
private: | |
chunk_adaptor* parent_; | |
constexpr explicit inner_sequence(chunk_adaptor& parent) | |
: parent_(std::addressof(parent)) | |
{} | |
friend struct self_t::flux_sequence_traits; | |
public: | |
inner_sequence(inner_sequence&&) = default; | |
inner_sequence& operator=(inner_sequence&&) = default; | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct inner_cursor { | |
inner_cursor(inner_cursor&&) = default; | |
inner_cursor& operator=(inner_cursor&&) = default; | |
friend struct inner_sequence::flux_sequence_traits; | |
private: | |
explicit inner_cursor() = default; | |
}; | |
public: | |
static constexpr auto first(inner_sequence&) -> inner_cursor | |
{ | |
return inner_cursor{}; | |
} | |
static constexpr auto is_last(inner_sequence& self, inner_cursor const&) -> bool | |
{ | |
return self.parent_->rem_ == 0; | |
} | |
static constexpr auto inc(inner_sequence& self, inner_cursor&) -> void | |
{ | |
flux::inc(self.parent_->base_, *self.parent_->cur_); | |
if (flux::is_last(self.parent_->base_, *self.parent_->cur_)) { | |
self.parent_->rem_ = 0; | |
} else { | |
--self.parent_->rem_; | |
} | |
} | |
static constexpr auto read_at(inner_sequence& self, inner_cursor const&) | |
-> element_t<Base> | |
{ | |
return flux::read_at(self.parent_->base_, *self.parent_->cur_); | |
} | |
}; | |
}; | |
static constexpr auto first(self_t& self) -> outer_cursor | |
{ | |
self.cur_ = optional<cursor_t<Base>>(flux::first(self.base_)); | |
self.rem_ = self.chunk_sz_; | |
return outer_cursor{}; | |
} | |
static constexpr auto is_last(self_t& self, outer_cursor const&) -> bool | |
{ | |
return self.rem_ != 0 && flux::is_last(self.base_, *self.cur_); | |
} | |
static constexpr auto inc(self_t& self, outer_cursor&) -> void | |
{ | |
advance(self.base_, *self.cur_, self.rem_); | |
self.rem_ = self.chunk_sz_; | |
} | |
static constexpr auto read_at(self_t& self, outer_cursor const&) -> inner_sequence | |
{ | |
return inner_sequence(self); | |
} | |
static constexpr auto size(self_t& self) -> int_t | |
requires sized_sequence<Base> | |
{ | |
auto s = flux::size(self.base_); | |
return s/self.chunk_sz_ + (s % self.chunk_sz_ == 0 ? 0 : 1); | |
} | |
}; | |
}; | |
template <multipass_sequence Base> | |
struct chunk_adaptor<Base> : inline_sequence_base<chunk_adaptor<Base>> { | |
private: | |
Base base_; | |
int_t chunk_sz_; | |
public: | |
constexpr chunk_adaptor(decays_to<Base> auto&& base, int_t chunk_sz) | |
: base_(FLUX_FWD(base)), | |
chunk_sz_(chunk_sz) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
static inline constexpr bool is_infinite = infinite_sequence<Base>; | |
static constexpr auto first(auto& self) -> cursor_t<Base> | |
{ | |
return flux::first(self.base_); | |
} | |
static constexpr auto is_last(auto& self, cursor_t<Base> const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur); | |
} | |
static constexpr auto inc(auto& self, cursor_t<Base>& cur) -> void | |
{ | |
advance(self.base_, cur, self.chunk_sz_); | |
} | |
static constexpr auto read_at(auto& self, cursor_t<Base> const& cur) | |
-> decltype(flux::take(flux::slice(self.base_, cur, flux::last), self.chunk_sz_)) | |
requires multipass_sequence<decltype((self.base_))> | |
{ | |
return flux::take(flux::slice(self.base_, cur, flux::last), self.chunk_sz_); | |
} | |
static constexpr auto last(auto& self) -> cursor_t<Base> | |
requires bounded_sequence<Base> | |
{ | |
return flux::last(self.base_); | |
} | |
static constexpr auto size(auto& self) -> int_t | |
requires sized_sequence<Base> | |
{ | |
auto s = flux::size(self.base_); | |
return s/self.chunk_sz_ + (s % self.chunk_sz_ == 0 ? 0 : 1); | |
} | |
}; | |
}; | |
template <bidirectional_sequence Base> | |
struct chunk_adaptor<Base> : inline_sequence_base<chunk_adaptor<Base>> { | |
private: | |
Base base_; | |
int_t chunk_sz_; | |
public: | |
constexpr chunk_adaptor(decays_to<Base> auto&& base, int_t chunk_sz) | |
: base_(FLUX_FWD(base)), | |
chunk_sz_(chunk_sz) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> cur{}; | |
int_t missing = 0; | |
friend constexpr auto operator==(cursor_type const& lhs, cursor_type const& rhs) -> bool | |
{ | |
return lhs.cur == rhs.cur; | |
} | |
friend constexpr auto operator<=>(cursor_type const& lhs, cursor_type const& rhs) | |
-> std::strong_ordering | |
requires ordered_cursor<cursor_t<Base>> | |
{ | |
return lhs.cur <=> rhs.cur; | |
} | |
}; | |
public: | |
static inline constexpr bool is_infinite = infinite_sequence<Base>; | |
static constexpr auto first(auto& self) -> cursor_type | |
{ | |
return cursor_type{ | |
.cur = flux::first(self.base_), | |
.missing = 0 | |
}; | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.cur); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) -> void | |
{ | |
cur.missing = advance(self.base_, cur.cur, self.chunk_sz_); | |
} | |
static constexpr auto read_at(auto& self, cursor_type const& cur) | |
requires sequence<decltype((self.base_))> | |
{ | |
if constexpr (random_access_sequence<Base>) { | |
auto end_cur = cur.cur; | |
advance(self.base_, end_cur, self.chunk_sz_); | |
return flux::slice(self.base_, cur.cur, std::move(end_cur)); | |
} else { | |
return flux::take(flux::slice(self.base_, cur.cur, flux::last), self.chunk_sz_); | |
} | |
} | |
static constexpr auto dec(auto& self, cursor_type& cur) | |
{ | |
advance(self.base_, cur.cur, cur.missing - self.chunk_sz_); | |
cur.missing = 0; | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
requires bounded_sequence<Base> && sized_sequence<Base> | |
{ | |
int_t missing | |
= (self.chunk_sz_ - flux::size(self.base_) % self.chunk_sz_) % self.chunk_sz_; | |
return cursor_type{ | |
.cur = flux::last(self.base_), | |
.missing = missing | |
}; | |
} | |
static constexpr auto size(auto& self) -> int_t | |
requires sized_sequence<Base> | |
{ | |
auto s = flux::size(self.base_); | |
return s/self.chunk_sz_ + (s % self.chunk_sz_ == 0 ? 0 : 1); | |
} | |
static constexpr auto distance(auto& self, cursor_type const& from, cursor_type const& to) | |
-> int_t | |
requires random_access_sequence<Base> | |
{ | |
return (flux::distance(self.base_, from.cur, to.cur) - from.missing + to.missing)/self.chunk_sz_; | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur, int_t offset) -> void | |
requires random_access_sequence<Base> | |
{ | |
if (offset > 0) { | |
cur.missing = advance(self.base_, cur.cur, num::mul(offset, self.chunk_sz_)) % self.chunk_sz_; | |
} else if (offset < 0) { | |
advance(self.base_, cur.cur, num::add(num::mul(offset, self.chunk_sz_), cur.missing)); | |
cur.missing = 0; | |
} | |
} | |
}; | |
}; | |
struct chunk_fn { | |
template <adaptable_iterable It> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, num::integral auto chunk_sz) const //-> iterable auto | |
{ | |
int_t chunk_sz_ = num::checked_cast<int_t>(chunk_sz); | |
FLUX_ASSERT(chunk_sz_ > 0); | |
return chunk_adaptor<std::decay_t<It>>(FLUX_FWD(it), chunk_sz_); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto chunk = detail::chunk_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::chunk(num::integral auto chunk_sz) && | |
{ | |
return flux::chunk(std::move(derived()), chunk_sz); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_CHUNK_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_CHUNK_BY_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CHUNK_BY_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <multipass_sequence Base, typename Pred> | |
struct chunk_by_adaptor : inline_sequence_base<chunk_by_adaptor<Base, Pred>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Pred pred_; | |
public: | |
constexpr explicit chunk_by_adaptor(decays_to<Base> auto&& base, Pred&& pred) | |
: base_(FLUX_FWD(base)), | |
pred_(std::move(pred)) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> from; | |
cursor_t<Base> to; | |
friend constexpr auto operator==(cursor_type const& lhs, cursor_type const& rhs) -> bool | |
{ | |
return lhs.from == rhs.from; | |
} | |
}; | |
static constexpr auto find_next(auto& self, cursor_t<Base> cur) -> cursor_t<Base> | |
{ | |
if (flux::is_last(self.base_, cur)) { | |
return cur; | |
} | |
auto nxt = flux::next(self.base_, cur); | |
while (!flux::is_last(self.base_, nxt)) { | |
if (!std::invoke(self.pred_, flux::read_at(self.base_, cur), flux::read_at(self.base_, nxt))) { | |
break; | |
} | |
cur = nxt; | |
flux::inc(self.base_, nxt); | |
} | |
return nxt; | |
} | |
static constexpr auto find_prev(auto& self, cursor_t<Base> cur) -> cursor_t<Base> | |
{ | |
auto const fst = flux::first(self.base_); | |
if (cur == fst || flux::dec(self.base_, cur) == fst) { | |
return cur; | |
} | |
do { | |
auto prv = flux::prev(self.base_, cur); | |
if (!std::invoke(self.pred_, flux::read_at(self.base_, prv), flux::read_at(self.base_, cur))) { | |
break; | |
} | |
cur = std::move(prv); | |
} while (cur != fst); | |
return cur; | |
} | |
public: | |
static constexpr auto first(auto& self) -> cursor_type | |
{ | |
return cursor_type{ | |
.from = flux::first(self.base_), | |
.to = find_next(self, flux::first(self.base_)) | |
}; | |
} | |
static constexpr auto is_last(auto&, cursor_type const& cur) -> bool | |
{ | |
return cur.from == cur.to; | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) -> void | |
{ | |
cur = cursor_type{ | |
.from = cur.to, | |
.to = find_next(self, cur.to) | |
}; | |
} | |
static constexpr auto read_at(auto& self, cursor_type const& cur) | |
-> decltype(flux::slice(self.base_, cur.from, cur.to)) | |
{ | |
return flux::slice(self.base_, cur.from, cur.to); | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
requires bounded_sequence<Base> | |
{ | |
return cursor_type{flux::last(self.base_), flux::last(self.base_)}; | |
} | |
static constexpr auto dec(auto& self, cursor_type& cur) -> void | |
requires bidirectional_sequence<Base> | |
{ | |
cur = cursor_type{ | |
.from = find_prev(self, cur.from), | |
.to = cur.from | |
}; | |
} | |
}; | |
}; | |
struct chunk_by_fn { | |
template <adaptable_sequence Seq, std::move_constructible Pred> | |
requires multipass_sequence<Seq> && | |
std::predicate<Pred&, element_t<Seq>, element_t<Seq>> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, Pred pred) const -> multipass_sequence auto | |
{ | |
return chunk_by_adaptor<std::decay_t<Seq>, Pred>(FLUX_FWD(seq), std::move(pred)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto chunk_by = detail::chunk_by_fn{}; | |
template <typename Derived> | |
template <typename Pred> | |
requires multipass_sequence<Derived> && | |
std::predicate<Pred&, element_t<Derived>, element_t<Derived>> | |
constexpr auto inline_sequence_base<Derived>::chunk_by(Pred pred) && | |
{ | |
return flux::chunk_by(std::move(derived()), std::move(pred)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_CHUNK_BY_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_CURSORS_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CURSORS_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename Base> | |
struct cursors_adaptor : inline_sequence_base<cursors_adaptor<Base>> { | |
private: | |
Base base_; | |
public: | |
constexpr explicit cursors_adaptor(decays_to<Base> auto&& base) | |
: base_(FLUX_FWD(base)) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
static inline constexpr bool is_infinite = infinite_sequence<Base>; | |
static constexpr auto first(auto& self) -> decltype(flux::first(self.base_)) | |
{ | |
return flux::first(self.base_); | |
} | |
static constexpr auto is_last(auto& self, cursor_t<Base> const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur); | |
} | |
static constexpr auto inc(auto& self, cursor_t<Base>& cur) -> void | |
{ | |
flux::inc(self.base_, cur); | |
} | |
static constexpr auto read_at(auto&, cursor_t<Base> const& cur) | |
-> cursor_t<Base> | |
{ | |
return cur; | |
} | |
static constexpr auto dec(auto& self, cursor_t<Base>& cur) -> void | |
requires bidirectional_sequence<Base> | |
{ | |
flux::dec(self.base_, cur); | |
} | |
static constexpr auto inc(auto& self, cursor_t<Base>& cur, int_t offset) -> void | |
requires random_access_sequence<Base> | |
{ | |
flux::inc(self.base_, cur, offset); | |
} | |
static constexpr auto distance(auto& self, cursor_t<Base> const& from, | |
cursor_t<Base> const& to) -> int_t | |
requires random_access_sequence<Base> | |
{ | |
return flux::distance(self.base_, from, to); | |
} | |
static constexpr auto last(auto& self) -> cursor_t<Base> | |
requires bounded_sequence<Base> | |
{ | |
return flux::last(self.base_); | |
} | |
static constexpr auto size(auto& self) -> int_t | |
requires sized_sequence<Base> | |
{ | |
return flux::size(self.base_); | |
} | |
}; | |
}; | |
struct cursors_fn { | |
template <adaptable_sequence Seq> | |
requires multipass_sequence<Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const -> sequence auto | |
{ | |
return cursors_adaptor<std::decay_t<Seq>>(FLUX_FWD(seq)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto cursors = detail::cursors_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::cursors() && | |
requires multipass_sequence<D> | |
{ | |
return flux::cursors(std::move(derived())); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_CURSORS_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_CYCLE_HPP_INCLUDED | |
#define FLUX_ADAPTOR_CYCLE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <bool> | |
struct cycle_data { | |
std::size_t count; | |
}; | |
template <> | |
struct cycle_data<true> {}; | |
template <multipass_sequence Base, bool IsInfinite> | |
struct cycle_adaptor : inline_sequence_base<cycle_adaptor<Base, IsInfinite>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS cycle_data<IsInfinite> data_; | |
public: | |
constexpr explicit cycle_adaptor(decays_to<Base> auto&& base) | |
requires IsInfinite | |
: base_(FLUX_FWD(base)) | |
{} | |
constexpr cycle_adaptor(decays_to<Base> auto&& base, std::size_t count) | |
requires (!IsInfinite) | |
: base_(FLUX_FWD(base)), | |
data_(count) | |
{} | |
struct flux_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> base_cur; | |
// Use an unsigned type to avoid UB on overflow | |
std::size_t n = 0; | |
friend auto operator==(cursor_type const&, cursor_type const&) -> bool = default; | |
friend auto operator<=>(cursor_type const&, cursor_type const&) | |
-> std::strong_ordering | |
requires std::three_way_comparable<cursor_t<Base>, std::strong_ordering> | |
= default; | |
}; | |
public: | |
using value_type = value_t<Base>; | |
static constexpr bool is_infinite = IsInfinite; | |
static constexpr auto first(auto& self) | |
-> decltype(cursor_type{flux::first(self.base_)}) | |
{ | |
if constexpr (IsInfinite) { | |
return cursor_type{flux::first(self.base_)}; | |
} else { | |
auto cur = flux::first(self.base_); | |
if (flux::is_last(self.base_, cur)) { | |
return cursor_type{std::move(cur), self.data_.count}; | |
} else { | |
return cursor_type{std::move(cur)}; | |
} | |
} | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) -> bool | |
{ | |
if constexpr (IsInfinite) { | |
return false; | |
} else { | |
return cur.n >= self.data_.count; | |
} | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) -> void | |
{ | |
flux::inc(self.base_, cur.base_cur); | |
if (flux::is_last(self.base_, cur.base_cur)) { | |
cur.base_cur = flux::first(self.base_); | |
++cur.n; | |
} | |
} | |
static constexpr auto read_at(auto& self, cursor_type const& cur) | |
-> decltype(static_cast<const_element_t<Base>>(flux::read_at(self.base_, cur.base_cur))) | |
{ | |
return static_cast<const_element_t<Base>>( | |
flux::read_at(self.base_, cur.base_cur)); | |
} | |
static constexpr auto read_at_unchecked(auto& self, cursor_type const& cur) | |
-> const_element_t<Base> | |
{ | |
return static_cast<const_element_t<Base>>( | |
flux::read_at_unchecked(self.base_, cur.base_cur)); | |
} | |
static constexpr auto move_at(auto& self, cursor_type const& cur) | |
-> decltype(auto) | |
{ | |
using R = std::common_reference_t<value_t<Base> const&&, rvalue_element_t<Base>>; | |
return static_cast<R>(flux::move_at(self.base_, cur.base_cur)); | |
} | |
static constexpr auto move_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(auto) | |
{ | |
using R = std::common_reference_t<value_t<Base> const&&, rvalue_element_t<Base>>; | |
return static_cast<R>(flux::move_at_unchecked(self.base_, cur.base_cur)); | |
} | |
static constexpr auto for_each_while(auto& self, auto&& pred) -> cursor_type | |
{ | |
auto constify_pred = [&pred](auto&& elem) { | |
return std::invoke(pred, static_cast<const_element_t<Base>>(FLUX_FWD(elem))); | |
}; | |
if constexpr (IsInfinite) { | |
std::size_t n = 0; | |
while (true) { | |
auto cur = flux::seq_for_each_while(self.base_, constify_pred); | |
if (!flux::is_last(self.base_, cur)) { | |
return cursor_type{std::move(cur), n}; | |
} | |
++n; | |
} | |
} else { | |
for (std::size_t n = 0; n < self.data_.count; ++n) { | |
auto cur = flux::seq_for_each_while(self.base_, constify_pred); | |
if (!flux::is_last(self.base_, cur)) { | |
return cursor_type{std::move(cur), n}; | |
} | |
} | |
return last(self); | |
} | |
} | |
static constexpr auto dec(auto& self, cursor_type& cur) -> void | |
requires bidirectional_sequence<decltype(self.base_)> && | |
bounded_sequence<decltype(self.base_)> | |
{ | |
if (cur.base_cur == flux::first(self.base_)) { | |
--cur.n; | |
cur.base_cur = flux::last(self.base_); | |
} | |
flux::dec(self.base_, cur.base_cur); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur, int_t offset) | |
requires random_access_sequence<decltype(self.base_)> | |
&& bounded_sequence<decltype(self.base_)> | |
{ | |
auto const first = flux::first(self.base_); | |
auto const sz = flux::size(self.base_); | |
if (sz == 0) { | |
return; | |
} | |
auto off = flux::distance(self.base_, first, cur.base_cur); | |
off = num::add(off, offset); | |
cur.n += static_cast<std::size_t>(off/sz); | |
off = off % sz; | |
if (off < 0) { | |
off +=sz; // differing signs | |
} | |
cur.base_cur = flux::next(self.base_, first, off); | |
} | |
static constexpr auto distance(auto& self, cursor_type const& from, cursor_type const& to) | |
-> int_t | |
requires random_access_sequence<decltype(self.base_)> | |
&& sized_sequence<decltype(self.base_)> | |
{ | |
auto dist = num::cast<int_t>(to.n) - num::cast<int_t>(from.n); | |
dist = num::mul(dist, flux::size(self.base_)); | |
return num::add(dist, | |
flux::distance(self.base_, from.base_cur, to.base_cur)); | |
} | |
// Weirdly, we don't actually need Base to be bounded | |
static constexpr auto last(auto& self) -> cursor_type | |
requires (!IsInfinite) | |
{ | |
return cursor_type{.base_cur = flux::first(self.base_), | |
.n = self.data_.count}; | |
} | |
static constexpr auto size(auto& self) -> int_t | |
requires(!IsInfinite && sized_sequence<Base>) | |
{ | |
return num::mul(flux::size(self.base_), num::cast<flux::int_t>(self.data_.count)); | |
} | |
}; | |
}; | |
struct cycle_fn { | |
template <adaptable_sequence Seq> | |
requires infinite_sequence<Seq> || multipass_sequence<Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const -> infinite_sequence auto | |
{ | |
if constexpr (infinite_sequence<Seq>) { | |
return FLUX_FWD(seq); | |
} else { | |
return cycle_adaptor<std::decay_t<Seq>, true>(FLUX_FWD(seq)); | |
} | |
} | |
template <adaptable_sequence Seq> | |
requires multipass_sequence<Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, num::integral auto count) const | |
-> multipass_sequence auto | |
{ | |
auto c = num::checked_cast<int_t>(count); | |
if (c < 0) { | |
runtime_error("Negative count passed to cycle()"); | |
} | |
return cycle_adaptor<std::decay_t<Seq>, false>( | |
FLUX_FWD(seq), num::checked_cast<std::size_t>(c)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto cycle = detail::cycle_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::cycle() && | |
requires infinite_sequence<D> || multipass_sequence<D> | |
{ | |
return flux::cycle(std::move(derived())); | |
} | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::cycle(num::integral auto count) && | |
requires multipass_sequence<D> | |
{ | |
return flux::cycle(std::move(derived()), count); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_CYCLE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_DROP_WHILE_HPP_INCLUDED | |
#define FLUX_ADAPTOR_DROP_WHILE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename Base, typename Pred> | |
struct drop_while_adaptor : inline_sequence_base<drop_while_adaptor<Base, Pred>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Pred pred_; | |
friend struct passthrough_traits_base; | |
constexpr auto base() & -> Base& { return base_; } | |
constexpr auto base() const& -> Base const& { return base_; } | |
template <typename BaseCtx, typename DropPred> | |
struct context_type : immovable { | |
BaseCtx base_ctx; | |
DropPred drop_pred; | |
bool done = false; | |
using element_type = context_element_t<BaseCtx>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
return base_ctx.run_while([&](auto&& elem) { | |
if (done) { | |
return std::invoke(pred, FLUX_FWD(elem)); | |
} else { | |
if (std::invoke(drop_pred, std::as_const(elem))) { | |
return loop_continue; | |
} else { | |
done = true; | |
return std::invoke(pred, FLUX_FWD(elem)); | |
} | |
} | |
}); | |
} | |
}; | |
public: | |
constexpr drop_while_adaptor(decays_to<Base> auto&& base, decays_to<Pred> auto&& pred) | |
: base_(FLUX_FWD(base)), | |
pred_(FLUX_FWD(pred)) | |
{ | |
} | |
[[nodiscard]] constexpr auto iterate() | |
{ | |
return context_type<iteration_context_t<Base>, copy_or_ref_t<Pred>>{ | |
.base_ctx = flux::iterate(base_), .drop_pred = copy_or_ref(pred_)}; | |
} | |
[[nodiscard]] | |
constexpr auto iterate() const | |
requires iterable<Base const> && std::predicate<Pred&, iterable_element_t<Base const>> | |
{ | |
return context_type<iteration_context_t<Base const>, copy_or_ref_t<Pred const>>{ | |
.base_ctx = flux::iterate(base_), .drop_pred = copy_or_ref(pred_)}; | |
} | |
struct flux_sequence_traits : detail::passthrough_traits_base { | |
static constexpr bool disable_multipass = !multipass_sequence<Base>; | |
static constexpr auto first(auto& self) | |
requires sequence<decltype((self.base_))> | |
{ | |
return flux::seq_for_each_while(self.base_, std::ref(self.pred_)); | |
} | |
static constexpr auto data(auto& self) | |
requires contiguous_sequence<Base> | |
{ | |
return flux::data(self.base_) + | |
flux::distance(self.base_, flux::first(self.base_), first(self)); | |
} | |
using default_sequence_traits::size; | |
using default_sequence_traits::for_each_while; | |
}; | |
}; | |
struct drop_while_fn { | |
template <adaptable_iterable It, std::move_constructible Pred> | |
requires std::predicate<Pred&, iterable_element_t<It>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Pred pred) const | |
{ | |
return drop_while_adaptor<std::decay_t<It>, Pred>(FLUX_FWD(it), std::move(pred)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto drop_while = detail::drop_while_fn{}; | |
template <typename D> | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<D>> | |
constexpr auto inline_sequence_base<D>::drop_while(Pred pred) && | |
{ | |
return flux::drop_while(std::move(derived()), std::move(pred)); | |
}; | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_DROP_WHILE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_FILTER_HPP_INCLUDED | |
#define FLUX_ADAPTOR_FILTER_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_FIND_HPP_INCLUDED | |
#define FLUX_ALGORITHM_FIND_HPP_INCLUDED | |
#include <cstring> | |
#include <type_traits> | |
namespace flux { | |
namespace detail { | |
struct find_fn { | |
private: | |
template <typename Seq, typename Value> | |
static constexpr auto impl(Seq&& seq, Value const& value) -> cursor_t<Seq> | |
{ | |
return seq_for_each_while(seq, [&](auto&& elem) { return FLUX_FWD(elem) != value; }); | |
} | |
public: | |
template <sequence Seq, typename Value> | |
requires std::equality_comparable_with<element_t<Seq>, Value const&> | |
constexpr auto operator()(Seq&& seq, Value const& value) const -> cursor_t<Seq> | |
{ | |
constexpr auto can_memchr = contiguous_sequence<Seq> && sized_sequence<Seq> | |
&& std::same_as<Value, value_t<Seq>> | |
&& flux::detail::any_of<value_t<Seq>, char, signed char, unsigned char, char8_t, | |
std::byte>; | |
if constexpr (can_memchr) { | |
if (std::is_constant_evaluated()) { | |
return impl(seq, value); // LCOV_EXCL_LINE | |
} else { | |
auto size = flux::usize(seq); | |
if (size == 0) { | |
return flux::last(seq); | |
} | |
FLUX_ASSERT(flux::data(seq) != nullptr); | |
auto location = std::memchr(flux::data(seq), static_cast<unsigned char>(value), | |
size * sizeof(value_t<Seq>)); | |
if (location == nullptr) { | |
return flux::last(seq); | |
} else { | |
auto offset = static_cast<value_t<Seq> const*>(location) - flux::data(seq); | |
return flux::next(seq, flux::first(seq), offset); | |
} | |
} | |
} else { | |
return impl(seq, value); | |
} | |
} | |
}; | |
struct find_if_fn { | |
template <sequence Seq, typename Pred> | |
requires std::predicate<Pred&, element_t<Seq>> | |
constexpr auto operator()(Seq&& seq, Pred pred) const | |
-> cursor_t<Seq> | |
{ | |
return seq_for_each_while(seq, | |
[&](auto&& elem) { return !std::invoke(pred, FLUX_FWD(elem)); }); | |
} | |
}; | |
struct find_if_not_fn { | |
template <sequence Seq, typename Pred> | |
requires std::predicate<Pred&, element_t<Seq>> | |
constexpr auto operator()(Seq&& seq, Pred pred) const | |
-> cursor_t<Seq> | |
{ | |
return seq_for_each_while(seq, | |
[&](auto&& elem) { return std::invoke(pred, FLUX_FWD(elem)); }); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto find = detail::find_fn{}; | |
FLUX_EXPORT inline constexpr auto find_if = detail::find_if_fn{}; | |
FLUX_EXPORT inline constexpr auto find_if_not = detail::find_if_not_fn{}; | |
template <typename D> | |
template <typename Value> | |
requires std::equality_comparable_with<element_t<D>, Value const&> | |
constexpr auto inline_sequence_base<D>::find(Value const& val) | |
{ | |
return flux::find(derived(), val); | |
} | |
template <typename D> | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<D>> | |
constexpr auto inline_sequence_base<D>::find_if(Pred pred) | |
{ | |
return flux::find_if(derived(), std::ref(pred)); | |
} | |
template <typename D> | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<D>> | |
constexpr auto inline_sequence_base<D>::find_if_not(Pred pred) | |
{ | |
return flux::find_if_not(derived(), std::ref(pred)); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_FIND_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename Base, typename Pred> | |
class filter_adaptor : public inline_sequence_base<filter_adaptor<Base, Pred>> { | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Pred pred_; | |
template <typename BaseCtx, typename FilterFn> | |
struct context_type : immovable { | |
BaseCtx base_ctx; | |
FilterFn filter_fn; | |
using element_type = context_element_t<BaseCtx>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
return base_ctx.run_while([this, &pred](auto&& elem) { | |
if (std::invoke(filter_fn, elem)) { | |
return std::invoke(pred, FLUX_FWD(elem)); | |
} else { | |
return loop_continue; | |
} | |
}); | |
} | |
}; | |
public: | |
constexpr filter_adaptor(decays_to<Base> auto&& base, decays_to<Pred> auto&& pred) | |
: base_(FLUX_FWD(base)), | |
pred_(FLUX_FWD(pred)) | |
{} | |
[[nodiscard]] | |
constexpr auto base() const& -> Base const& { return base_; } | |
[[nodiscard]] | |
constexpr auto base() && -> Base { return std::move(base_); } | |
constexpr auto iterate() | |
{ | |
return context_type<iteration_context_t<Base>, copy_or_ref_t<Pred>>{ | |
.base_ctx = flux::iterate(base_), .filter_fn = copy_or_ref(pred_)}; | |
} | |
constexpr auto iterate() const | |
requires iterable<Base const> && std::invocable<Pred&, iterable_element_t<Base const>> | |
{ | |
return context_type<iteration_context_t<Base const>, copy_or_ref_t<Pred const>>{ | |
.base_ctx = flux::iterate(base_), .filter_fn = copy_or_ref(pred_)}; | |
} | |
constexpr auto reverse_iterate() | |
requires reverse_iterable<Base> | |
{ | |
return context_type<reverse_iteration_context_t<Base>, copy_or_ref_t<Pred>>{ | |
.base_ctx = flux::reverse_iterate(base_), .filter_fn = copy_or_ref(pred_)}; | |
} | |
constexpr auto reverse_iterate() const | |
requires reverse_iterable<Base const> | |
&& std::invocable<Pred&, iterable_element_t<Base const>> | |
{ | |
return context_type<reverse_iteration_context_t<Base const>, copy_or_ref_t<Pred const>>{ | |
.base_ctx = flux::reverse_iterate(base_), .filter_fn = copy_or_ref(pred_)}; | |
} | |
struct flux_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> base_cur; | |
friend auto operator==(cursor_type const&, cursor_type const&) -> bool | |
requires std::equality_comparable<cursor_t<Base>> | |
= default; | |
}; | |
public: | |
using value_type = value_t<Base>; | |
static constexpr bool disable_multipass = !multipass_sequence<Base>; | |
static constexpr auto first(auto& self) -> cursor_type | |
{ | |
return cursor_type{flux::find_if(self.base_, std::ref(self.pred_))}; | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.base_cur); | |
} | |
static constexpr auto read_at(auto& self, cursor_type const& cur) | |
-> decltype(flux::read_at(self.base_, cur.base_cur)) | |
{ | |
return flux::read_at(self.base_, cur.base_cur); | |
} | |
static constexpr auto read_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(flux::read_at_unchecked(self.base_, cur.base_cur)) | |
{ | |
return flux::read_at_unchecked(self.base_, cur.base_cur); | |
} | |
static constexpr auto move_at(auto& self, cursor_type const& cur) | |
-> decltype(flux::move_at(self.base_, cur.base_cur)) | |
{ | |
return flux::move_at(self.base_, cur.base_cur); | |
} | |
static constexpr auto move_at_unchecked(auto& self, cursor_type const& cur) | |
-> decltype(flux::move_at_unchecked(self.base_, cur.base_cur)) | |
{ | |
return flux::move_at_unchecked(self.base_, cur.base_cur); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) -> void | |
{ | |
flux::inc(self.base_, cur.base_cur); | |
cur.base_cur = flux::slice(self.base_, std::move(cur).base_cur, flux::last) | |
.find_if(std::ref(self.pred_)); | |
} | |
static constexpr auto dec(auto& self, cursor_type& cur) -> void | |
requires bidirectional_sequence<Base> | |
{ | |
do { | |
flux::dec(self.base_, cur.base_cur); | |
} while(!std::invoke(self.pred_, flux::read_at(self.base_, cur.base_cur))); | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
requires bounded_sequence<Base> | |
{ | |
return cursor_type{flux::last(self.base_)}; | |
} | |
static constexpr auto for_each_while(auto& self, auto&& func) | |
-> cursor_type | |
{ | |
return cursor_type {flux::seq_for_each_while(self.base_, [&](auto&& elem) { | |
if (std::invoke(self.pred_, elem)) { | |
return std::invoke(func, FLUX_FWD(elem)); | |
} else { | |
return true; | |
} | |
})}; | |
} | |
}; | |
}; | |
struct filter_fn { | |
template <adaptable_iterable It, std::move_constructible Pred> | |
requires std::predicate<Pred&, iterable_element_t<It> const&> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Pred pred) const | |
{ | |
return filter_adaptor<std::decay_t<It>, Pred>(FLUX_FWD(it), std::move(pred)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto filter = detail::filter_fn{}; | |
template <typename D> | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<D>> | |
constexpr auto inline_sequence_base<D>::filter(Pred pred) && | |
{ | |
return detail::filter_adaptor<D, Pred>(std::move(derived()), std::move(pred)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_FILTER_HPP_INCLUDED | |
// Copyright (c) 2024 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_FILTER_MAP_HPP_INCLUDED | |
#define FLUX_ADAPTOR_FILTER_MAP_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_MAP_HPP_INCLUDED | |
#define FLUX_ADAPTOR_MAP_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename Base, typename Func> | |
struct map_adaptor : inline_sequence_base<map_adaptor<Base, Func>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Func func_; | |
friend struct sequence_traits<map_adaptor>; | |
template <typename BaseCtx, typename MapFn> | |
struct context_type : immovable { | |
BaseCtx base_ctx; | |
MapFn map_fn; | |
using element_type = std::invoke_result_t<MapFn&, context_element_t<BaseCtx>>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
return base_ctx.run_while( | |
[&pred, this](auto&& elem) { return pred(std::invoke(map_fn, FLUX_FWD(elem))); }); | |
} | |
}; | |
public: | |
constexpr map_adaptor(decays_to<Base> auto&& base, decays_to<Func> auto&& func) | |
: base_(FLUX_FWD(base)), | |
func_(FLUX_FWD(func)) | |
{} | |
constexpr auto base() & -> Base& { return base_; } | |
constexpr auto base() const& -> Base const& { return base_; } | |
constexpr auto base() && -> Base&& { return std::move(base_); } | |
constexpr auto base() const&& -> Base const&& { return std::move(base_); } | |
[[nodiscard]] constexpr auto iterate() | |
{ | |
return context_type<iteration_context_t<Base>, copy_or_ref_t<Func>>{ | |
.base_ctx = flux::iterate(base_), .map_fn = copy_or_ref(func_)}; | |
} | |
[[nodiscard]] constexpr auto iterate() const | |
requires iterable<Base const> | |
&& std::regular_invocable<Func&, iterable_element_t<Base const>> | |
{ | |
return context_type<iteration_context_t<Base const>, copy_or_ref_t<Func const>>{ | |
.base_ctx = flux::iterate(base_), .map_fn = copy_or_ref(func_)}; | |
} | |
[[nodiscard]] constexpr auto reverse_iterate() | |
requires reverse_iterable<Base> | |
{ | |
return context_type<reverse_iteration_context_t<Base>, copy_or_ref_t<Func>>{ | |
.base_ctx = flux::reverse_iterate(base_), .map_fn = copy_or_ref(func_)}; | |
} | |
[[nodiscard]] constexpr auto reverse_iterate() const | |
requires reverse_iterable<Base const> | |
&& std::regular_invocable<Func&, iterable_element_t<Base const>> | |
{ | |
return context_type<reverse_iteration_context_t<Base const>, copy_or_ref_t<Func const>>{ | |
.base_ctx = flux::reverse_iterate(base_), .map_fn = copy_or_ref(func_)}; | |
} | |
constexpr auto size() -> int_t | |
requires sized_iterable<Base> | |
{ | |
return flux::iterable_size(base_); | |
} | |
constexpr auto size() const -> int_t | |
requires sized_iterable<Base const> | |
{ | |
return flux::iterable_size(base_); | |
} | |
struct flux_sequence_traits : detail::passthrough_traits_base { | |
static constexpr bool disable_multipass = !multipass_sequence<Base>; | |
static constexpr bool is_infinite = infinite_sequence<Base>; | |
template <typename Self> | |
static constexpr auto read_at(Self& self, cursor_t<Self> const& cur) | |
-> decltype(std::invoke(self.func_, flux::read_at(self.base_, cur))) | |
{ | |
return std::invoke(self.func_, flux::read_at(self.base_, cur)); | |
} | |
template <typename Self> | |
static constexpr auto read_at_unchecked(Self& self, cursor_t<Self> const& cur) | |
-> decltype(std::invoke(self.func_, flux::read_at_unchecked(self.base_, cur))) | |
{ | |
return std::invoke(self.func_, flux::read_at_unchecked(self.base_, cur)); | |
} | |
static constexpr auto for_each_while(auto& self, auto&& pred) | |
{ | |
return flux::seq_for_each_while(self.base_, [&](auto&& elem) { | |
return std::invoke(pred, std::invoke(self.func_, FLUX_FWD(elem))); | |
}); | |
} | |
using default_sequence_traits::move_at; | |
using default_sequence_traits::move_at_unchecked; | |
static void data() = delete; // we're not a contiguous sequence | |
}; | |
}; | |
struct map_fn { | |
template <adaptable_iterable It, std::move_constructible Func> | |
requires std::regular_invocable<Func&, iterable_element_t<It>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Func func) const | |
{ | |
return map_adaptor<std::decay_t<It>, Func>(FLUX_FWD(it), std::move(func)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto map = detail::map_fn{}; | |
template <typename Derived> | |
template <typename Func> | |
requires std::invocable<Func&, element_t<Derived>> | |
constexpr auto inline_sequence_base<Derived>::map(Func func) && | |
{ | |
return detail::map_adaptor<Derived, Func>(std::move(derived()), std::move(func)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_MAP_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
struct filter_map_fn { | |
// If dereffing the optional would give us an rvalue reference, | |
// prevent a probable dangling reference by returning by value instead | |
template <typename T> | |
using strip_rvalue_ref_t = std::conditional_t< | |
std::is_rvalue_reference_v<T>, std::remove_reference_t<T>, T>; | |
template <adaptable_iterable It, typename Func> | |
requires(std::invocable<Func&, iterable_element_t<It>> | |
&& optional_like< | |
std::remove_cvref_t<std::invoke_result_t<Func&, iterable_element_t<It>>>>) | |
constexpr auto operator()(It&& it, Func func) const | |
{ | |
// FIXME: Change this back to a pipeline later | |
auto mapped = flux::map(FLUX_FWD(it), std::move(func)); | |
auto filtered | |
= flux::filter(std::move(mapped), [](auto&& opt) { return static_cast<bool>(opt); }); | |
return flux::map(std::move(filtered), | |
[](auto&& opt) -> strip_rvalue_ref_t<decltype(*FLUX_FWD(opt))> { | |
return *FLUX_FWD(opt); | |
}); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto filter_map = detail::filter_map_fn{}; | |
template <typename D> | |
template <typename Func> | |
requires std::invocable<Func&, element_t<D>> && | |
detail::optional_like<std::invoke_result_t<Func&, element_t<D>>> | |
constexpr auto inline_sequence_base<D>::filter_map(Func func) && | |
{ | |
return flux::filter_map(derived(), std::move(func)); | |
} | |
namespace detail | |
{ | |
struct filter_deref_fn { | |
template <adaptable_iterable It> | |
requires optional_like<iterable_value_t<It>> | |
constexpr auto operator()(It&& it) const | |
{ | |
return filter_map(FLUX_FWD(it), [](auto&& opt) -> decltype(auto) { return FLUX_FWD(opt); }); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto filter_deref = detail::filter_deref_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::filter_deref() && requires detail::optional_like<value_t<D>> | |
{ | |
return flux::filter_deref(derived()); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_FILTER_MAP_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_FLATTEN_HPP_INCLUDED | |
#define FLUX_ADAPTOR_FLATTEN_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename BaseCtx, auto const& IterateFn = flux::iterate> | |
struct flatten_iteration_context : immovable { | |
BaseCtx base_ctx; | |
using OptInnerElem = decltype(next_element(base_ctx)); | |
OptInnerElem inner_elem = nullopt; | |
using InnerCtx = decltype(IterateFn(*inner_elem)); | |
optional<InnerCtx> inner_ctx = nullopt; | |
using element_type = context_element_t<InnerCtx>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
while (true) { | |
if (!inner_ctx.has_value()) { | |
inner_elem = next_element(base_ctx); | |
if (!inner_elem) { | |
return iteration_result::complete; | |
} | |
inner_ctx.emplace(detail::emplace_from([&] { return IterateFn(*inner_elem); })); | |
} | |
FLUX_DEBUG_ASSERT(inner_ctx.has_value()); | |
auto res = flux::run_while(*inner_ctx, pred); | |
if (res == iteration_result::incomplete) { | |
return res; | |
} else { | |
inner_ctx.reset(); | |
} | |
} | |
} | |
}; | |
template <typename Base> | |
struct flatten_adaptor : inline_sequence_base<flatten_adaptor<Base>> { | |
private: | |
Base base_; | |
public: | |
constexpr explicit flatten_adaptor(decays_to<Base> auto&& base) : base_(FLUX_FWD(base)) { } | |
constexpr auto iterate() | |
{ | |
using Ctx = flatten_iteration_context<iteration_context_t<Base>>; | |
return Ctx{.base_ctx = flux::iterate(base_)}; | |
} | |
constexpr auto iterate() const | |
requires iterable<Base const> && iterable<iterable_element_t<Base const>> | |
{ | |
using Ctx = flatten_iteration_context<iteration_context_t<Base const>>; | |
return Ctx{.base_ctx = flux::iterate(base_)}; | |
} | |
constexpr auto reverse_iterate() | |
requires reverse_iterable<Base> && reverse_iterable<iterable_element_t<Base>> | |
{ | |
using Ctx | |
= flatten_iteration_context<reverse_iteration_context_t<Base>, flux::reverse_iterate>; | |
return Ctx{.base_ctx = flux::reverse_iterate(base_)}; | |
} | |
constexpr auto reverse_iterate() const | |
requires reverse_iterable<Base const> && reverse_iterable<iterable_element_t<Base const>> | |
{ | |
using Ctx = flatten_iteration_context<reverse_iteration_context_t<Base const>, | |
flux::reverse_iterate>; | |
return Ctx{.base_ctx = flux::reverse_iterate(base_)}; | |
} | |
}; | |
template <sequence Base> | |
struct flatten_adaptor<Base> : inline_sequence_base<flatten_adaptor<Base>> { | |
private: | |
using InnerSeq = element_t<Base>; | |
Base base_; | |
optional<InnerSeq> inner_ = nullopt; | |
public: | |
constexpr explicit flatten_adaptor(decays_to<Base> auto&& base) : base_(FLUX_FWD(base)) { } | |
constexpr auto iterate() | |
{ | |
return flatten_iteration_context<iteration_context_t<Base>>{.base_ctx | |
= flux::iterate(base_)}; | |
} | |
constexpr auto iterate() const | |
requires iterable<Base const> && iterable<iterable_element_t<Base const>> | |
{ | |
return flatten_iteration_context<iteration_context_t<Base const>>{.base_ctx | |
= flux::iterate(base_)}; | |
} | |
constexpr auto reverse_iterate() | |
requires reverse_iterable<Base> && reverse_iterable<iterable_element_t<Base>> | |
{ | |
using Ctx | |
= flatten_iteration_context<reverse_iteration_context_t<Base>, flux::reverse_iterate>; | |
return Ctx{.base_ctx = flux::reverse_iterate(base_)}; | |
} | |
constexpr auto reverse_iterate() const | |
requires reverse_iterable<Base const> && reverse_iterable<iterable_element_t<Base const>> | |
{ | |
using Ctx = flatten_iteration_context<reverse_iteration_context_t<Base const>, | |
flux::reverse_iterate>; | |
return Ctx{.base_ctx = flux::reverse_iterate(base_)}; | |
} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
using self_t = flatten_adaptor; | |
struct cursor_type { | |
constexpr explicit cursor_type(cursor_t<Base>&& outer_cur) | |
: outer_cur(std::move(outer_cur)) | |
{} | |
cursor_type() = default; | |
cursor_type(cursor_type&&) = default; | |
cursor_type& operator=(cursor_type&&) = default; | |
cursor_t<Base> outer_cur; | |
optional<cursor_t<InnerSeq>> inner_cur = nullopt; | |
}; | |
static constexpr auto satisfy(auto& self, cursor_type& cur) -> void | |
{ | |
while (!flux::is_last(self.base_, cur.outer_cur)) { | |
self.inner_.emplace(flux::read_at(self.base_, cur.outer_cur)); | |
cur.inner_cur.emplace(flux::first(*self.inner_)); | |
if (!flux::is_last(*self.inner_, *cur.inner_cur)) { | |
return; | |
} | |
flux::inc(self.base_, cur.outer_cur); | |
} | |
} | |
public: | |
using value_type = value_t<InnerSeq>; | |
static constexpr auto first(self_t& self) -> cursor_type | |
{ | |
cursor_type cur(flux::first(self.base_)); | |
satisfy(self, cur); | |
return cur; | |
} | |
static constexpr auto is_last(self_t& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.outer_cur); | |
} | |
static constexpr auto inc(self_t& self, cursor_type& cur) -> void | |
{ | |
flux::inc(*self.inner_, *cur.inner_cur); | |
if (flux::is_last(*self.inner_, *cur.inner_cur)) { | |
flux::inc(self.base_, cur.outer_cur); | |
satisfy(self, cur); | |
} | |
} | |
static constexpr auto read_at(self_t& self, cursor_type const& cur) -> decltype(auto) | |
{ | |
FLUX_ASSERT(self.inner_.has_value()); | |
FLUX_ASSERT(cur.inner_cur.has_value()); | |
return flux::read_at(*self.inner_, *cur.inner_cur); | |
} | |
static constexpr auto last(self_t& self) -> cursor_type | |
requires bounded_sequence<Base> | |
{ | |
return cursor_type(flux::last(self.base_)); | |
} | |
}; | |
}; | |
template <multipass_sequence Base> | |
requires std::is_reference_v<element_t<Base>> && | |
multipass_sequence<element_t<Base>> | |
struct flatten_adaptor<Base> : inline_sequence_base<flatten_adaptor<Base>> { | |
private: | |
Base base_; | |
public: | |
constexpr explicit flatten_adaptor(decays_to<Base> auto&& base) : base_(FLUX_FWD(base)) { } | |
constexpr auto iterate() -> flatten_iteration_context<iteration_context_t<Base>> | |
{ | |
return {.base_ctx = flux::iterate(base_)}; | |
} | |
constexpr auto iterate() const -> flatten_iteration_context<iteration_context_t<Base const>> | |
requires iterable<Base const> && iterable<iterable_element_t<Base const>> | |
{ | |
return {.base_ctx = flux::iterate(base_)}; | |
} | |
constexpr auto size() -> int_t | |
requires sized_sequence<Base> && sized_sequence<element_t<Base>> | |
{ | |
return num::mul(flux::size(base_), flux::size(flux::read_at(base_, flux::first(base_)))); | |
} | |
constexpr auto size() const -> int_t | |
requires sized_sequence<Base const> && sized_sequence<element_t<Base const>> | |
{ | |
return num::mul(flux::size(base_), flux::size(flux::read_at(base_, flux::first(base_)))); | |
} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
using InnerSeq = element_t<Base>; | |
template <typename Self> | |
static constexpr bool can_flatten = [] () consteval { | |
if constexpr (std::is_const_v<Self>) { | |
return multipass_sequence<Base const> && | |
std::same_as<element_t<Base const>, std::remove_reference_t<InnerSeq> const&> && | |
multipass_sequence<InnerSeq const>; | |
} else { | |
return true; | |
} | |
}(); | |
struct cursor_type { | |
cursor_t<Base> outer_cur{}; | |
cursor_t<InnerSeq> inner_cur{}; | |
friend auto operator==(cursor_type const&, cursor_type const&) -> bool = default; | |
}; | |
static constexpr auto satisfy(auto& self, cursor_type& cur) -> void | |
{ | |
while (true) { | |
if (flux::is_last(self.base_, cur.outer_cur)) { | |
cur.inner_cur = cursor_t<InnerSeq>{}; | |
return; | |
} | |
auto&& inner = flux::read_at(self.base_, cur.outer_cur); | |
cur.inner_cur = flux::first(inner); | |
if (!flux::is_last(inner, cur.inner_cur)) { | |
return; | |
} | |
flux::inc(self.base_, cur.outer_cur); | |
} | |
} | |
public: | |
using value_type = value_t<InnerSeq>; | |
template <typename Self> | |
requires can_flatten<Self> | |
static constexpr auto first(Self& self) -> cursor_type | |
{ | |
cursor_type cur{.outer_cur = flux::first(self.base_) }; | |
satisfy(self, cur); | |
return cur; | |
} | |
template <typename Self> | |
requires can_flatten<Self> | |
static constexpr auto is_last(Self& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.outer_cur); | |
} | |
template <typename Self> | |
requires can_flatten<Self> | |
static constexpr auto read_at(Self& self, cursor_type const& cur) -> decltype(auto) | |
{ | |
return flux::read_at(flux::read_at(self.base_, cur.outer_cur), | |
cur.inner_cur); | |
} | |
template <typename Self> | |
requires can_flatten<Self> | |
static constexpr auto inc(Self& self, cursor_type& cur) -> void | |
{ | |
auto&& inner = flux::read_at(self.base_, cur.outer_cur); | |
flux::inc(inner, cur.inner_cur); | |
if (flux::is_last(inner, cur.inner_cur)) { | |
flux::inc(self.base_, cur.outer_cur); | |
satisfy(self, cur); | |
} | |
} | |
template <typename Self> | |
requires can_flatten<Self> | |
static constexpr auto for_each_while(Self& self, auto&& pred) -> cursor_type | |
{ | |
auto inner_cur = cursor_t<InnerSeq>{}; | |
auto outer_cur = flux::seq_for_each_while(self.base_, [&](auto&& inner_seq) { | |
inner_cur = flux::seq_for_each_while(inner_seq, pred); | |
return flux::is_last(inner_seq, inner_cur); | |
}); | |
return cursor_type{.outer_cur = std::move(outer_cur), | |
.inner_cur = std::move(inner_cur)}; | |
} | |
template <typename Self> | |
requires can_flatten<Self> && bounded_sequence<Base> | |
static constexpr auto last(Self& self) -> cursor_type | |
{ | |
return cursor_type{.outer_cur = flux::last(self.base_)}; | |
} | |
template <typename Self> | |
requires can_flatten<Self> && | |
bidirectional_sequence<Base> && | |
bidirectional_sequence<InnerSeq> && | |
bounded_sequence<InnerSeq> | |
static constexpr auto dec(Self& self, cursor_type& cur) -> void | |
{ | |
if (flux::is_last(self.base_, cur.outer_cur)) { | |
flux::dec(self.base_, cur.outer_cur); | |
auto&& inner = flux::read_at(self.base_, cur.outer_cur); | |
cur.inner_cur = flux::last(inner); | |
} | |
while (true) { | |
auto&& inner = flux::read_at(self.base_, cur.outer_cur); | |
if (cur.inner_cur != flux::first(inner)) { | |
flux::dec(inner, cur.inner_cur); | |
return; | |
} else { | |
flux::dec(self.base_, cur.outer_cur); | |
auto&& next_inner = flux::read_at(self.base_, cur.outer_cur); | |
cur.inner_cur = flux::last(next_inner); | |
} | |
} | |
} | |
}; | |
}; | |
struct flatten_fn { | |
template <adaptable_iterable It> | |
requires iterable<iterable_element_t<It>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it) const -> iterable auto | |
{ | |
return flatten_adaptor<std::decay_t<It>>(FLUX_FWD(it)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto flatten = detail::flatten_fn{}; | |
template <typename Derived> | |
constexpr auto inline_sequence_base<Derived>::flatten() && | |
requires sequence<element_t<Derived>> | |
{ | |
return flux::flatten(std::move(derived())); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_FLATTEN_HPP_INCLUDED | |
// Copyright (c) 2024 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_FLATTEN_WITH_HPP_INCLUDED | |
#define FLUX_ADAPTOR_FLATTEN_WITH_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_SINGLE_HPP_INCLUDED | |
#define FLUX_SEQUENCE_SINGLE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <std::movable T> | |
struct single_sequence : inline_sequence_base<single_sequence<T>> { | |
private: | |
T obj_; | |
friend struct sequence_traits<single_sequence>; | |
public: | |
constexpr single_sequence() | |
requires std::default_initializable<T> | |
= default; | |
constexpr explicit single_sequence(T const& obj) | |
requires std::copy_constructible<T> | |
: obj_(obj) | |
{} | |
constexpr explicit single_sequence(T&& obj) | |
requires std::move_constructible<T> | |
: obj_(std::move(obj)) | |
{} | |
template <typename... Args> | |
constexpr explicit single_sequence(std::in_place_t, Args&&... args) | |
requires std::constructible_from<T, Args...> | |
: obj_(FLUX_FWD(args)...) | |
{} | |
constexpr auto value() -> T& { return obj_; } | |
constexpr auto value() const -> T const& { return obj_; } | |
}; | |
struct single_fn { | |
template <typename T> | |
constexpr auto operator()(T&& t) const -> single_sequence<std::decay_t<T>> | |
{ | |
return single_sequence<std::decay_t<T>>(FLUX_FWD(t)); | |
} | |
}; | |
} // namespace detail | |
template <typename T> | |
struct sequence_traits<detail::single_sequence<T>> : default_sequence_traits | |
{ | |
private: | |
using self_t = detail::single_sequence<T>; | |
enum class cursor_type : bool { valid, done }; | |
public: | |
static constexpr auto first(self_t const&) { return cursor_type::valid; } | |
static constexpr auto last(self_t const&) { return cursor_type::done; } | |
static constexpr bool is_last(self_t const&, cursor_type cur) | |
{ | |
return cur == cursor_type::done; | |
} | |
static constexpr auto read_at(auto& self, [[maybe_unused]] cursor_type cur) -> auto& | |
{ | |
FLUX_DEBUG_ASSERT(cur == cursor_type::valid); | |
return self.obj_; | |
} | |
static constexpr auto inc(self_t const&, cursor_type& cur) -> cursor_type& | |
{ | |
FLUX_DEBUG_ASSERT(cur == cursor_type::valid); | |
cur = cursor_type::done; | |
return cur; | |
} | |
static constexpr auto dec(self_t const&, cursor_type& cur) -> cursor_type& | |
{ | |
FLUX_DEBUG_ASSERT(cur == cursor_type::done); | |
cur = cursor_type::valid; | |
return cur; | |
} | |
static constexpr auto inc(self_t const&, cursor_type& cur, int_t off) -> cursor_type& | |
{ | |
if (off > 0) { | |
FLUX_DEBUG_ASSERT(cur == cursor_type::valid && off == 1); | |
cur = cursor_type::done; | |
} else if (off < 0) { | |
FLUX_DEBUG_ASSERT(cur == cursor_type::done && off == -1); | |
cur = cursor_type::valid; | |
} | |
return cur; | |
} | |
static constexpr auto distance(self_t const&, cursor_type from, | |
cursor_type to) | |
-> std::ptrdiff_t | |
{ | |
return static_cast<int>(to) - static_cast<int>(from); | |
} | |
static constexpr auto size(self_t const&) { return 1; } | |
static constexpr auto data(auto& self) | |
{ | |
return std::addressof(self.obj_); | |
} | |
static constexpr auto for_each_while(auto& self, auto&& pred) | |
{ | |
return std::invoke(pred, self.obj_) ? cursor_type::done : cursor_type::valid; | |
} | |
}; | |
FLUX_EXPORT inline constexpr auto single = detail::single_fn{}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_SINGLE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
// Workaround for std::variant::emplace<N> not being constexpr in libc++ | |
// See P2231 (C++20 DR) | |
template <std::size_t N> | |
inline constexpr auto variant_emplace = []<typename... Types>(std::variant<Types...>& variant, | |
auto&&... args) { | |
if (std::is_constant_evaluated()) { | |
variant = std::variant<Types...>(std::in_place_index<N>, FLUX_FWD(args)...); // LCOV_EXCL_LINE | |
} else { | |
variant.template emplace<N>(FLUX_FWD(args)...); | |
} | |
}; | |
template <typename BaseCtx, typename Pattern> | |
struct flatten_with_iteration_context : immovable { | |
BaseCtx base_ctx; | |
Pattern pattern; | |
using OptInnerElem = decltype(next_element(base_ctx)); | |
OptInnerElem inner_elem = nullopt; | |
using InnerCtx = decltype(flux::iterate(*inner_elem)); | |
optional<InnerCtx> inner_ctx = nullopt; | |
using PatternCtx = decltype(flux::iterate(pattern)); | |
optional<PatternCtx> pattern_ctx = nullopt; | |
enum class mode_t { pattern, inner }; | |
mode_t mode = mode_t::inner; | |
constexpr bool try_advance_inner() | |
{ | |
if (inner_ctx.has_value()) { | |
return true; | |
} else { | |
inner_elem = flux::next_element(base_ctx); | |
if (!inner_elem) { | |
return false; | |
} else { | |
inner_ctx.emplace(detail::emplace_from([&] { return flux::iterate(*inner_elem); })); | |
return true; | |
} | |
} | |
} | |
using element_type = std::common_reference_t<context_element_t<InnerCtx>, element_t<Pattern>>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
if (!try_advance_inner()) { | |
return iteration_result::complete; | |
} | |
while (true) { | |
if (mode == mode_t::inner) { | |
FLUX_DEBUG_ASSERT(inner_ctx.has_value()); | |
auto res = flux::run_while(*inner_ctx, pred); | |
if (res == iteration_result::incomplete) { | |
return res; | |
} else { | |
inner_ctx.reset(); | |
mode = mode_t::pattern; | |
} | |
} else { | |
if (!try_advance_inner()) { | |
return iteration_result::complete; | |
} | |
if (!pattern_ctx.has_value()) { | |
pattern_ctx.emplace( | |
detail::emplace_from([&] { return flux::iterate(pattern); })); | |
} | |
FLUX_DEBUG_ASSERT(pattern_ctx.has_value()); | |
auto res = flux::run_while(*pattern_ctx, pred); | |
if (res == iteration_result::incomplete) { | |
return res; | |
} else { | |
pattern_ctx.reset(); | |
mode = mode_t::inner; | |
} | |
} | |
} | |
} | |
}; | |
template <typename Base, multipass_sequence Pattern> | |
struct flatten_with_adaptor : inline_sequence_base<flatten_with_adaptor<Base, Pattern>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Pattern pattern_; | |
public: | |
constexpr flatten_with_adaptor(decays_to<Base> auto&& base, decays_to<Pattern> auto&& pattern) | |
: base_(FLUX_FWD(base)), | |
pattern_(FLUX_FWD(pattern)) | |
{ | |
} | |
constexpr auto iterate() | |
{ | |
return detail::flatten_with_iteration_context<iteration_context_t<Base>, | |
decltype(flux::mut_ref(pattern_))>{ | |
.base_ctx = flux::iterate(base_), .pattern = flux::mut_ref(pattern_)}; | |
} | |
constexpr auto iterate() const | |
requires iterable<Base const> && iterable<iterable_element_t<Base const>> | |
{ | |
return detail::flatten_with_iteration_context<iteration_context_t<Base const>, | |
decltype(flux::ref(pattern_))>{ | |
.base_ctx = flux::iterate(base_), .pattern = flux::ref(pattern_)}; | |
} | |
}; | |
template <sequence Base, multipass_sequence Pattern> | |
struct flatten_with_adaptor<Base, Pattern> | |
: inline_sequence_base<flatten_with_adaptor<Base, Pattern>> { | |
private: | |
using InnerSeq = element_t<Base>; | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Pattern pattern_; | |
optional<InnerSeq> inner_ = nullopt; | |
public: | |
constexpr flatten_with_adaptor(decays_to<Base> auto&& base, | |
decays_to<Pattern> auto&& pattern) | |
: base_(FLUX_FWD(base)), | |
pattern_(FLUX_FWD(pattern)) | |
{ | |
} | |
constexpr auto iterate() | |
{ | |
return detail::flatten_with_iteration_context<iteration_context_t<Base>, | |
decltype(flux::mut_ref(pattern_))>{ | |
.base_ctx = flux::iterate(base_), .pattern = flux::mut_ref(pattern_)}; | |
} | |
constexpr auto iterate() const | |
requires iterable<Base const> && iterable<iterable_element_t<Base const>> | |
{ | |
return detail::flatten_with_iteration_context<iteration_context_t<Base const>, | |
decltype(flux::ref(pattern_))>{ | |
.base_ctx = flux::iterate(base_), .pattern = flux::ref(pattern_)}; | |
} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
using self_t = flatten_with_adaptor; | |
using element_type = | |
std::common_reference_t<element_t<InnerSeq>, element_t<Pattern>>; | |
using rvalue_element_type = | |
std::common_reference_t<rvalue_element_t<InnerSeq>, rvalue_element_t<Pattern>>; | |
struct cursor_type { | |
constexpr explicit cursor_type(cursor_t<Base>&& outer_cur) | |
: outer_cur(std::move(outer_cur)) | |
{} | |
cursor_type(cursor_type&&) = default; | |
cursor_type& operator=(cursor_type&&) = default; | |
cursor_t<Base> outer_cur; | |
std::variant<cursor_t<Pattern>, cursor_t<InnerSeq>> inner_cur{}; | |
}; | |
static constexpr auto satisfy(self_t& self, cursor_type& cur) -> void | |
{ | |
while (true) { | |
if (cur.inner_cur.index() == 0) { | |
if (!flux::is_last(self.pattern_, std::get<0>(cur.inner_cur))) { | |
break; | |
} | |
self.inner_.emplace(flux::read_at(self.base_, cur.outer_cur)); | |
variant_emplace<1>(cur.inner_cur, flux::first(*self.inner_)); | |
} else { | |
FLUX_ASSERT(self.inner_.has_value()); | |
if (!flux::is_last(*self.inner_, std::get<1>(cur.inner_cur))) { | |
break; | |
} | |
flux::inc(self.base_, cur.outer_cur); | |
if (!flux::is_last(self.base_, cur.outer_cur)) { | |
variant_emplace<0>(cur.inner_cur, flux::first(self.pattern_)); | |
} else { | |
break; | |
} | |
} | |
} | |
} | |
public: | |
using value_type = std::common_type_t<value_t<InnerSeq>, value_t<Pattern>>; | |
static constexpr auto first(self_t& self) -> cursor_type | |
{ | |
cursor_type cur(flux::first(self.base_)); | |
if (!flux::is_last(self.base_, cur.outer_cur)) { | |
self.inner_.emplace(flux::read_at(self.base_, cur.outer_cur)); | |
variant_emplace<1>(cur.inner_cur, flux::first(*self.inner_)); | |
satisfy(self, cur); | |
} | |
return cur; | |
} | |
static constexpr auto is_last(self_t& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.outer_cur); | |
} | |
static constexpr auto inc(self_t& self, cursor_type& cur) -> void | |
{ | |
if (cur.inner_cur.index() == 0) { | |
flux::inc(self.pattern_, std::get<0>(cur.inner_cur)); | |
} else { | |
FLUX_ASSERT(self.inner_.has_value()); | |
flux::inc(*self.inner_, std::get<1>(cur.inner_cur)); | |
} | |
satisfy(self, cur); | |
} | |
static constexpr auto read_at(self_t& self, cursor_type const& cur) | |
-> element_type | |
{ | |
if (cur.inner_cur.index() == 0) { | |
return static_cast<element_type>( | |
flux::read_at(self.pattern_, std::get<0>(cur.inner_cur))); | |
} else { | |
FLUX_ASSERT(self.inner_.has_value()); | |
return static_cast<element_type>( | |
flux::read_at(*self.inner_, std::get<1>(cur.inner_cur))); | |
} | |
} | |
static constexpr auto move_at(self_t& self, cursor_type const& cur) | |
-> rvalue_element_type | |
{ | |
if (cur.inner_cur.index() == 0) { | |
return static_cast<rvalue_element_type>( | |
flux::move_at(self.pattern_, std::get<0>(cur.inner_cur))); | |
} else { | |
FLUX_ASSERT(self.inner_.has_value()); | |
return static_cast<rvalue_element_type>( | |
flux::move_at(*self.inner_, std::get<1>(cur.inner_cur))); | |
} | |
} | |
static constexpr auto last(self_t& self) -> cursor_type | |
requires bounded_sequence<Base> | |
{ | |
return cursor_type{.outer_cur = flux::last(self)}; | |
} | |
}; | |
}; | |
template <multipass_sequence Base, multipass_sequence Pattern> | |
requires std::is_lvalue_reference_v<element_t<Base>> && | |
multipass_sequence<element_t<Base>> | |
struct flatten_with_adaptor<Base, Pattern> | |
: inline_sequence_base<flatten_with_adaptor<Base, Pattern>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Pattern pattern_; | |
public: | |
constexpr flatten_with_adaptor(decays_to<Base> auto&& base, | |
decays_to<Pattern> auto&& pattern) | |
: base_(FLUX_FWD(base)), | |
pattern_(FLUX_FWD(pattern)) | |
{} | |
constexpr auto iterate() | |
{ | |
return detail::flatten_with_iteration_context<iteration_context_t<Base>, | |
decltype(flux::mut_ref(pattern_))>{ | |
.base_ctx = flux::iterate(base_), .pattern = flux::mut_ref(pattern_)}; | |
} | |
constexpr auto iterate() const | |
requires iterable<Base const> && iterable<iterable_element_t<Base const>> | |
{ | |
return detail::flatten_with_iteration_context<iteration_context_t<Base const>, | |
decltype(flux::ref(pattern_))>{ | |
.base_ctx = flux::iterate(base_), .pattern = flux::ref(pattern_)}; | |
} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
using InnerSeq = element_t<Base>; | |
template <typename Self> | |
static constexpr bool can_flatten = [] () consteval { | |
if constexpr (std::is_const_v<Self>) { | |
return multipass_sequence<Base const> && | |
std::same_as<element_t<Base const>, std::remove_reference_t<InnerSeq> const&> && | |
multipass_sequence<InnerSeq const> && | |
multipass_sequence<Pattern const>; | |
} else { | |
return true; | |
} | |
}(); | |
struct cursor_type { | |
cursor_t<Base> outer_cur; | |
std::variant<cursor_t<Pattern>, cursor_t<InnerSeq>> inner_cur{}; | |
friend auto operator==(cursor_type const&, cursor_type const&) -> bool = default; | |
}; | |
static constexpr auto satisfy(auto& self, cursor_type& cur) -> void | |
{ | |
while (true) { | |
if (cur.inner_cur.index() == 0) { | |
if (!flux::is_last(self.pattern_, std::get<0>(cur.inner_cur))) { | |
break; | |
} | |
auto& inner = flux::read_at(self.base_, cur.outer_cur); | |
variant_emplace<1>(cur.inner_cur, flux::first(inner)); | |
} else { | |
auto& inner = flux::read_at(self.base_, cur.outer_cur); | |
if (!flux::is_last(inner, std::get<1>(cur.inner_cur))) { | |
break; | |
} | |
flux::inc(self.base_, cur.outer_cur); | |
variant_emplace<0>(cur.inner_cur, flux::first(self.pattern_)); | |
if (flux::is_last(self.base_, cur.outer_cur)) { | |
break; | |
} | |
} | |
} | |
} | |
public: | |
using value_type = std::common_type_t<value_t<InnerSeq>, value_t<Pattern>>; | |
template <typename Self> | |
requires can_flatten<Self> | |
static constexpr auto first(Self& self) -> cursor_type | |
{ | |
cursor_type cur{.outer_cur = flux::first(self.base_)}; | |
if (!flux::is_last(self.base_, cur.outer_cur)) { | |
auto& inner = flux::read_at(self.base_, cur.outer_cur); | |
variant_emplace<1>(cur.inner_cur, flux::first(inner)); | |
} | |
satisfy(self, cur); | |
return cur; | |
} | |
template <typename Self> | |
requires can_flatten<Self> | |
static constexpr auto is_last(Self& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.outer_cur); | |
} | |
template <typename Self> | |
requires can_flatten<Self> | |
static constexpr auto read_at(Self& self, cursor_type const& cur) | |
-> decltype(auto) | |
{ | |
using R = std::common_reference_t< | |
element_t<element_t<decltype((self.base_))>>, | |
element_t<decltype((self.pattern_))>>; | |
if (cur.inner_cur.index() == 0) { | |
return static_cast<R>(flux::read_at(self.pattern_, std::get<0>(cur.inner_cur))); | |
} else { | |
auto& inner = flux::read_at(self.base_, cur.outer_cur); | |
return static_cast<R>(flux::read_at(inner, std::get<1>(cur.inner_cur))); | |
} | |
} | |
template <typename Self> | |
requires can_flatten<Self> | |
static constexpr auto move_at(Self& self, cursor_type const& cur) | |
-> decltype(auto) | |
{ | |
using R = std::common_reference_t< | |
rvalue_element_t<element_t<decltype((self.base_))>>, | |
rvalue_element_t<decltype((self.pattern_))>>; | |
if (cur.inner_cur.index() == 0) { | |
return static_cast<R>(flux::move_at(self.pattern_, std::get<0>(cur.inner_cur))); | |
} else { | |
auto& inner = flux::read_at(self.base_, cur.outer_cur); | |
return static_cast<R>(flux::move_at(inner, std::get<1>(cur.inner_cur))); | |
} | |
} | |
template <typename Self> | |
requires can_flatten<Self> | |
static constexpr auto inc(Self& self, cursor_type& cur) -> void | |
{ | |
if (cur.inner_cur.index() == 0) { | |
flux::inc(self.pattern_, std::get<0>(cur.inner_cur)); | |
} else { | |
auto& inner = flux::read_at(self.base_, cur.outer_cur); | |
flux::inc(inner, std::get<1>(cur.inner_cur)); | |
} | |
satisfy(self, cur); | |
} | |
template <typename Self> | |
requires can_flatten<Self> && bounded_sequence<Base> | |
static constexpr auto last(Self& self) -> cursor_type | |
{ | |
return cursor_type{.outer_cur = flux::last(self.base_)}; | |
} | |
template <typename Self> | |
requires can_flatten<Self> && | |
bidirectional_sequence<Base> && | |
bidirectional_sequence<InnerSeq> && | |
bounded_sequence<InnerSeq> && | |
bidirectional_sequence<Pattern> && | |
bounded_sequence<Pattern> | |
static constexpr auto dec(Self& self, cursor_type& cur) -> void | |
{ | |
if (flux::is_last(self.base_, cur.outer_cur)) { | |
flux::dec(self.base_, cur.outer_cur); | |
auto& inner = flux::read_at(self.base_, cur.outer_cur); | |
variant_emplace<1>(cur.inner_cur, flux::last(inner)); | |
} | |
while (true) { | |
if (cur.inner_cur.index() == 0) { | |
if (std::get<0>(cur.inner_cur) == flux::first(self.pattern_)) { | |
flux::dec(self.base_, cur.outer_cur); | |
auto& inner = flux::read_at(self.base_, cur.outer_cur); | |
variant_emplace<1>(cur.inner_cur, flux::last(inner)); | |
} else { | |
break; | |
} | |
} else { | |
auto& inner = flux::read_at(self.base_, cur.outer_cur); | |
if (std::get<1>(cur.inner_cur) == flux::first(inner)) { | |
variant_emplace<0>(cur.inner_cur, flux::last(self.pattern_)); | |
} else { | |
break; | |
} | |
} | |
} | |
if (cur.inner_cur.index() == 0) { | |
flux::dec(self.pattern_, std::get<0>(cur.inner_cur)); | |
} else { | |
auto& inner = flux::read_at(self.base_, cur.outer_cur); | |
flux::dec(inner, std::get<1>(cur.inner_cur)); | |
} | |
} | |
}; | |
}; | |
struct flatten_with_fn { | |
template <adaptable_iterable It, adaptable_sequence Pattern> | |
requires iterable<iterable_element_t<It>> && multipass_sequence<Pattern> | |
&& flatten_with_compatible<iterable_element_t<It>, Pattern> | |
constexpr auto operator()(It&& it, Pattern&& pattern) const -> iterable auto | |
{ | |
return flatten_with_adaptor<std::decay_t<It>, std::decay_t<Pattern>>(FLUX_FWD(it), | |
FLUX_FWD(pattern)); | |
} | |
template <adaptable_iterable It> | |
requires sequence<iterable_element_t<It>> | |
&& std::movable<iterable_value_t<iterable_element_t<It>>> | |
constexpr auto operator()(It&& it, iterable_value_t<iterable_element_t<It>> value) const | |
-> iterable auto | |
{ | |
return (*this)(FLUX_FWD(it), flux::single(std::move(value))); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto flatten_with = detail::flatten_with_fn{}; | |
template <typename Derived> | |
template <adaptable_sequence Pattern> | |
requires sequence<element_t<Derived>> && | |
multipass_sequence<Pattern> && | |
detail::flatten_with_compatible<element_t<Derived>, Pattern> | |
constexpr auto inline_sequence_base<Derived>::flatten_with(Pattern&& pattern) && | |
{ | |
return flux::flatten_with(std::move(derived()), FLUX_FWD(pattern)); | |
} | |
template <typename Derived> | |
template <typename Value> | |
requires sequence<element_t<Derived>> && | |
std::constructible_from<value_t<element_t<Derived>>, Value&&> | |
constexpr auto inline_sequence_base<Derived>::flatten_with(Value value) && | |
{ | |
return flux::flatten_with(std::move(derived()), std::move(value)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_FLATTEN_WITH_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_MASK_HPP_INCLUDED | |
#define FLUX_ADAPTOR_MASK_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <iterable Base, iterable Mask> | |
struct mask_adaptor : inline_sequence_base<mask_adaptor<Base, Mask>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Mask mask_; | |
template <typename BaseCtx, typename MaskCtx> | |
struct context_type : immovable { | |
BaseCtx base_ctx; | |
MaskCtx mask_ctx; | |
using element_type = typename BaseCtx::element_type; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
while (true) { | |
auto base_elem = next_element(base_ctx); | |
auto mask_elem = next_element(mask_ctx); | |
if (!base_elem || !mask_elem) { | |
return iteration_result::complete; | |
} | |
if (*mask_elem) { | |
if (!pred(*std::move(base_elem))) { | |
return iteration_result::incomplete; | |
} | |
} | |
} | |
} | |
}; | |
public: | |
constexpr mask_adaptor(decays_to<Base> auto&& base, decays_to<Mask> auto&& mask) | |
: base_(FLUX_FWD(base)), | |
mask_(FLUX_FWD(mask)) | |
{ | |
} | |
[[nodiscard]] constexpr auto iterate() | |
{ | |
return context_type<iteration_context_t<Base>, iteration_context_t<Mask>>{ | |
.base_ctx = flux::iterate(base_), .mask_ctx = flux::iterate(mask_)}; | |
} | |
[[nodiscard]] constexpr auto iterate() const | |
requires iterable<Base const> && iterable<Mask const> | |
{ | |
return context_type<iteration_context_t<Base const>, iteration_context_t<Mask const>>{ | |
.base_ctx = flux::iterate(base_), .mask_ctx = flux::iterate(mask_)}; | |
} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> base_cur; | |
cursor_t<Mask> mask_cur; | |
friend auto operator==(cursor_type const&, cursor_type const&) -> bool | |
requires std::equality_comparable<cursor_t<Base>> && | |
std::equality_comparable<cursor_t<Mask>> | |
= default; | |
}; | |
template <typename Self> | |
static inline constexpr bool maybe_const_iterable = | |
std::is_const_v<Self> | |
? sequence<Base const> && sequence<Mask const> | |
: true; | |
public: | |
using value_type = value_t<Base>; | |
static inline constexpr bool is_infinite = | |
infinite_sequence<Base> && infinite_sequence<Mask>; | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto first(Self& self) -> cursor_type | |
{ | |
auto base_cur = flux::first(self.base_); | |
auto mask_cur = flux::first(self.mask_); | |
while (!flux::is_last(self.base_, base_cur) && !flux::is_last(self.mask_, mask_cur)) { | |
if (static_cast<bool>(flux::read_at(self.mask_, mask_cur))) { | |
break; | |
} | |
flux::inc(self.base_, base_cur); | |
flux::inc(self.mask_, mask_cur); | |
} | |
return cursor_type{std::move(base_cur), std::move(mask_cur)}; | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto is_last(Self& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.base_cur) || | |
flux::is_last(self.mask_, cur.mask_cur); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto inc(Self& self, cursor_type& cur) -> void | |
{ | |
while (!flux::is_last(self.base_, flux::inc(self.base_, cur.base_cur)) && | |
!flux::is_last(self.mask_, flux::inc(self.mask_, cur.mask_cur))) { | |
if (static_cast<bool>(flux::read_at(self.mask_, cur.mask_cur))) { | |
break; | |
} | |
} | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto read_at(Self& self, cursor_type const& cur) -> decltype(auto) | |
{ | |
return flux::read_at(self.base_, cur.base_cur); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto read_at_unchecked(Self& self, cursor_type const& cur) | |
-> decltype(auto) | |
{ | |
return flux::read_at_unchecked(self.base_, cur.base_cur); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto move_at(Self& self, cursor_type const& cur) -> decltype(auto) | |
{ | |
return flux::move_at(self.base_, cur.base_cur); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto move_at_unchecked(Self& self, cursor_type const& cur) | |
-> decltype(auto) | |
{ | |
return flux::move_at_unchecked(self.base_, cur.base_cur); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> && | |
bounded_sequence<Base> && | |
bounded_sequence<Mask> | |
static constexpr auto last(Self& self) -> cursor_type | |
{ | |
return cursor_type{.base_cur = flux::last(self.base_), | |
.mask_cur = flux::last(self.mask_)}; | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> && | |
bidirectional_sequence<Base> && | |
bidirectional_sequence<Mask> | |
static constexpr auto dec(Self& self, cursor_type& cur) -> void | |
{ | |
do { | |
flux::dec(self.base_, cur.base_cur); | |
flux::dec(self.mask_, cur.mask_cur); | |
} while (!static_cast<bool>(flux::read_at(self.mask_, cur.mask_cur))); | |
} | |
}; | |
}; | |
struct mask_fn { | |
template <adaptable_iterable Base, adaptable_iterable Mask> | |
requires boolean_testable<iterable_element_t<Mask>> | |
[[nodiscard]] | |
constexpr auto operator()(Base&& base, Mask&& mask) const | |
{ | |
return mask_adaptor<std::decay_t<Base>, std::decay_t<Mask>>( | |
FLUX_FWD(base), FLUX_FWD(mask)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto mask = detail::mask_fn{}; | |
template <typename D> | |
template <adaptable_sequence Mask> | |
requires detail::boolean_testable<element_t<Mask>> | |
constexpr auto inline_sequence_base<D>::mask(Mask&& mask_) && | |
{ | |
return flux::mask(std::move(derived()), FLUX_FWD(mask_)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_MASK_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_READ_ONLY_HPP_INCLUDED | |
#define FLUX_ADAPTOR_READ_ONLY_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename T> | |
struct cast_to_const { | |
constexpr auto operator()(auto&& elem) const -> T { return FLUX_FWD(elem); } | |
}; | |
template <iterable Base> | |
struct read_only_adaptor : map_adaptor<Base, cast_to_const<iterable_const_element_t<Base>>> { | |
private: | |
using map = map_adaptor<Base, cast_to_const<iterable_const_element_t<Base>>>; | |
public: | |
constexpr explicit read_only_adaptor(decays_to<Base> auto&& base) | |
: map(FLUX_FWD(base), cast_to_const<iterable_const_element_t<Base>>{}) | |
{} | |
struct flux_sequence_traits : map::flux_sequence_traits { | |
private: | |
using const_rvalue_element_t = std::common_reference_t< | |
value_t<Base> const&&, rvalue_element_t<Base>>; | |
public: | |
using value_type = value_t<Base>; | |
static constexpr auto move_at(auto& self, cursor_t<Base> const& cur) | |
-> const_rvalue_element_t | |
{ | |
return static_cast<const_rvalue_element_t>(flux::move_at(self.base(), cur)); | |
} | |
static constexpr auto move_at_unchecked(auto& self, cursor_t<Base> const& cur) | |
-> const_rvalue_element_t | |
{ | |
return static_cast<const_rvalue_element_t>(flux::move_at_unchecked(self.base(), cur)); | |
} | |
static constexpr auto data(auto& self) | |
requires contiguous_sequence<Base> | |
{ | |
using P = std::add_pointer_t<std::remove_reference_t<const_element_t<Base>>>; | |
return static_cast<P>(flux::data(self.base())); | |
} | |
}; | |
}; | |
struct read_only_fn { | |
template <adaptable_iterable It> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it) const -> iterable auto | |
{ | |
if constexpr (std::same_as<iterable_element_t<It>, iterable_const_element_t<It>>) { | |
return FLUX_FWD(it); | |
} else { | |
return read_only_adaptor<std::decay_t<It>>(FLUX_FWD(it)); | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto read_only = detail::read_only_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::read_only() && | |
{ | |
return flux::read_only(std::move(derived())); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_READ_ONlY_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_SCAN_HPP_INCLUDED | |
#define FLUX_ADAPTOR_SCAN_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_FOLD_HPP_INCLUDED | |
#define FLUX_ALGORITHM_FOLD_HPP_INCLUDED | |
// Copyright (c) 2025 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_FOR_EACH_WHILE_HPP_INCLUDED | |
#define FLUX_ALGORITHM_FOR_EACH_WHILE_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
struct for_each_while_t { | |
template <iterable It, callable_mut<bool(iterable_element_t<It>)> Pred> | |
constexpr auto operator()(It&& it, Pred&& pred) const -> iteration_result | |
{ | |
iteration_context auto ctx = iterate(it); | |
return ctx.run_while(FLUX_FWD(pred)); | |
} | |
}; | |
FLUX_EXPORT inline constexpr for_each_while_t for_each_while {}; | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_FOR_EACH_WHILE_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
struct fold_t { | |
template <iterable It, typename Func, std::movable Init = iterable_value_t<It>, | |
typename R = fold_result_t<It, Func, Init>> | |
requires std::invocable<Func&, Init, element_t<It>> | |
&& std::invocable<Func&, R, element_t<It>> && std::convertible_to<Init, R> | |
&& std::assignable_from<Init&, std::invoke_result_t<Func&, R, element_t<It>>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Func func, Init init = Init{}) const -> R | |
{ | |
R init_ = R(std::move(init)); | |
for_each_while(it, [&func, &init_](auto&& elem) { | |
init_ = std::invoke(func, std::move(init_), FLUX_FWD(elem)); | |
return true; | |
}); | |
return init_; | |
} | |
}; | |
FLUX_EXPORT inline constexpr auto fold = fold_t{}; | |
FLUX_EXPORT | |
struct fold_first_t { | |
template <iterable It, typename Func, typename V = iterable_value_t<It>> | |
requires std::invocable<Func&, V, iterable_element_t<It>> | |
&& std::assignable_from<iterable_value_t<It>&, | |
std::invoke_result_t<Func&, V&&, iterable_element_t<It>>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Func func) const -> flux::optional<V> | |
{ | |
iteration_context auto ctx = iterate(it); | |
auto opt = next_element(ctx); | |
if (!opt.has_value()) { | |
return flux::nullopt; | |
} | |
V init = std::move(opt).value(); | |
run_while(ctx, [&](auto&& elem) { | |
init = std::invoke(func, std::move(init), FLUX_FWD(elem)); | |
return loop_continue; | |
}); | |
return flux::optional<V>(std::in_place, std::move(init)); | |
} | |
}; | |
FLUX_EXPORT inline constexpr fold_first_t fold_first{}; | |
namespace detail { | |
// Workaround libc++18 invoke() bug: https://github.com/llvm/llvm-project/issues/106428 | |
consteval bool libcpp_fold_invoke_workaround_required() | |
{ | |
#if defined(_LIBCPP_VERSION) | |
return _LIBCPP_VERSION >= 180000 && _LIBCPP_VERSION < 190000; | |
#else | |
return false; | |
#endif | |
} | |
} // namespace detail | |
FLUX_EXPORT | |
struct sum_t { | |
template <iterable It> | |
requires std::invocable<fold_t, It, std::plus<>> && requires { iterable_value_t<It>(0); } | |
[[nodiscard]] | |
constexpr auto operator()(It&& it) const -> iterable_value_t<It> | |
{ | |
if constexpr (num::integral<iterable_value_t<It>>) { | |
if constexpr (detail::libcpp_fold_invoke_workaround_required()) { | |
auto add = []<typename T>(T lhs, T rhs) -> T { return num::add(lhs, rhs); }; | |
return fold(FLUX_FWD(it), add, iterable_value_t<It>(0)); | |
} else { | |
return fold(FLUX_FWD(it), num::add, iterable_value_t<It>(0)); | |
} | |
} else { | |
return fold(FLUX_FWD(it), std::plus<>{}, iterable_value_t<It>(0)); | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr auto sum = sum_t{}; | |
FLUX_EXPORT | |
struct product_t { | |
template <iterable It> | |
requires std::invocable<fold_t, It, std::multiplies<>> | |
&& requires { iterable_value_t<It>(1); } | |
[[nodiscard]] | |
constexpr auto operator()(It&& it) const -> iterable_value_t<It> | |
{ | |
if constexpr (num::integral<iterable_value_t<It>>) { | |
if constexpr (detail::libcpp_fold_invoke_workaround_required()) { | |
auto mul = []<typename T>(T lhs, T rhs) -> T { return num::mul(lhs, rhs); }; | |
return fold(FLUX_FWD(it), mul, iterable_value_t<It>(1)); | |
} else { | |
return fold(FLUX_FWD(it), num::mul, iterable_value_t<It>(1)); | |
} | |
} else { | |
return fold(FLUX_FWD(it), std::multiplies<>{}, iterable_value_t<It>(1)); | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr auto product = product_t{}; | |
template <typename Derived> | |
template <typename D, typename Func, typename Init> | |
requires foldable<Derived, Func, Init> | |
[[nodiscard]] | |
constexpr auto inline_sequence_base<Derived>::fold(Func func, Init init) | |
-> fold_result_t<D, Func, Init> | |
{ | |
return flux::fold(derived(), std::move(func), std::move(init)); | |
} | |
template <typename Derived> | |
template <typename D, typename Func> | |
requires std::invocable<Func&, value_t<D>, element_t<D>> | |
&& std::assignable_from<value_t<D>&, std::invoke_result_t<Func&, value_t<D>, element_t<D>>> | |
constexpr auto inline_sequence_base<Derived>::fold_first(Func func) | |
{ | |
return flux::fold_first(derived(), std::move(func)); | |
} | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::sum() | |
requires foldable<D, std::plus<>, value_t<D>> && std::default_initializable<value_t<D>> | |
{ | |
return flux::sum(derived()); | |
} | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::product() | |
requires foldable<D, std::multiplies<>, value_t<D>> && requires { value_t<D>(1); } | |
{ | |
return flux::product(derived()); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_FOLD_HPP_INCLUDED | |
#include <utility> // for std::as_const | |
namespace flux { | |
namespace detail { | |
enum class scan_mode { | |
inclusive, | |
exclusive | |
}; | |
template <scan_mode> | |
struct scan_cursor_base {}; | |
template <> | |
struct scan_cursor_base<scan_mode::exclusive> { | |
bool is_last; | |
}; | |
template <typename Base, typename Func, typename R, scan_mode Mode> | |
struct scan_adaptor : inline_sequence_base<scan_adaptor<Base, Func, R, Mode>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Func func_; | |
FLUX_NO_UNIQUE_ADDRESS R accum_; | |
struct context_type : immovable { | |
scan_adaptor* parent; | |
iteration_context_t<Base> base_ctx; | |
bool first = true; | |
using element_type = R const&; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
if constexpr (Mode == scan_mode::exclusive) { | |
if (first) { | |
first = false; | |
if (!pred(std::as_const(parent->accum_))) { | |
return iteration_result::incomplete; | |
} | |
} | |
} | |
return base_ctx.run_while([&](auto&& elem) { | |
parent->accum_ | |
= std::invoke(parent->func_, std::move(parent->accum_), FLUX_FWD(elem)); | |
return pred(std::as_const(parent->accum_)); | |
}); | |
} | |
}; | |
public: | |
constexpr scan_adaptor(decays_to<Base> auto&& base, Func&& func, auto&& init) | |
: base_(FLUX_FWD(base)), | |
func_(std::move(func)), | |
accum_(FLUX_FWD(init)) | |
{} | |
scan_adaptor(scan_adaptor&&) = default; | |
scan_adaptor& operator=(scan_adaptor&&) = default; | |
constexpr auto iterate() -> context_type | |
{ | |
return context_type{.parent = this, .base_ctx = flux::iterate(base_)}; | |
} | |
constexpr auto size() -> int_t | |
requires sized_iterable<Base> | |
{ | |
if constexpr (Mode == scan_mode::exclusive) { | |
return num::add(flux::iterable_size(base_), int_t{1}); | |
} else { | |
return flux::iterable_size(base_); | |
} | |
} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type : private scan_cursor_base<Mode> { | |
cursor_type(cursor_type&&) = default; | |
cursor_type& operator=(cursor_type&&) = default; | |
private: | |
friend struct flux_sequence_traits; | |
constexpr explicit cursor_type(cursor_t<Base>&& base_cur) | |
: base_cur(std::move(base_cur)) | |
{} | |
cursor_t<Base> base_cur; | |
}; | |
using self_t = scan_adaptor; | |
static constexpr auto update(self_t& self, cursor_t<Base> const& cur) -> void | |
{ | |
if (!flux::is_last(self.base_, cur)) { | |
self.accum_ = std::invoke(self.func_, std::move(self.accum_), | |
flux::read_at(self.base_, cur)); | |
} | |
} | |
public: | |
static inline constexpr bool is_infinite = infinite_sequence<Base>; | |
static constexpr auto first(self_t& self) -> cursor_type | |
{ | |
auto cur = flux::first(self.base_); | |
if constexpr (Mode == scan_mode::inclusive) { | |
update(self, cur); | |
return cursor_type(std::move(cur)); | |
} else if constexpr (Mode == scan_mode::exclusive) { | |
bool last = flux::is_last(self.base_, cur); | |
cursor_type out = cursor_type(std::move(cur)); | |
out.is_last = last; | |
return out; | |
} | |
} | |
static constexpr auto is_last(self_t& self, cursor_type const& cur) -> bool | |
{ | |
if constexpr (Mode == scan_mode::exclusive) { | |
return cur.is_last; | |
} else { | |
return flux::is_last(self.base_, cur.base_cur); | |
} | |
} | |
static constexpr auto inc(self_t& self, cursor_type& cur) -> void | |
{ | |
if constexpr (Mode == scan_mode::inclusive) { | |
flux::inc(self.base_, cur.base_cur); | |
update(self, cur.base_cur); | |
} else { | |
update(self, cur.base_cur); | |
if (flux::is_last(self.base_, cur.base_cur)) { | |
cur.is_last = true; | |
} else { | |
flux::inc(self.base_, cur.base_cur); | |
} | |
} | |
} | |
static constexpr auto read_at(self_t& self, cursor_type const&) -> R const& | |
{ | |
return self.accum_; | |
} | |
static constexpr auto last(self_t& self) -> cursor_type | |
requires bounded_sequence<Base> | |
{ | |
auto cur = cursor_type(flux::last(self.base_)); | |
if constexpr (Mode == scan_mode::exclusive) { | |
cur.is_last = true; | |
} | |
return cur; | |
} | |
static constexpr auto size(self_t& self) -> int_t | |
requires sized_sequence<Base> | |
{ | |
if constexpr (Mode == scan_mode::exclusive) { | |
return num::add(flux::size(self.base_), int_t {1}); | |
} else { | |
return flux::size(self.base_); | |
} | |
} | |
static constexpr auto for_each_while(self_t& self, auto&& pred) -> cursor_type | |
requires (Mode != scan_mode::exclusive) | |
{ | |
return cursor_type(flux::seq_for_each_while(self.base_, [&](auto&& elem) { | |
self.accum_ = std::invoke(self.func_, std::move(self.accum_), FLUX_FWD(elem)); | |
return std::invoke(pred, std::as_const(self.accum_)); | |
})); | |
} | |
using default_sequence_traits::for_each_while; // when Mode == exclusive | |
}; | |
}; | |
struct scan_fn { | |
template <adaptable_iterable It, typename Func, std::movable Init = iterable_value_t<It>, | |
typename R = fold_result_t<It, Func, Init>> | |
requires foldable<It, Func, Init> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Func func, Init init = Init{}) const -> iterable auto | |
{ | |
return scan_adaptor<std::decay_t<It>, Func, R, scan_mode::inclusive>( | |
FLUX_FWD(it), std::move(func), std::move(init)); | |
} | |
}; | |
struct prescan_fn { | |
template <adaptable_iterable It, typename Func, std::movable Init, | |
typename R = fold_result_t<It, Func, Init>> | |
requires foldable<It, Func, Init> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Func func, Init init) const -> iterable auto | |
{ | |
return scan_adaptor<std::decay_t<It>, Func, R, scan_mode::exclusive>( | |
FLUX_FWD(it), std::move(func), std::move(init)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto scan = detail::scan_fn{}; | |
FLUX_EXPORT inline constexpr auto prescan = detail::prescan_fn{}; | |
template <typename Derived> | |
template <typename D, typename Func, typename Init> | |
requires foldable<Derived, Func, Init> | |
constexpr auto inline_sequence_base<Derived>::scan(Func func, Init init) && | |
{ | |
return flux::scan(std::move(derived()), std::move(func), std::move(init)); | |
} | |
template <typename Derived> | |
template <typename Func, typename Init> | |
requires foldable<Derived, Func, Init> | |
constexpr auto inline_sequence_base<Derived>::prescan(Func func, Init init) && | |
{ | |
return flux::prescan(std::move(derived()), std::move(func), std::move(init)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_SCAN_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_SCAN_FIRST_HPP_INCLUDED | |
#define FLUX_ADAPTOR_SCAN_FIRST_HPP_INCLUDED | |
#include <utility> // for std::as_const | |
namespace flux { | |
namespace detail { | |
template <typename Base, typename Func, typename R> | |
struct scan_first_adaptor : inline_sequence_base<scan_first_adaptor<Base, Func, R>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Func func_; | |
flux::optional<R> accum_; | |
struct context_type : immovable { | |
scan_first_adaptor* parent; | |
iteration_context_t<Base> base_ctx; | |
using element_type = R const&; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
if (!parent->accum_.has_value()) { | |
base_ctx.run_while([&](auto&& elem) { | |
parent->accum_.emplace(FLUX_FWD(elem)); | |
return false; | |
}); | |
if (!parent->accum_.has_value()) { | |
return iteration_result::complete; | |
} | |
if (!std::invoke(pred, std::as_const(*parent->accum_))) { | |
return iteration_result::incomplete; | |
} | |
} | |
FLUX_DEBUG_ASSERT(parent->accum_.has_value()); | |
return base_ctx.run_while([&](auto&& elem) { | |
parent->accum_.emplace( | |
std::invoke(parent->func_, *std::move(parent->accum_), FLUX_FWD(elem))); | |
return std::invoke(pred, std::as_const(*parent->accum_)); | |
}); | |
} | |
}; | |
public: | |
constexpr scan_first_adaptor(decays_to<Base> auto&& base, Func&& func) | |
: base_(FLUX_FWD(base)), | |
func_(std::move(func)) | |
{ | |
} | |
scan_first_adaptor(scan_first_adaptor&&) = default; | |
scan_first_adaptor& operator=(scan_first_adaptor&&) = default; | |
[[nodiscard]] constexpr auto iterate() -> context_type | |
{ | |
return context_type{.parent = this, .base_ctx = flux::iterate(base_)}; | |
} | |
[[nodiscard]] constexpr auto size() -> int_t | |
requires sized_iterable<Base> | |
{ | |
return flux::iterable_size(base_); | |
} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_type(cursor_type&&) = default; | |
cursor_type& operator=(cursor_type&&) = default; | |
private: | |
friend struct flux_sequence_traits; | |
constexpr explicit cursor_type(cursor_t<Base>&& base_cur) | |
: base_cur(std::move(base_cur)) | |
{} | |
cursor_t<Base> base_cur; | |
}; | |
using self_t = scan_first_adaptor; | |
public: | |
static constexpr auto first(self_t& self) -> cursor_type | |
{ | |
auto cur = flux::first(self.base_); | |
if (!flux::is_last(self.base_, cur)) { | |
self.accum_.emplace(flux::read_at(self.base_, cur)); | |
} | |
return cursor_type(std::move(cur)); | |
} | |
static constexpr auto is_last(self_t& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.base_cur); | |
} | |
static constexpr auto inc(self_t& self, cursor_type& cur) -> void | |
{ | |
flux::inc(self.base_, cur.base_cur); | |
if (!flux::is_last(self.base_, cur.base_cur)) { | |
self.accum_.emplace( | |
std::invoke(self.func_, | |
std::move(self.accum_.value_unchecked()), | |
flux::read_at(self.base_, cur.base_cur))); | |
} | |
} | |
static constexpr auto read_at(self_t& self, cursor_type const&) -> R const& | |
{ | |
return self.accum_.value(); | |
} | |
static constexpr auto read_at_unchecked(self_t& self, cursor_type const&) | |
-> R const& | |
{ | |
return self.accum_.value_unchecked(); | |
} | |
static constexpr auto last(self_t& self) -> cursor_type | |
requires bounded_sequence<Base> | |
{ | |
return cursor_type(flux::last(self.base_)); | |
} | |
static constexpr auto size(self_t& self) -> int_t | |
requires sized_sequence<Base> | |
{ | |
return flux::size(self.base_); | |
} | |
static constexpr auto for_each_while(self_t& self, auto&& pred) -> cursor_type | |
{ | |
return cursor_type(flux::seq_for_each_while(self.base_, [&](auto&& elem) { | |
if (self.accum_.has_value()) { | |
self.accum_.emplace( | |
std::invoke(self.func_, | |
std::move(self.accum_.value_unchecked()), | |
FLUX_FWD(elem))); | |
} else { | |
self.accum_.emplace(FLUX_FWD(elem)); | |
} | |
return std::invoke(pred, self.accum_.value_unchecked()); | |
})); | |
} | |
}; | |
}; | |
struct scan_first_fn { | |
template <adaptable_iterable It, typename Func> | |
requires foldable<It, Func, iterable_element_t<It>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Func func) const -> iterable auto | |
{ | |
using R = fold_result_t<It, Func, iterable_element_t<It>>; | |
return scan_first_adaptor<std::decay_t<It>, Func, R>(FLUX_FWD(it), std::move(func)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto scan_first = detail::scan_first_fn{}; | |
template <typename Derived> | |
template <typename Func> | |
requires foldable<Derived, Func, element_t<Derived>> | |
constexpr auto inline_sequence_base<Derived>::scan_first(Func func) && | |
{ | |
return flux::scan_first(std::move(derived()), std::move(func)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_SCAN_FIRST_HPP_INCLUDED | |
// Copyright (c) 2023 Jiri Nytra (jiri.nytra at gmail 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 FLUX_ADAPTOR_SET_ADAPTORS_HPP_INCLUDED | |
#define FLUX_ADAPTOR_SET_ADAPTORS_HPP_INCLUDED | |
#include <utility> | |
namespace flux::detail { | |
template <sequence Base1, sequence Base2, typename Cmp> | |
struct set_union_adaptor | |
: flux::inline_sequence_base<set_union_adaptor<Base1, Base2, Cmp>> | |
{ | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base1 base1_; | |
FLUX_NO_UNIQUE_ADDRESS Base2 base2_; | |
FLUX_NO_UNIQUE_ADDRESS Cmp cmp_; | |
public: | |
constexpr set_union_adaptor(decays_to<Base1> auto&& base1, decays_to<Base2> auto&& base2, Cmp cmp) | |
: base1_(FLUX_FWD(base1)), | |
base2_(FLUX_FWD(base2)), | |
cmp_(cmp) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base1> base1_cursor; | |
cursor_t<Base2> base2_cursor; | |
enum : bool {first, second} active_ = first; | |
friend auto operator==(cursor_type const&, cursor_type const&) -> bool | |
requires std::equality_comparable<cursor_t<Base1>> && | |
std::equality_comparable<cursor_t<Base2>> | |
= default; | |
}; | |
template <typename Self> | |
static inline constexpr bool maybe_const_iterable | |
= std::is_const_v<Self> ? (flux::sequence<Base1 const> && flux::sequence<Base2 const>) : true; | |
template <typename Self> | |
static constexpr void update(Self& self, cursor_type& cur) { | |
if (flux::is_last(self.base1_, cur.base1_cursor)) { | |
cur.active_ = cursor_type::second; | |
return; | |
} | |
if (flux::is_last(self.base2_, cur.base2_cursor)) { | |
cur.active_ = cursor_type::first; | |
return; | |
} | |
auto r = std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), | |
flux::read_at(self.base2_, cur.base2_cursor)); | |
if (r == std::weak_ordering::greater) { | |
cur.active_ = cursor_type::second; | |
return; | |
} else if (r == std::weak_ordering::equivalent) { | |
flux::inc(self.base2_, cur.base2_cursor); | |
} | |
cur.active_ = cursor_type::first; | |
} | |
public: | |
using value_type = std::common_type_t<value_t<Base1>, value_t<Base2>>; | |
inline static constexpr bool is_infinite = flux::infinite_sequence<Base1> || | |
flux::infinite_sequence<Base2>; | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto first(Self& self) -> cursor_type | |
{ | |
auto cur = cursor_type{.base1_cursor = flux::first(self.base1_), | |
.base2_cursor = flux::first(self.base2_)}; | |
update(self, cur); | |
return cur; | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto is_last(Self& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base1_, cur.base1_cursor) && | |
flux::is_last(self.base2_, cur.base2_cursor); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto inc(Self& self, cursor_type& cur) -> void | |
{ | |
if (cur.active_ == cursor_type::first) { | |
flux::inc(self.base1_, cur.base1_cursor); | |
} else { | |
flux::inc(self.base2_, cur.base2_cursor); | |
} | |
update(self, cur); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto read_at(Self& self, cursor_type const& cur) | |
-> std::common_reference_t<decltype(flux::read_at(self.base1_, cur.base1_cursor)), | |
decltype(flux::read_at(self.base2_, cur.base2_cursor))> | |
{ | |
if (cur.active_ == cursor_type::first) { | |
return flux::read_at(self.base1_, cur.base1_cursor); | |
} else { | |
return flux::read_at(self.base2_, cur.base2_cursor); | |
} | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> && | |
bounded_sequence<Base1> && bounded_sequence<Base2> | |
static constexpr auto last(Self& self) -> cursor_type | |
{ | |
return cursor_type{flux::last(self.base1_), flux::last(self.base2_), cursor_type::second}; | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto move_at(Self& self, cursor_type const& cur) | |
-> std::common_reference_t<decltype(flux::move_at(self.base1_, cur.base1_cursor)), | |
decltype(flux::move_at(self.base2_, cur.base2_cursor))> | |
{ | |
if (cur.active_ == cursor_type::first) { | |
return flux::move_at(self.base1_, cur.base1_cursor); | |
} else { | |
return flux::move_at(self.base2_, cur.base2_cursor); | |
} | |
} | |
}; | |
}; | |
template <sequence Base1, sequence Base2, typename Cmp> | |
struct set_difference_adaptor | |
: flux::inline_sequence_base<set_difference_adaptor<Base1, Base2, Cmp>> | |
{ | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base1 base1_; | |
FLUX_NO_UNIQUE_ADDRESS Base2 base2_; | |
FLUX_NO_UNIQUE_ADDRESS Cmp cmp_; | |
public: | |
constexpr set_difference_adaptor(decays_to<Base1> auto&& base1, decays_to<Base2> auto&& base2, Cmp cmp) | |
: base1_(FLUX_FWD(base1)), | |
base2_(FLUX_FWD(base2)), | |
cmp_(cmp) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base1> base1_cursor; | |
cursor_t<Base2> base2_cursor; | |
friend auto operator==(cursor_type const&, cursor_type const&) -> bool | |
requires std::equality_comparable<cursor_t<Base1>> && | |
std::equality_comparable<cursor_t<Base2>> | |
= default; | |
}; | |
template <typename Self> | |
static inline constexpr bool maybe_const_iterable | |
= std::is_const_v<Self> ? (flux::sequence<Base1 const> && flux::sequence<Base2 const>) : true; | |
template <typename Self> | |
static constexpr void update(Self& self, cursor_type& cur) { | |
while(not flux::is_last(self.base1_, cur.base1_cursor)) | |
{ | |
if(flux::is_last(self.base2_, cur.base2_cursor)) { | |
return; | |
} | |
auto r = std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), | |
flux::read_at(self.base2_, cur.base2_cursor)); | |
if (r == std::weak_ordering::less) { | |
return; | |
} else if (r == std::weak_ordering::equivalent) { | |
flux::inc(self.base1_, cur.base1_cursor); | |
} | |
flux::inc(self.base2_, cur.base2_cursor); | |
} | |
} | |
public: | |
using value_type = value_t<Base1>; | |
inline static constexpr bool is_infinite = flux::infinite_sequence<Base1>; | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto first(Self& self) -> cursor_type | |
{ | |
auto cur = cursor_type{.base1_cursor = flux::first(self.base1_), | |
.base2_cursor = flux::first(self.base2_)}; | |
update(self, cur); | |
return cur; | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto is_last(Self& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base1_, cur.base1_cursor); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto inc(Self& self, cursor_type& cur) -> void | |
{ | |
flux::inc(self.base1_, cur.base1_cursor); | |
update(self, cur); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto read_at(Self& self, cursor_type const& cur) | |
-> decltype(flux::read_at(self.base1_, cur.base1_cursor)) | |
{ | |
return flux::read_at(self.base1_, cur.base1_cursor); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto move_at(Self& self, cursor_type const& cur) | |
-> decltype(flux::move_at(self.base1_, cur.base1_cursor)) | |
{ | |
return flux::move_at(self.base1_, cur.base1_cursor); | |
} | |
}; | |
}; | |
template <sequence Base1, sequence Base2, typename Cmp> | |
struct set_symmetric_difference_adaptor | |
: flux::inline_sequence_base<set_symmetric_difference_adaptor<Base1, Base2, Cmp>> | |
{ | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base1 base1_; | |
FLUX_NO_UNIQUE_ADDRESS Base2 base2_; | |
FLUX_NO_UNIQUE_ADDRESS Cmp cmp_; | |
public: | |
constexpr set_symmetric_difference_adaptor(decays_to<Base1> auto&& base1, decays_to<Base2> auto&& base2, Cmp cmp) | |
: base1_(FLUX_FWD(base1)), | |
base2_(FLUX_FWD(base2)), | |
cmp_(cmp) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base1> base1_cursor; | |
cursor_t<Base2> base2_cursor; | |
enum : char {first, second, first_done, second_done} state_ = first; | |
friend constexpr auto operator==(cursor_type const& lhs, cursor_type const& rhs) -> bool | |
requires std::equality_comparable<cursor_t<Base1>> && | |
std::equality_comparable<cursor_t<Base2>> | |
{ | |
return lhs.base1_cursor == rhs.base1_cursor && | |
lhs.base2_cursor == rhs.base2_cursor; | |
} | |
}; | |
template <typename Self> | |
static inline constexpr bool maybe_const_iterable | |
= std::is_const_v<Self> ? (flux::sequence<Base1 const> && flux::sequence<Base2 const>) : true; | |
template <typename Self> | |
static constexpr void update(Self& self, cursor_type& cur) { | |
while(not flux::is_last(self.base1_, cur.base1_cursor)) | |
{ | |
if(flux::is_last(self.base2_, cur.base2_cursor)) { | |
cur.state_ = cursor_type::second_done; | |
return; | |
} | |
auto r = std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), | |
flux::read_at(self.base2_, cur.base2_cursor)); | |
if (r == std::weak_ordering::less) { | |
cur.state_ = cursor_type::first; | |
return; | |
} else if (r == std::weak_ordering::greater) { | |
cur.state_ = cursor_type::second; | |
return; | |
} else { | |
flux::inc(self.base1_, cur.base1_cursor); | |
flux::inc(self.base2_, cur.base2_cursor); | |
} | |
} | |
cur.state_ = cursor_type::first_done; | |
} | |
public: | |
using value_type = std::common_type_t<value_t<Base1>, value_t<Base2>>; | |
inline static constexpr bool is_infinite = flux::infinite_sequence<Base1> || | |
flux::infinite_sequence<Base2>; | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto first(Self& self) -> cursor_type | |
{ | |
auto cur = cursor_type{.base1_cursor = flux::first(self.base1_), | |
.base2_cursor = flux::first(self.base2_)}; | |
update(self, cur); | |
return cur; | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto is_last(Self& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base1_, cur.base1_cursor) && | |
flux::is_last(self.base2_, cur.base2_cursor); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto inc(Self& self, cursor_type& cur) -> void | |
{ | |
switch (cur.state_) { | |
case cursor_type::first: | |
flux::inc(self.base1_, cur.base1_cursor); | |
break; | |
case cursor_type::second: | |
flux::inc(self.base2_, cur.base2_cursor); | |
break; | |
case cursor_type::first_done: | |
flux::inc(self.base2_, cur.base2_cursor); | |
return; | |
case cursor_type::second_done: | |
flux::inc(self.base1_, cur.base1_cursor); | |
return; | |
} | |
update(self, cur); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto read_at(Self& self, cursor_type const& cur) | |
-> std::common_reference_t<decltype(flux::read_at(self.base1_, cur.base1_cursor)), | |
decltype(flux::read_at(self.base2_, cur.base2_cursor))> | |
{ | |
using R = std::common_reference_t<decltype(flux::read_at(self.base1_, cur.base1_cursor)), | |
decltype(flux::read_at(self.base2_, cur.base2_cursor))>; | |
if (cur.state_ == cursor_type::first || cur.state_ == cursor_type::second_done) { | |
return static_cast<R>(flux::read_at(self.base1_, cur.base1_cursor)); | |
} else { | |
return static_cast<R>(flux::read_at(self.base2_, cur.base2_cursor)); | |
} | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> && | |
bounded_sequence<Base1> && | |
bounded_sequence<Base2> | |
static constexpr auto last(Self& self) -> cursor_type | |
{ | |
return cursor_type{flux::last(self.base1_), flux::last(self.base2_)}; | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto move_at(Self& self, cursor_type const& cur) | |
-> std::common_reference_t<decltype(flux::move_at(self.base1_, cur.base1_cursor)), | |
decltype(flux::move_at(self.base2_, cur.base2_cursor))> | |
{ | |
using R = std::common_reference_t<decltype(flux::move_at(self.base1_, cur.base1_cursor)), | |
decltype(flux::move_at(self.base2_, cur.base2_cursor))>; | |
if (cur.state_ == cursor_type::first || cur.state_ == cursor_type::second_done) { | |
return static_cast<R>(flux::move_at(self.base1_, cur.base1_cursor)); | |
} else { | |
return static_cast<R>(flux::move_at(self.base2_, cur.base2_cursor)); | |
} | |
} | |
}; | |
}; | |
template <sequence Base1, sequence Base2, typename Cmp> | |
struct set_intersection_adaptor | |
: flux::inline_sequence_base<set_intersection_adaptor<Base1, Base2, Cmp>> | |
{ | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base1 base1_; | |
FLUX_NO_UNIQUE_ADDRESS Base2 base2_; | |
FLUX_NO_UNIQUE_ADDRESS Cmp cmp_; | |
public: | |
constexpr set_intersection_adaptor(decays_to<Base1> auto&& base1, decays_to<Base2> auto&& base2, Cmp cmp) | |
: base1_(FLUX_FWD(base1)), | |
base2_(FLUX_FWD(base2)), | |
cmp_(cmp) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base1> base1_cursor; | |
cursor_t<Base2> base2_cursor; | |
friend auto operator==(cursor_type const&, cursor_type const&) -> bool | |
requires std::equality_comparable<cursor_t<Base1>> && | |
std::equality_comparable<cursor_t<Base2>> | |
= default; | |
}; | |
template <typename Self> | |
static inline constexpr bool maybe_const_iterable | |
= std::is_const_v<Self> ? (flux::sequence<Base1 const> && flux::sequence<Base2 const>) : true; | |
template <typename Self> | |
static constexpr void update(Self& self, cursor_type& cur) { | |
while(not flux::is_last(self.base1_, cur.base1_cursor) && | |
not flux::is_last(self.base2_, cur.base2_cursor)) | |
{ | |
auto r = std::invoke(self.cmp_, flux::read_at(self.base1_, cur.base1_cursor), | |
flux::read_at(self.base2_, cur.base2_cursor)); | |
if (r == std::weak_ordering::less) { | |
flux::inc(self.base1_, cur.base1_cursor); | |
} else if (r == std::weak_ordering::greater) { | |
flux::inc(self.base2_, cur.base2_cursor); | |
} else { | |
return; | |
} | |
} | |
} | |
public: | |
using value_type = value_t<Base1>; | |
inline static constexpr bool is_infinite = flux::infinite_sequence<Base1>; | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto first(Self& self) -> cursor_type | |
{ | |
auto cur = cursor_type{.base1_cursor = flux::first(self.base1_), | |
.base2_cursor = flux::first(self.base2_)}; | |
update(self, cur); | |
return cur; | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto is_last(Self& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base1_, cur.base1_cursor) || | |
flux::is_last(self.base2_, cur.base2_cursor); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto inc(Self& self, cursor_type& cur) -> void | |
{ | |
flux::inc(self.base1_, cur.base1_cursor); | |
flux::inc(self.base2_, cur.base2_cursor); | |
update(self, cur); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto read_at(Self& self, cursor_type const& cur) | |
-> decltype(flux::read_at(self.base1_, cur.base1_cursor)) | |
{ | |
return flux::read_at(self.base1_, cur.base1_cursor); | |
} | |
template <typename Self> | |
requires maybe_const_iterable<Self> | |
static constexpr auto move_at(Self& self, cursor_type const& cur) | |
-> decltype(flux::move_at(self.base1_, cur.base1_cursor)) | |
{ | |
return flux::move_at(self.base1_, cur.base1_cursor); | |
} | |
}; | |
}; | |
template <typename T1, typename T2> | |
concept set_op_compatible = | |
std::common_reference_with<element_t<T1>, element_t<T2>> && | |
std::common_reference_with<rvalue_element_t<T1>, rvalue_element_t<T2>> && | |
requires { typename std::common_type_t<value_t<T1>, value_t<T2>>; }; | |
struct set_union_fn { | |
template <adaptable_sequence Seq1, adaptable_sequence Seq2, typename Cmp = std::compare_three_way> | |
requires set_op_compatible<Seq1, Seq2> && | |
weak_ordering_for<Cmp, Seq1> && | |
weak_ordering_for<Cmp, Seq2> | |
[[nodiscard]] | |
constexpr auto operator()(Seq1&& seq1, Seq2&& seq2, Cmp cmp = {}) const | |
{ | |
return set_union_adaptor<std::decay_t<Seq1>, std::decay_t<Seq2>, Cmp>(FLUX_FWD(seq1), FLUX_FWD(seq2), cmp); | |
} | |
}; | |
struct set_difference_fn { | |
template <adaptable_sequence Seq1, adaptable_sequence Seq2, typename Cmp = std::compare_three_way> | |
requires weak_ordering_for<Cmp, Seq1> && | |
weak_ordering_for<Cmp, Seq2> | |
[[nodiscard]] | |
constexpr auto operator()(Seq1&& seq1, Seq2&& seq2, Cmp cmp = {}) const | |
{ | |
return set_difference_adaptor<std::decay_t<Seq1>, std::decay_t<Seq2>, Cmp>(FLUX_FWD(seq1), FLUX_FWD(seq2), cmp); | |
} | |
}; | |
struct set_symmetric_difference_fn { | |
template <adaptable_sequence Seq1, adaptable_sequence Seq2, typename Cmp = std::compare_three_way> | |
requires set_op_compatible<Seq1, Seq2> && | |
weak_ordering_for<Cmp, Seq1> && | |
weak_ordering_for<Cmp, Seq2> | |
[[nodiscard]] | |
constexpr auto operator()(Seq1&& seq1, Seq2&& seq2, Cmp cmp = {}) const | |
{ | |
return set_symmetric_difference_adaptor<std::decay_t<Seq1>, std::decay_t<Seq2>, Cmp>(FLUX_FWD(seq1), FLUX_FWD(seq2), cmp); | |
} | |
}; | |
struct set_intersection_fn { | |
template <adaptable_sequence Seq1, adaptable_sequence Seq2, typename Cmp = std::compare_three_way> | |
requires weak_ordering_for<Cmp, Seq1> && | |
weak_ordering_for<Cmp, Seq2> | |
[[nodiscard]] | |
constexpr auto operator()(Seq1&& seq1, Seq2&& seq2, Cmp cmp = {}) const | |
{ | |
return set_intersection_adaptor<std::decay_t<Seq1>, std::decay_t<Seq2>, Cmp>(FLUX_FWD(seq1), FLUX_FWD(seq2), cmp); | |
} | |
}; | |
} // namespace detail | |
namespace flux { | |
FLUX_EXPORT inline constexpr auto set_union = detail::set_union_fn{}; | |
FLUX_EXPORT inline constexpr auto set_difference = detail::set_difference_fn{}; | |
FLUX_EXPORT inline constexpr auto set_symmetric_difference = detail::set_symmetric_difference_fn{}; | |
FLUX_EXPORT inline constexpr auto set_intersection = detail::set_intersection_fn{}; | |
} // namespace flux | |
#endif // namespace FLUX_ADAPTOR_SET_ADAPTORS_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_SLIDE_HPP_INCLUDED | |
#define FLUX_ADAPTOR_SLIDE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <multipass_sequence Base> | |
struct slide_adaptor : inline_sequence_base<slide_adaptor<Base>> { | |
private: | |
Base base_; | |
int_t win_sz_; | |
public: | |
constexpr slide_adaptor(decays_to<Base> auto&& base, int_t win_sz) | |
: base_(FLUX_FWD(base)), | |
win_sz_(win_sz) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> from; | |
cursor_t<Base> to; | |
friend constexpr auto operator==(cursor_type const& lhs, cursor_type const& rhs) | |
-> bool | |
{ | |
return lhs.from == rhs.from; | |
} | |
friend constexpr auto operator<=>(cursor_type const& lhs, cursor_type const& rhs) | |
-> std::strong_ordering | |
requires ordered_cursor<cursor_t<Base>> | |
{ | |
return lhs.from <=> rhs.from; | |
} | |
}; | |
public: | |
static constexpr auto first(auto& self) -> cursor_type { | |
auto cur = flux::first(self.base_); | |
auto end = cur; | |
advance(self.base_, end, num::sub(self.win_sz_, int_t {1})); | |
return cursor_type{.from = std::move(cur), .to = std::move(end)}; | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) -> bool | |
{ | |
return flux::is_last(self.base_, cur.to); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) -> void | |
{ | |
flux::inc(self.base_, cur.from); | |
flux::inc(self.base_, cur.to); | |
} | |
static constexpr auto read_at(auto& self, cursor_type const& cur) | |
-> decltype(flux::take(flux::slice(self.base_, cur.from, flux::last), self.win_sz_)) | |
{ | |
return flux::take(flux::slice(self.base_, cur.from, flux::last), self.win_sz_); | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
requires bounded_sequence<Base> && bidirectional_sequence<Base> | |
{ | |
auto end = flux::last(self.base_); | |
auto cur = end; | |
advance(self.base_, cur, num::sub(int_t {1}, self.win_sz_)); | |
return cursor_type{.from = std::move(cur), .to = std::move(end)}; | |
} | |
static constexpr auto dec(auto& self, cursor_type& cur) -> void | |
requires bidirectional_sequence<Base> | |
{ | |
flux::dec(self.base_, cur.from); | |
flux::dec(self.base_, cur.to); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur, int_t offset) -> void | |
requires random_access_sequence<Base> | |
{ | |
flux::inc(self.base_, cur.from, offset); | |
flux::inc(self.base_, cur.to, offset); | |
} | |
static constexpr auto distance(auto& self, cursor_type const& from, cursor_type const& to) | |
-> int_t | |
requires random_access_sequence<Base> | |
{ | |
return flux::distance(self.base_, from.from, to.from); | |
} | |
static constexpr auto size(auto& self) -> int_t | |
requires sized_sequence<Base> | |
{ | |
auto s = num::add(num::sub(flux::size(self.base_), self.win_sz_), int_t {1}); | |
return (cmp::max)(s, int_t {0}); | |
} | |
}; | |
}; | |
struct slide_fn { | |
template <adaptable_sequence Seq> | |
requires multipass_sequence<Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, num::integral auto win_sz) const | |
-> sequence auto | |
{ | |
return slide_adaptor<std::decay_t<Seq>>(FLUX_FWD(seq), num::checked_cast<int_t>(win_sz)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto slide = detail::slide_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::slide(num::integral auto win_sz) && | |
requires multipass_sequence<D> | |
{ | |
FLUX_ASSERT(win_sz > 0); | |
return flux::slide(std::move(derived()), win_sz); | |
} | |
} // namespace slide | |
#endif // FLUX_ADAPTOR_SLIDE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_SPLIT_HPP_INCLUDED | |
#define FLUX_ADAPTOR_SPLIT_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_SEARCH_HPP_INCLUDED | |
#define FLUX_ALGORITHM_SEARCH_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
struct search_fn { | |
template <multipass_sequence Haystack, multipass_sequence Needle, | |
typename Cmp = std::ranges::equal_to> | |
requires std::predicate<Cmp&, element_t<Haystack>, element_t<Needle>> | |
constexpr auto operator()(Haystack&& h, Needle&& n, Cmp cmp = {}) const | |
-> bounds_t<Haystack> | |
{ | |
auto hfirst = flux::first(h); | |
while(true) { | |
auto cur1 = hfirst; | |
auto cur2 = flux::first(n); | |
while (true) { | |
if (is_last(n, cur2)) { | |
return {std::move(hfirst), std::move(cur1)}; | |
} | |
if (is_last(h, cur1)) { | |
return {cur1, cur1}; | |
} | |
if (!std::invoke(cmp, read_at(h, cur1), read_at(n, cur2))) { | |
break; | |
} | |
inc(h, cur1); | |
inc(n, cur2); | |
} | |
inc(h, hfirst); | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto search = detail::search_fn{}; | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_SEARCH_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename Splitter, typename Seq> | |
concept splitter_for = requires(Splitter& splitter, Seq& seq, cursor_t<Seq> const& cur) { | |
{ splitter(flux::slice(seq, cur, flux::last)) } -> std::same_as<bounds_t<Seq>>; | |
}; | |
template <multipass_sequence Base, splitter_for<Base> Splitter> | |
struct split_adaptor : inline_sequence_base<split_adaptor<Base, Splitter>> { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Base base_; | |
FLUX_NO_UNIQUE_ADDRESS Splitter splitter_; | |
public: | |
constexpr split_adaptor(decays_to<Base> auto&& base, decays_to<Splitter> auto&& splitter) | |
: base_(FLUX_FWD(base)), | |
splitter_(FLUX_FWD(splitter)) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
cursor_t<Base> cur{}; | |
bounds_t<Base> next{}; | |
bool trailing_empty = false; | |
friend constexpr bool operator==(cursor_type const& lhs, cursor_type const& rhs) | |
{ | |
return lhs.cur == rhs.cur && lhs.trailing_empty == rhs.trailing_empty; | |
} | |
}; | |
public: | |
static constexpr bool is_infinite = infinite_sequence<Base>; | |
static constexpr auto first(auto& self) -> cursor_type | |
requires sequence<decltype((self.base_))> && | |
splitter_for<decltype((self.splitter_)), decltype((self.base_))> | |
{ | |
auto fst = flux::first(self.base_); | |
auto bounds = self.splitter_(flux::slice(self.base_, fst, flux::last)); | |
return cursor_type{.cur = std::move(fst), | |
.next = std::move(bounds)}; | |
} | |
static constexpr auto is_last(auto& self, cursor_type const& cur) | |
-> bool | |
{ | |
return flux::is_last(self.base_, cur.cur) && !cur.trailing_empty; | |
} | |
static constexpr auto read_at(auto& self, cursor_type const& cur) | |
{ | |
return flux::slice(self.base_, cur.cur, cur.next.from); | |
} | |
static constexpr auto inc(auto& self, cursor_type& cur) -> void | |
{ | |
cur.cur = cur.next.from; | |
if (!flux::is_last(self.base_, cur.cur)) { | |
cur.cur = cur.next.to; | |
if (flux::is_last(self.base_, cur.cur)) { | |
cur.trailing_empty = true; | |
cur.next = {cur.cur, cur.cur}; | |
} else { | |
cur.next = self.splitter_(flux::slice(self.base_, cur.cur, flux::last)); | |
} | |
} else { | |
cur.trailing_empty = false; | |
} | |
} | |
static constexpr auto last(auto& self) -> cursor_type | |
requires bounded_sequence<decltype(self.base_)> | |
{ | |
return cursor_type{.cur = flux::last(self.base_)}; | |
} | |
}; | |
}; | |
template <multipass_sequence Pattern> | |
struct pattern_splitter { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Pattern pattern_; | |
public: | |
constexpr explicit pattern_splitter(decays_to<Pattern> auto&& pattern) | |
: pattern_(FLUX_FWD(pattern)) | |
{} | |
template <multipass_sequence Seq> | |
requires std::equality_comparable_with<element_t<Seq>, element_t<Pattern>> | |
constexpr auto operator()(Seq&& seq) -> bounds_t<Seq> | |
{ | |
return flux::search(seq, pattern_); | |
} | |
template <multipass_sequence Seq> | |
requires multipass_sequence<Pattern const> && | |
std::equality_comparable_with<element_t<Seq>, element_t<Pattern const>> | |
constexpr auto operator()(Seq&& seq) const -> bounds_t<Seq> | |
{ | |
return flux::search(seq, pattern_); | |
} | |
}; | |
template <typename Delim> | |
struct delim_splitter { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Delim delim_; | |
public: | |
constexpr explicit delim_splitter(decays_to<Delim> auto&& delim) | |
: delim_(FLUX_FWD(delim)) | |
{} | |
template <multipass_sequence Seq> | |
requires std::equality_comparable_with<element_t<Seq>, Delim const&> | |
constexpr auto operator()(Seq&& seq) const -> bounds_t<Seq> | |
{ | |
auto nxt = flux::find(seq, delim_); | |
if (!flux::is_last(seq, nxt)) { | |
return bounds{nxt, flux::next(seq, nxt)}; | |
} else { | |
return bounds{nxt, nxt}; | |
} | |
} | |
}; | |
template <typename Pred> | |
struct predicate_splitter { | |
private: | |
FLUX_NO_UNIQUE_ADDRESS Pred pred_; | |
public: | |
constexpr explicit predicate_splitter(decays_to<Pred> auto&& pred) | |
: pred_(FLUX_FWD(pred)) | |
{} | |
template <multipass_sequence Seq> | |
requires std::predicate<Pred const&, element_t<Seq>> | |
constexpr auto operator()(Seq&& seq) const -> bounds_t<Seq> | |
{ | |
auto nxt = flux::find_if(seq, pred_); | |
if (!flux::is_last(seq, nxt)) { | |
return bounds{nxt, flux::next(seq, nxt)}; | |
} else { | |
return bounds{nxt, nxt}; | |
} | |
} | |
}; | |
struct split_fn { | |
template <adaptable_sequence Seq, adaptable_sequence Pattern> | |
requires multipass_sequence<Seq> && | |
multipass_sequence<Pattern> && | |
std::equality_comparable_with<element_t<Seq>, element_t<Pattern>> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, Pattern&& pattern) const | |
{ | |
using splitter_t = pattern_splitter<std::decay_t<Pattern>>; | |
return split_adaptor<std::decay_t<Seq>, splitter_t>( | |
FLUX_FWD(seq), splitter_t(FLUX_FWD(pattern))); | |
} | |
template <adaptable_sequence Seq, typename Delim> | |
requires multipass_sequence<Seq> && | |
std::equality_comparable_with<element_t<Seq>, Delim const&> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, Delim&& delim) const | |
{ | |
using splitter_t = delim_splitter<std::decay_t<Delim>>; | |
return split_adaptor<std::decay_t<Seq>, splitter_t>( | |
FLUX_FWD(seq), splitter_t(FLUX_FWD(delim))); | |
} | |
template <adaptable_sequence Seq, typename Pred> | |
requires multipass_sequence<Seq> && | |
std::predicate<Pred const&, element_t<Seq>> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, Pred pred) const | |
{ | |
using splitter_t = predicate_splitter<Pred>; | |
return split_adaptor<std::decay_t<Seq>, splitter_t>( | |
FLUX_FWD(seq), splitter_t(std::move(pred))); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto split = detail::split_fn{}; | |
template <typename Derived> | |
template <typename Pattern> | |
requires multipass_sequence<Derived> && | |
multipass_sequence<Pattern> && | |
std::equality_comparable_with<element_t<Derived>, element_t<Pattern>> | |
constexpr auto inline_sequence_base<Derived>::split(Pattern&& pattern) && | |
{ | |
return flux::split(std::move(derived()), FLUX_FWD(pattern)); | |
} | |
template <typename Derived> | |
template <typename Delim> | |
requires multipass_sequence<Derived> && | |
std::equality_comparable_with<element_t<Derived>, Delim const&> | |
constexpr auto inline_sequence_base<Derived>::split(Delim&& delim) && | |
{ | |
return flux::split(std::move(derived()), FLUX_FWD(delim)); | |
} | |
template <typename Derived> | |
template <typename Pred> | |
requires multipass_sequence<Derived> && | |
std::predicate<Pred const&, element_t<Derived>> | |
constexpr auto inline_sequence_base<Derived>::split(Pred pred) && | |
{ | |
return flux::split(std::move(derived()), std::move(pred)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_SPLIT_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_STRING_SPLIT_HPP_INCLUDED | |
#define FLUX_ADAPTOR_STRING_SPLIT_HPP_INCLUDED | |
#include <string_view> | |
namespace flux { | |
namespace detail { | |
template <typename C> | |
concept character = any_of<C, char, wchar_t, char8_t, char16_t, char32_t>; | |
struct to_string_view_fn { | |
template <contiguous_sequence Seq> | |
requires sized_sequence<Seq> && character<value_t<Seq>> | |
constexpr auto operator()(Seq&& seq) const | |
{ | |
return std::basic_string_view<value_t<Seq>>(flux::data(seq), flux::usize(seq)); | |
} | |
}; | |
inline constexpr auto to_string_view = to_string_view_fn{}; | |
struct split_string_fn { | |
template <contiguous_sequence Seq, multipass_sequence Pattern> | |
requires character<value_t<Seq>> && | |
std::equality_comparable_with<element_t<Seq>, element_t<Pattern>> | |
constexpr auto operator()(Seq&& seq, Pattern&& pattern) const | |
{ | |
return flux::split(FLUX_FWD(seq), FLUX_FWD(pattern)).map(to_string_view); | |
} | |
// Attempt to hijack string literal patterns to do the right thing | |
template <contiguous_sequence Seq, std::size_t N> | |
requires character<value_t<Seq>> | |
constexpr auto operator()(Seq&& seq, value_t<Seq> const (&pattern)[N]) const | |
{ | |
return flux::split(FLUX_FWD(seq), std::basic_string_view(pattern)) | |
.map(to_string_view); | |
} | |
template <contiguous_sequence Seq> | |
requires character<value_t<Seq>> | |
constexpr auto operator()(Seq&& seq, value_t<Seq> delim) const | |
{ | |
return flux::split(FLUX_FWD(seq), delim).map(to_string_view); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto split_string = detail::split_string_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::split_string(auto&& pattern) && | |
{ | |
return flux::split_string(std::move(derived()), FLUX_FWD(pattern)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_SPLIT_STRING_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_TAKE_WHILE_HPP_INCLUDED | |
#define FLUX_ADAPTOR_TAKE_WHILE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename Base, typename Pred> | |
struct take_while_adaptor : inline_sequence_base<take_while_adaptor<Base, Pred>> { | |
private: | |
Base base_; | |
Pred pred_; | |
constexpr auto base() & -> Base& { return base_; } | |
friend struct sequence_traits<take_while_adaptor>; | |
friend struct passthrough_traits_base; | |
template <typename BaseCtx, typename TakePred> | |
struct context_type : immovable { | |
BaseCtx base_ctx; | |
TakePred take_pred; | |
bool done = false; | |
using element_type = context_element_t<BaseCtx>; | |
constexpr auto run_while(auto&& pred) -> iteration_result | |
{ | |
if (!done) { | |
auto res = base_ctx.run_while([&](auto&& elem) { | |
if (!std::invoke(take_pred, std::as_const(elem))) { | |
done = true; | |
return loop_break; | |
} else { | |
return std::invoke(pred, FLUX_FWD(elem)); | |
} | |
}); | |
return static_cast<iteration_result>(static_cast<bool>(res) || done); | |
} else { | |
return iteration_result::complete; | |
} | |
} | |
}; | |
public: | |
constexpr take_while_adaptor(decays_to<Base> auto&& base, decays_to<Pred> auto&& pred) | |
: base_(FLUX_FWD(base)), | |
pred_(FLUX_FWD(pred)) | |
{ | |
} | |
[[nodiscard]] constexpr auto base() const& -> Base const& { return base_; } | |
[[nodiscard]] constexpr auto base() && -> Base { return std::move(base_); } | |
[[nodiscard]] | |
constexpr auto iterate() | |
{ | |
return context_type<iteration_context_t<Base>, copy_or_ref_t<Pred>>{ | |
.base_ctx = flux::iterate(base_), .take_pred = copy_or_ref(pred_)}; | |
} | |
[[nodiscard]] | |
constexpr auto iterate() const | |
requires iterable<Base const> | |
&& std::predicate<Pred&, iterable_element_t<Base const> const&> | |
{ | |
return context_type<iteration_context_t<Base const>, copy_or_ref_t<Pred const>>{ | |
.base_ctx = flux::iterate(base_), .take_pred = copy_or_ref(pred_)}; | |
} | |
}; | |
struct take_while_fn { | |
template <adaptable_iterable It, std::move_constructible Pred> | |
requires std::predicate<Pred&, iterable_element_t<It> const&> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Pred pred) const | |
{ | |
return take_while_adaptor<std::decay_t<It>, Pred>(FLUX_FWD(it), std::move(pred)); | |
} | |
}; | |
} // namespace detail | |
template <typename Base, typename Pred> | |
struct sequence_traits<detail::take_while_adaptor<Base, Pred>> : detail::passthrough_traits_base { | |
using self_t = detail::take_while_adaptor<Base, Pred>; | |
using value_type = value_t<Base>; | |
static constexpr bool is_infinite = false; | |
template <typename Self> | |
static constexpr bool is_last(Self& self, cursor_t<Self> const& cur) | |
requires std::predicate<decltype((self.pred_)), element_t<decltype(self.base_)>> | |
{ | |
if (flux::is_last(self.base_, cur) || | |
!std::invoke(self.pred_, flux::read_at(self.base_, cur))) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
void last() = delete; | |
void size() = delete; | |
static constexpr auto for_each_while(auto& self, auto&& func) | |
{ | |
return flux::seq_for_each_while(self.base_, [&](auto&& elem) { | |
if (!std::invoke(self.pred_, elem)) { | |
return false; | |
} else { | |
return std::invoke(func, FLUX_FWD(elem)); | |
} | |
}); | |
} | |
}; | |
FLUX_EXPORT inline constexpr auto take_while = detail::take_while_fn{}; | |
template <typename D> | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<D>> | |
constexpr auto inline_sequence_base<D>::take_while(Pred pred) && | |
{ | |
return flux::take_while(std::move(derived()), std::move(pred)); | |
} | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_TAKE_WHILE_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ADAPTOR_UNCHECKED_HPP_INCLUDED | |
#define FLUX_ADAPTOR_UNCHECKED_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <sequence Base> | |
struct unchecked_adaptor : inline_sequence_base<unchecked_adaptor<Base>> { | |
private: | |
Base base_; | |
public: | |
constexpr explicit unchecked_adaptor(decays_to<Base> auto&& base) | |
: base_(FLUX_FWD(base)) | |
{} | |
constexpr auto base() & -> Base& { return base_; } | |
constexpr auto base() const& -> Base const& { return base_; } | |
struct flux_sequence_traits : passthrough_traits_base { | |
using value_type = value_t<Base>; | |
static constexpr bool disable_multipass = !multipass_sequence<Base>; | |
static constexpr bool is_infinite = infinite_sequence<Base>; | |
static constexpr auto read_at(auto& self, auto const& cur) | |
-> element_t<Base> | |
{ | |
return flux::read_at_unchecked(self.base(), cur); | |
} | |
static constexpr auto move_at(auto& self, auto const& cur) | |
-> rvalue_element_t<Base> | |
{ | |
return flux::move_at_unchecked(self.base(), cur); | |
} | |
}; | |
}; | |
struct unchecked_fn { | |
template <adaptable_sequence Seq> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq) const | |
-> unchecked_adaptor<std::decay_t<Seq>> | |
{ | |
return unchecked_adaptor<std::decay_t<Seq>>(FLUX_FWD(seq)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto unchecked = detail::unchecked_fn{}; | |
} // namespace flux | |
#endif // FLUX_ADAPTOR_UNCHECKED_HPP_INCLUDED | |
#endif // FLUX_ADAPTOR_HPP_INCLUDED | |
// Copyright (c) 2024 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_HPP_INCLUDED | |
#define FLUX_ALGORITHM_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_ALL_ANY_NONE_HPP_INCLUDED | |
#define FLUX_ALGORITHM_ALL_ANY_NONE_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
struct all_t { | |
template <iterable It, typename Pred> | |
requires std::predicate<Pred const&, iterable_element_t<It>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Pred const pred) const -> bool | |
{ | |
return for_each_while(it, [&](auto&& elem) { return std::invoke(pred, FLUX_FWD(elem)); }) | |
== iteration_result::complete; | |
} | |
}; | |
FLUX_EXPORT inline constexpr all_t all {}; | |
FLUX_EXPORT | |
struct none_t { | |
template <iterable It, typename Pred> | |
requires std::predicate<Pred&, iterable_element_t<It>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Pred const pred) const -> bool | |
{ | |
return for_each_while(it, [&](auto&& elem) { return !std::invoke(pred, FLUX_FWD(elem)); }) | |
== iteration_result::complete; | |
} | |
}; | |
FLUX_EXPORT inline constexpr none_t none {}; | |
FLUX_EXPORT | |
struct any_t { | |
template <iterable It, typename Pred> | |
requires std::predicate<Pred&, iterable_element_t<It>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Pred const pred) const -> bool | |
{ | |
return for_each_while(it, [&](auto&& elem) { return !std::invoke(pred, FLUX_FWD(elem)); }) | |
== iteration_result::incomplete; | |
} | |
}; | |
FLUX_EXPORT inline constexpr any_t any {}; | |
template <typename D> | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<D>> | |
constexpr auto inline_sequence_base<D>::all(Pred pred) | |
{ | |
return flux::all(derived(), std::move(pred)); | |
} | |
template <typename D> | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<D>> | |
constexpr auto inline_sequence_base<D>::any(Pred pred) | |
{ | |
return flux::any(derived(), std::move(pred)); | |
} | |
template <typename D> | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<D>> | |
constexpr auto inline_sequence_base<D>::none(Pred pred) | |
{ | |
return flux::none(derived(), std::move(pred)); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_ALL_ANY_NONE_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_COMPARE_HPP_INCLUDED | |
#define FLUX_ALGORITHM_COMPARE_HPP_INCLUDED | |
#include <compare> | |
#include <cstring> | |
#include <bit> | |
namespace flux { | |
FLUX_EXPORT | |
struct compare_t { | |
private: | |
template <iterable It1, iterable It2, typename Cmp> | |
requires ordering_invocable<Cmp&, iterable_element_t<It1>, iterable_element_t<It2>> | |
static constexpr auto impl(It1& it1, It2& it2, Cmp& cmp) -> std::decay_t< | |
std::invoke_result_t<Cmp&, iterable_element_t<It1>, iterable_element_t<It2>>> | |
{ | |
iteration_context auto ctx1 = iterate(it1); | |
iteration_context auto ctx2 = iterate(it2); | |
while (true) { | |
auto opt1 = next_element(ctx1); | |
auto opt2 = next_element(ctx2); | |
if (opt1.has_value() && opt2.has_value()) { | |
auto r = std::invoke(cmp, opt1.value(), opt2.value()); | |
if (r != 0) { | |
return r; | |
} | |
} else if (opt1.has_value()) { | |
return std::strong_ordering::greater; | |
} else if (opt2.has_value()) { | |
return std::strong_ordering::less; | |
} else { | |
return std::strong_ordering::equal; | |
} | |
} | |
} | |
template <contiguous_sequence Seq1, contiguous_sequence Seq2> | |
static constexpr auto memcmp_impl(Seq1& seq1, Seq2& seq2) -> std::strong_ordering | |
{ | |
auto const seq1_size = flux::usize(seq1); | |
auto const seq2_size = flux::usize(seq2); | |
auto min_size = (cmp::min)(seq1_size, seq2_size); | |
int cmp_result = 0; | |
if (min_size > 0) { | |
auto data1 = flux::data(seq1); | |
FLUX_ASSERT(data1 != nullptr); | |
auto data2 = flux::data(seq2); | |
FLUX_ASSERT(data2 != nullptr); | |
cmp_result = std::memcmp(data1, data2, min_size); | |
} | |
if (cmp_result == 0) { | |
if (seq1_size == seq2_size) { | |
return std::strong_ordering::equal; | |
} else if (seq1_size < seq2_size) { | |
return std::strong_ordering::less; | |
} else /* seq1_size > seq2_size */ { | |
return std::strong_ordering::greater; | |
} | |
} else if (cmp_result > 0) { | |
return std::strong_ordering::greater; | |
} else /* cmp_result < 0 */ { | |
return std::strong_ordering::less; | |
} | |
} | |
template <typename It1, typename It2, typename Cmp> | |
static consteval auto can_memcmp() -> bool | |
{ | |
return std::same_as<Cmp, std::compare_three_way> && contiguous_sequence<It1> | |
&& contiguous_sequence<It2> && sized_sequence<It1> && sized_sequence<It2> | |
&& std::same_as<iterable_value_t<It1>, iterable_value_t<It2>> | |
&& std::unsigned_integral<iterable_value_t<It1>> | |
&& ((sizeof(iterable_value_t<It1>) == 1) || (std::endian::native == std::endian::big)); | |
} | |
public: | |
template <iterable It1, iterable It2, typename Cmp = std::compare_three_way> | |
requires ordering_invocable<Cmp&, iterable_element_t<It1>, iterable_element_t<It2>> | |
constexpr auto operator()(It1&& it1, It2&& it2, Cmp cmp = {}) const -> std::decay_t< | |
std::invoke_result_t<Cmp&, iterable_element_t<It1>, iterable_element_t<It2>>> | |
{ | |
if constexpr (can_memcmp<It1, It2, Cmp>()) { | |
if (std::is_constant_evaluated()) { | |
return impl(it1, it2, cmp); // LCOV_EXCL_LINE | |
} else { | |
return memcmp_impl(it1, it2); | |
} | |
} else { | |
return impl(it1, it2, cmp); | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr auto compare = compare_t{}; | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_COMPARE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_CONTAINS_HPP_INCLUDED | |
#define FLUX_ALGORITHM_CONTAINS_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
struct contains_t { | |
template <iterable It, typename Value> | |
requires std::equality_comparable_with<iterable_element_t<It>, Value const&> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Value const& value) const -> bool | |
{ | |
return any(it, [&](auto&& elem) { return FLUX_FWD(elem) == value; }); | |
} | |
}; | |
FLUX_EXPORT inline constexpr contains_t contains {}; | |
template <typename D> | |
template <typename Value> | |
requires std::equality_comparable_with<element_t<D>, Value const&> | |
constexpr auto inline_sequence_base<D>::contains(Value const& value) -> bool | |
{ | |
return flux::contains(derived(), value); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_CONTAINS_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_COUNT_HPP_INCLUDED | |
#define FLUX_ALGORITHM_COUNT_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_FOR_EACH_HPP_INCLUDED | |
#define FLUX_ALGORITHM_FOR_EACH_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
struct for_each_t { | |
template <iterable It, typename Func> | |
requires std::invocable<Func&, iterable_element_t<It>> | |
constexpr auto operator()(It&& it, Func func) const -> Func | |
{ | |
for_each_while(it, [&](auto&& elem) { | |
static_cast<void>(std::invoke(func, FLUX_FWD(elem))); | |
return loop_continue; | |
}); | |
return func; | |
} | |
}; | |
FLUX_EXPORT inline constexpr for_each_t for_each {}; | |
template <typename D> | |
template <typename Func> | |
requires std::invocable<Func&, element_t<D>> | |
constexpr auto inline_sequence_base<D>::for_each(Func func) -> Func | |
{ | |
return flux::for_each(derived(), std::move(func)); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_FOR_EACH_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
struct count_t { | |
template <iterable It> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it) const -> int_t | |
{ | |
if constexpr (sized_iterable<It>) { | |
return flux::iterable_size(it); | |
} else { | |
int_t counter = 0; | |
for_each(it, [&](auto&&) { counter = num::add(counter, int_t{1}); }); | |
return counter; | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr count_t count{}; | |
FLUX_EXPORT | |
struct count_if_t { | |
template <iterable It, typename Pred> | |
requires std::predicate<Pred&, iterable_element_t<It>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Pred pred) const -> int_t | |
{ | |
int_t counter = 0; | |
for_each(it, [&](auto&& elem) { | |
if (std::invoke(pred, FLUX_FWD(elem))) { | |
++counter; | |
} | |
}); | |
return counter; | |
} | |
}; | |
FLUX_EXPORT inline constexpr count_if_t count_if{}; | |
FLUX_EXPORT | |
struct count_eq_t { | |
template <iterable It, typename Value> | |
requires std::equality_comparable_with<iterable_element_t<It>, Value const&> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Value const& value) const -> int_t | |
{ | |
return count_if(it, [&](auto&& elem) { return value == FLUX_FWD(elem); }); | |
} | |
}; | |
FLUX_EXPORT inline constexpr count_eq_t count_eq{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::count() | |
{ | |
return flux::count(derived()); | |
} | |
template <typename D> | |
template <typename Value> | |
requires std::equality_comparable_with<element_t<D>, Value const&> | |
constexpr auto inline_sequence_base<D>::count_eq(Value const& value) | |
{ | |
return flux::count_eq(derived(), value); | |
} | |
template <typename D> | |
template <typename Pred> | |
requires std::predicate<Pred&, element_t<D>> | |
constexpr auto inline_sequence_base<D>::count_if(Pred pred) | |
{ | |
return flux::count_if(derived(), std::move(pred)); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_COUNT_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_ENDS_WITH_HPP_INCLUDED | |
#define FLUX_ALGORITHM_ENDS_WITH_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_EQUAL_HPP_INCLUDED | |
#define FLUX_ALGORITHM_EQUAL_HPP_INCLUDED | |
#include <type_traits> | |
#include <cstring> | |
namespace flux { | |
FLUX_EXPORT | |
struct equal_t { | |
private: | |
template <typename It1, typename It2, typename Cmp> | |
static constexpr auto impl(It1& it1, It2& it2, Cmp& cmp) -> bool | |
{ | |
iteration_context auto ctx1 = iterate(it1); | |
iteration_context auto ctx2 = iterate(it2); | |
while (true) { | |
flux::optional opt1 = next_element(ctx1); | |
flux::optional opt2 = next_element(ctx2); | |
if (opt1.has_value() && opt2.has_value()) { | |
if (!std::invoke(cmp, opt1.value(), opt2.value())) { | |
return false; | |
} | |
} else if (opt1.has_value() || opt2.has_value()) { | |
return false; | |
} else { | |
return true; | |
} | |
} | |
} | |
template <contiguous_sequence Seq1, contiguous_sequence Seq2> | |
static constexpr auto memcmp_impl(Seq1& seq1, Seq2& seq2) -> bool | |
{ | |
auto size = flux::usize(seq1); | |
if (size == 0) { | |
return true; | |
} | |
auto data1 = flux::data(seq1); | |
auto data2 = flux::data(seq2); | |
FLUX_ASSERT(data1 != nullptr); | |
FLUX_ASSERT(data2 != nullptr); | |
auto result = std::memcmp(data1, data2, size * sizeof(iterable_value_t<Seq1>)); | |
return result == 0; | |
} | |
template <typename It1, typename It2, typename Cmp> | |
static consteval auto can_memcmp() -> bool | |
{ | |
return std::same_as<Cmp, std::ranges::equal_to> && contiguous_sequence<It1> | |
&& contiguous_sequence<It2> && sized_sequence<It1> && sized_sequence<It2> | |
&& std::same_as<iterable_value_t<It1>, iterable_value_t<It2>> | |
&& (std::integral<iterable_value_t<It1>> || std::is_pointer_v<iterable_value_t<It1>>) | |
&& std::has_unique_object_representations_v<iterable_value_t<It1>>; | |
} | |
public: | |
template <iterable It1, iterable It2, typename Cmp = std::ranges::equal_to> | |
requires std::predicate<Cmp&, iterable_element_t<It1>, iterable_element_t<It2>> | |
constexpr auto operator()(It1&& it1, It2&& it2, Cmp cmp = {}) const -> bool | |
{ | |
if constexpr (sized_iterable<It1> && sized_iterable<It2>) { | |
if (flux::iterable_size(it1) != flux::iterable_size(it2)) { | |
return false; | |
} | |
} | |
if constexpr (can_memcmp<It1, It2, Cmp>()) { | |
if (std::is_constant_evaluated()) { | |
return impl(it1, it2, cmp); // LCOV_EXCL_LINE | |
} else { | |
return memcmp_impl(it1, it2); | |
} | |
} else { | |
return impl(it1, it2, cmp); | |
} | |
} | |
template <iterable It1, iterable It2> | |
requires iterable<iterable_element_t<It1>> && iterable<iterable_element_t<It2>> | |
&& (!std::equality_comparable_with<iterable_element_t<It1>, iterable_element_t<It2>> | |
&& std::is_invocable_v<equal_t&, It1&, It2&, equal_t&>) | |
constexpr auto operator()(It1&& it1, It2&& it2) const -> bool | |
{ | |
if constexpr (sized_iterable<It1> && sized_iterable<It2>) { | |
if (flux::iterable_size(it1) != flux::iterable_size(it2)) { | |
return false; | |
} | |
} | |
return (*this)(it1, it2, *this); | |
} | |
}; | |
FLUX_EXPORT inline constexpr equal_t equal{}; | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_EQUAL_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_STARTS_WITH_HPP_INCLUDED | |
#define FLUX_ALGORITHM_STARTS_WITH_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
struct starts_with_t { | |
template <iterable Haystack, iterable Needle, typename Cmp = std::ranges::equal_to> | |
requires std::predicate<Cmp&, element_t<Haystack>, element_t<Needle>> | |
[[nodiscard]] | |
constexpr auto operator()(Haystack&& haystack, Needle&& needle, Cmp cmp = Cmp{}) const -> bool | |
{ | |
if constexpr (sized_iterable<Haystack> && sized_iterable<Needle>) { | |
if (flux::iterable_size(haystack) < flux::iterable_size(needle)) { | |
return false; | |
} | |
} | |
iteration_context auto haystack_ctx = iterate(haystack); | |
iteration_context auto needle_ctx = iterate(needle); | |
while (true) { | |
auto haystack_elem = next_element(haystack_ctx); | |
auto needle_elem = next_element(needle_ctx); | |
if (haystack_elem.has_value() && needle_elem.has_value()) { | |
if (!std::invoke(cmp, haystack_elem.value(), needle_elem.value())) { | |
return false; | |
} | |
} else if (needle_elem.has_value()) { | |
return false; | |
} else { | |
return true; | |
} | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr starts_with_t starts_with{}; | |
template <typename Derived> | |
template <sequence Needle, typename Cmp> | |
requires std::predicate<Cmp&, element_t<Derived>, element_t<Needle>> | |
constexpr auto inline_sequence_base<Derived>::starts_with(Needle&& needle, Cmp cmp) -> bool | |
{ | |
return flux::starts_with(derived(), FLUX_FWD(needle), std::move(cmp)); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_STARTS_WITH_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
struct ends_with_t { | |
template <iterable Haystack, iterable Needle, typename Cmp = std::ranges::equal_to> | |
requires std::predicate<Cmp&, iterable_element_t<Haystack>, iterable_element_t<Needle>> | |
&& (multipass_sequence<Haystack> || sized_iterable<Haystack>) | |
&& (multipass_sequence<Needle> || sized_iterable<Needle>) | |
[[nodiscard]] | |
constexpr auto operator()(Haystack&& haystack, Needle&& needle, Cmp cmp = Cmp{}) const -> bool | |
{ | |
if constexpr (reverse_iterable<Haystack> && reverse_iterable<Needle>) { | |
return starts_with(reverse(from_fwd_ref(haystack)), reverse(from_fwd_ref(needle)), | |
std::ref(cmp)); | |
} else { | |
int_t haystack_size = flux::count(haystack); | |
int_t needle_size = flux::count(needle); | |
if (haystack_size < needle_size) { | |
return false; | |
} | |
return equal(drop(from_fwd_ref(haystack), num::sub(haystack_size, needle_size)), needle, | |
std::ref(cmp)); | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr auto ends_with = ends_with_t{}; | |
template <typename Derived> | |
template <sequence Needle, typename Cmp> | |
requires std::predicate<Cmp&, element_t<Derived>, element_t<Needle>> && | |
(multipass_sequence<Derived> || sized_sequence<Derived>) && | |
(multipass_sequence<Needle> || sized_sequence<Needle>) | |
constexpr auto inline_sequence_base<Derived>::ends_with(Needle&& needle, Cmp cmp) -> bool | |
{ | |
return flux::ends_with(derived(), FLUX_FWD(needle), std::move(cmp)); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_ENDS_WITH_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_FILL_HPP_INCLUDED | |
#define FLUX_ALGORITHM_FILL_HPP_INCLUDED | |
#include <type_traits> | |
#include <cstring> | |
namespace flux { | |
FLUX_EXPORT | |
struct fill_t { | |
private: | |
template <typename It, typename Value> | |
static constexpr auto impl(It&& it, Value const& value) -> void | |
{ | |
flux::for_each(it, [&value](auto&& elem) { FLUX_FWD(elem) = value; }); | |
} | |
template <typename Seq, typename Value> | |
static constexpr auto memset_impl(Seq& seq, Value const& value) -> void | |
{ | |
if (std::is_constant_evaluated()) { | |
impl(seq, value); // LCOV_EXCL_LINE | |
} else { | |
auto size = flux::usize(seq); | |
if (size == 0) { | |
return; | |
} | |
FLUX_ASSERT(flux::data(seq) != nullptr); | |
std::memset(flux::data(seq), value, size * sizeof(value_t<Seq>)); | |
} | |
} | |
public: | |
template <iterable It, typename Value> | |
requires std::assignable_from<element_t<It>, Value const&> | |
constexpr auto operator()(It&& it, Value const& value) const -> void | |
{ | |
if constexpr (contiguous_sequence<It> && sized_sequence<It> | |
&& std::same_as<Value, iterable_value_t<It>> | |
// only allow memset on single byte types | |
&& sizeof(value_t<It>) == 1 && std::is_trivially_copyable_v<value_t<It>>) { | |
memset_impl(it, value); | |
} else { | |
impl(it, value); | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr fill_t fill {}; | |
template <typename D> | |
template <typename Value> | |
requires writable_sequence_of<D, Value const&> | |
constexpr void inline_sequence_base<D>::fill(Value const& value) | |
{ | |
flux::fill(derived(), value); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_FILL_HPP_INCLUDED | |
// Copyright (c) 2025 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_FIND_ELEMENT_HPP_INCLUDED | |
#define FLUX_ALGORITHM_FIND_ELEMENT_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
struct find_element_if_t { | |
template <iterable It, typename Pred> | |
requires std::predicate<Pred&, iterable_element_t<It>> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Pred pred) const | |
{ | |
iterable auto filtered = filter(std::ref(it), std::ref(pred)); | |
iteration_context auto ctx = iterate(filtered); | |
return next_element(ctx); | |
} | |
}; | |
FLUX_EXPORT inline constexpr find_element_if_t find_element_if{}; | |
FLUX_EXPORT | |
struct find_element_t { | |
template <iterable It, typename Value> | |
requires std::equality_comparable_with<iterable_element_t<It>, Value const&> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Value const& value) const | |
{ | |
return find_element_if(it, [&value](auto&& elem) { return FLUX_FWD(elem) == value; }); | |
} | |
}; | |
FLUX_EXPORT inline constexpr find_element_t find_element{}; | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_FIND_ELEMENT_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_FIND_MIN_MAX_HPP_INCLUDED | |
#define FLUX_ALGORITHM_FIND_MIN_MAX_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_MINMAX_HPP_INCLUDED | |
#define FLUX_ALGORITHM_MINMAX_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
template <typename T> | |
struct minmax_result { | |
T min; | |
T max; | |
}; | |
FLUX_EXPORT | |
struct min_t { | |
template <iterable It, weak_ordering_for<It> Cmp = std::compare_three_way> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Cmp cmp = Cmp{}) const | |
-> flux::optional<iterable_value_t<It>> | |
{ | |
return fold_first(FLUX_FWD(it), [&](auto min, auto&& elem) -> iterable_value_t<It> { | |
if (std::invoke(cmp, elem, min) < 0) { | |
return iterable_value_t<It>(FLUX_FWD(elem)); | |
} else { | |
return min; | |
} | |
}); | |
} | |
}; | |
FLUX_EXPORT inline constexpr min_t min{}; | |
FLUX_EXPORT | |
struct max_t { | |
template <iterable It, weak_ordering_for<It> Cmp = std::compare_three_way> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Cmp cmp = Cmp{}) const | |
-> flux::optional<iterable_value_t<It>> | |
{ | |
return fold_first(FLUX_FWD(it), [&](auto max, auto&& elem) -> iterable_value_t<It> { | |
if (!(std::invoke(cmp, elem, max) < 0)) { | |
return iterable_value_t<It>(FLUX_FWD(elem)); | |
} else { | |
return max; | |
} | |
}); | |
} | |
}; | |
FLUX_EXPORT inline constexpr max_t max{}; | |
FLUX_EXPORT | |
struct minmax_t { | |
template <iterable It, weak_ordering_for<It> Cmp = std::compare_three_way> | |
[[nodiscard]] | |
constexpr auto operator()(It&& it, Cmp cmp = Cmp{}) const | |
-> flux::optional<minmax_result<iterable_value_t<It>>> | |
{ | |
using R = minmax_result<iterable_value_t<It>>; | |
iteration_context auto ctx = iterate(it); | |
auto opt = next_element(ctx); | |
if (!opt.has_value()) { | |
return flux::nullopt; | |
} | |
auto min = iterable_value_t<It>(opt.value()); | |
auto max = iterable_value_t<It>(std::move(opt).value()); | |
run_while(ctx, [&](auto&& elem) { | |
if (std::invoke(cmp, elem, min) < 0) { | |
min = iterable_value_t<It>(elem); | |
} | |
if (!(std::invoke(cmp, elem, max) < 0)) { | |
max = iterable_value_t<It>(FLUX_FWD(elem)); | |
} | |
return loop_continue; | |
}); | |
return flux::optional<R>(R(std::move(min), std::move(max))); | |
} | |
}; | |
FLUX_EXPORT inline constexpr minmax_t minmax{}; | |
template <typename Derived> | |
template <typename Cmp> | |
requires weak_ordering_for<Cmp, Derived> | |
constexpr auto inline_sequence_base<Derived>::max(Cmp cmp) | |
{ | |
return flux::max(derived(), std::move(cmp)); | |
} | |
template <typename Derived> | |
template <typename Cmp> | |
requires weak_ordering_for<Cmp, Derived> | |
constexpr auto inline_sequence_base<Derived>::min(Cmp cmp) | |
{ | |
return flux::min(derived(), std::move(cmp)); | |
} | |
template <typename Derived> | |
template <typename Cmp> | |
requires weak_ordering_for<Cmp, Derived> | |
constexpr auto inline_sequence_base<Derived>::minmax(Cmp cmp) | |
{ | |
return flux::minmax(derived(), std::move(cmp)); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_MINMAX_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
struct find_min_fn { | |
template <multipass_sequence Seq, | |
weak_ordering_for<Seq> Cmp = std::compare_three_way> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const -> cursor_t<Seq> | |
{ | |
auto min = first(seq); | |
if (!is_last(seq, min)) { | |
for (auto cur = next(seq, min); !is_last(seq, cur); inc(seq, cur)) { | |
if (std::invoke(cmp, read_at(seq, cur), read_at(seq, min)) < 0) { | |
min = cur; | |
} | |
} | |
} | |
return min; | |
} | |
}; | |
struct find_max_fn { | |
template <multipass_sequence Seq, | |
weak_ordering_for<Seq> Cmp = std::compare_three_way> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const -> cursor_t<Seq> | |
{ | |
auto max = first(seq); | |
if (!is_last(seq, max)) { | |
for (auto cur = next(seq, max); !is_last(seq, cur); inc(seq, cur)) { | |
if (!(std::invoke(cmp, read_at(seq, cur), read_at(seq, max)) < 0)) { | |
max = cur; | |
} | |
} | |
} | |
return max; | |
} | |
}; | |
struct find_minmax_fn { | |
template <multipass_sequence Seq, | |
weak_ordering_for<Seq> Cmp = std::compare_three_way> | |
[[nodiscard]] | |
constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const | |
-> minmax_result<cursor_t<Seq>> | |
{ | |
auto min = first(seq); | |
auto max = min; | |
if (!is_last(seq, min)) { | |
for (auto cur = next(seq, min); !is_last(seq, cur); inc(seq, cur)) { | |
auto&& elem = read_at(seq, cur); | |
if (std::invoke(cmp, elem, read_at(seq, min)) < 0) { | |
min = cur; | |
} | |
if (!(std::invoke(cmp, elem, read_at(seq, max)) < 0)) { | |
max = cur; | |
} | |
} | |
} | |
return {std::move(min), std::move(max)}; | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto find_min = detail::find_min_fn{}; | |
FLUX_EXPORT inline constexpr auto find_max = detail::find_max_fn{}; | |
FLUX_EXPORT inline constexpr auto find_minmax = detail::find_minmax_fn{}; | |
template <typename D> | |
template <typename Cmp> | |
requires weak_ordering_for<Cmp, D> | |
constexpr auto inline_sequence_base<D>::find_min(Cmp cmp) | |
{ | |
return flux::find_min(derived(), std::move(cmp)); | |
} | |
template <typename D> | |
template <typename Cmp> | |
requires weak_ordering_for<Cmp, D> | |
constexpr auto inline_sequence_base<D>::find_max(Cmp cmp) | |
{ | |
return flux::find_max(derived(), std::move(cmp)); | |
} | |
template <typename D> | |
template <typename Cmp> | |
requires weak_ordering_for<Cmp, D> | |
constexpr auto inline_sequence_base<D>::find_minmax(Cmp cmp) | |
{ | |
return flux::find_minmax(derived(), std::move(cmp)); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_FIND_MIN_MAX_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_INPLACE_REVERSE_HPP_INCLUDED | |
#define FLUX_ALGORITHM_INPLACE_REVERSE_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
struct inplace_reverse_fn { | |
template <bidirectional_sequence Seq> | |
requires bounded_sequence<Seq> && | |
element_swappable_with<Seq, Seq> | |
constexpr void operator()(Seq&& seq) const | |
{ | |
auto first = flux::first(seq); | |
auto last = flux::last(seq); | |
while (first != last && first != flux::dec(seq, last)) { | |
flux::swap_at(seq, first, last); | |
flux::inc(seq, first); | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto inplace_reverse = detail::inplace_reverse_fn{}; | |
template <typename D> | |
constexpr auto inline_sequence_base<D>::inplace_reverse() | |
requires bounded_sequence<D> && detail::element_swappable_with<D, D> | |
{ | |
return flux::inplace_reverse(derived()); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_INPLACE_REVERSE_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_OUTPUT_TO_HPP_INCLUDED | |
#define FLUX_ALGORITHM_OUTPUT_TO_HPP_INCLUDED | |
#include <cstring> | |
#include <iterator> | |
namespace flux { | |
FLUX_EXPORT | |
struct output_to_t { | |
private: | |
template <typename Seq, typename Iter> | |
static constexpr auto impl(Seq& seq, Iter iter) -> Iter | |
{ | |
for_each(seq, [&iter](auto&& elem) { | |
*iter = FLUX_FWD(elem); | |
++iter; | |
}); | |
return iter; | |
} | |
template <typename Seq, typename Iter> | |
static consteval auto can_memcpy() -> bool | |
{ | |
return contiguous_sequence<Seq> && sized_sequence<Seq> && std::contiguous_iterator<Iter> | |
&& std::is_trivially_copyable_v<value_t<Seq>>; | |
} | |
template <typename Seq, typename Iter> | |
static constexpr auto memcpy_impl(Seq& seq, Iter iter) -> Iter | |
{ | |
auto size = flux::usize(seq); | |
if (size == 0) { | |
return iter; | |
} | |
FLUX_ASSERT(flux::data(seq) != nullptr); | |
std::memmove(std::to_address(iter), flux::data(seq), size * sizeof(value_t<Seq>)); | |
return iter + num::cast<std::iter_difference_t<Iter>>(flux::size(seq)); | |
} | |
public: | |
template <iterable Seq, typename Iter> | |
requires std::weakly_incrementable<Iter> | |
&& std::indirectly_writable<Iter, iterable_element_t<Seq>> | |
constexpr auto operator()(Seq&& seq, Iter iter) const -> Iter | |
{ | |
if constexpr (can_memcpy<Seq, Iter>()) { | |
if (std::is_constant_evaluated()) { | |
return impl(seq, iter); // LCOV_EXCL_LINE | |
} else { | |
return memcpy_impl(seq, iter); | |
} | |
} else { | |
return impl(seq, iter); | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr output_to_t output_to{}; | |
template <typename D> | |
template <typename Iter> | |
requires std::weakly_incrementable<Iter> && std::indirectly_writable<Iter, element_t<D>> | |
constexpr auto inline_sequence_base<D>::output_to(Iter iter) -> Iter | |
{ | |
return flux::output_to(derived(), std::move(iter)); | |
} | |
} | |
#endif // FLUX_ALGORITHM_OUTPUT_TO_HPP_INCLUDED | |
#ifndef FLUX_ALGORITHM_SORT_HPP_INCLUDED | |
#define FLUX_ALGORITHM_SORT_HPP_INCLUDED | |
// flux/op/detail/pqdsort.hpp | |
// | |
// Copyright Orson Peters 2017. | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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) | |
// Modified from Boost.Sort by Orson Peters | |
// https://github.com/boostorg/sort/blob/develop/include/boost/sort/pdqsort/pdqsort.hpp | |
#ifndef FLUX_ALGORITHM_DETAIL_PDQSORT_HPP_INCLUDED | |
#define FLUX_ALGORITHM_DETAIL_PDQSORT_HPP_INCLUDED | |
// flux/op/detail/heap_sift.hpp | |
// | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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) | |
// cmcstl2 - A concept-enabled C++ standard library | |
// | |
// Copyright Eric Niebler 2014 | |
// Copyright Casey Carter 2015 | |
// | |
// Use, modification and distribution is subject to 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) | |
// | |
// Project home: https://github.com/caseycarter/cmcstl2 | |
// | |
//===----------------------------------------------------------------------===// | |
// | |
// The LLVM Compiler Infrastructure | |
// | |
// This file is dual licensed under the MIT and the University of Illinois Open | |
// Source Licenses. See LICENSE.TXT for details. | |
// | |
//===----------------------------------------------------------------------===// | |
#ifndef FLUX_ALGORITHM_DETAIL_HEAP_OPS_HPP_INCLUDED | |
#define FLUX_ALGORITHM_DETAIL_HEAP_OPS_HPP_INCLUDED | |
namespace flux::detail { | |
template <typename Seq, typename Comp> | |
constexpr void sift_up_n(Seq& seq, int_t n, Comp& comp) | |
{ | |
cursor_t<Seq> first = flux::first(seq); | |
if (n > 1) { | |
cursor_t<Seq> last = flux::next(seq, first, n); | |
n = (n - 2) / 2; | |
cursor_t<Seq> i = first + n; | |
if (std::invoke(comp, read_at(seq, i), read_at(seq, dec(seq, last)))) { | |
value_t<Seq> v = move_at(seq, last); | |
do { | |
read_at(seq, last) = move_at(seq, i); | |
last = i; | |
if (n == 0) { | |
break; | |
} | |
n = (n - 1) / 2; | |
i = next(seq, first, n); | |
} while (std::invoke(comp, read_at(seq, i), v)); | |
read_at(seq, last) = std::move(v); | |
} | |
} | |
} | |
template <typename Seq, typename Comp> | |
constexpr void sift_down_n(Seq& seq, int_t n, cursor_t<Seq> start, Comp& comp) | |
{ | |
cursor_t<Seq> first = flux::first(seq); | |
// left-child of start is at 2 * start + 1 | |
// right-child of start is at 2 * start + 2 | |
//auto child = start - first; | |
auto child = flux::distance(seq, first, start); | |
if (n < 2 || (n - 2) / 2 < child) { | |
return; | |
} | |
child = 2 * child + 1; | |
cursor_t<Seq> child_i = flux::next(seq, first, child); | |
if ((child + 1) < n && std::invoke(comp, read_at(seq, child_i), | |
read_at(seq, next(seq, child_i)))) { | |
// right-child exists and is greater than left-child | |
flux::inc(seq, child_i); | |
++child; | |
} | |
// check if we are in heap-order | |
if (std::invoke(comp, read_at(seq, child_i), | |
read_at(seq, start))) { | |
// we are, start is larger than its largest child | |
return; | |
} | |
value_t<Seq> top = move_at(seq, start); | |
do { | |
// we are not in heap-order, swap the parent with it's largest child | |
read_at(seq, start) = move_at(seq, child_i); | |
//*start = nano::iter_move(child_i); | |
start = child_i; | |
if ((n - 2) / 2 < child) { | |
break; | |
} | |
// recompute the child based off of the updated parent | |
child = 2 * child + 1; | |
child_i = next(seq, first, child); //child_i = first + child; | |
if ((child + 1) < n && | |
std::invoke(comp, read_at(seq, child_i), | |
read_at(seq, next(seq, child_i)))) { | |
// right-child exists and is greater than left-child | |
inc(seq, child_i); | |
++child; | |
} | |
// check if we are in heap-order | |
} while (!std::invoke(comp, read_at(seq, child_i), top)); | |
read_at(seq, start) = std::move(top); | |
} | |
template <sequence Seq, typename Comp> | |
constexpr void make_heap(Seq& seq, Comp& comp) | |
{ | |
int_t n = flux::size(seq); | |
auto first = flux::first(seq); | |
if (n > 1) { | |
for (auto start = (n - 2) / 2; start >= 0; --start) { | |
detail::sift_down_n(seq, n, flux::next(seq, first, start), comp); | |
} | |
} | |
} | |
template <sequence Seq, typename Comp> | |
constexpr void pop_heap(Seq& seq, int_t n, Comp& comp) | |
{ | |
auto first = flux::first(seq); | |
if (n > 1) { | |
swap_at(seq, first, next(seq, first, n - 1)); | |
detail::sift_down_n(seq, n - 1, first, comp); | |
} | |
} | |
template <sequence Seq, typename Comp> | |
constexpr void sort_heap(Seq& seq, Comp& comp) | |
{ | |
auto n = flux::size(seq); | |
if (n < 2) { | |
return; | |
} | |
for (auto i = n; i > 1; --i) { | |
pop_heap(seq, i, comp); | |
} | |
} | |
} | |
#endif // FLUX_ALGORITHM_DETAIL_HEAP_OPS_INCLUDED_HPP | |
namespace flux::detail { | |
// Partitions below this size are sorted using insertion sort. | |
inline constexpr int pdqsort_insertion_sort_threshold = 24; | |
// Partitions above this size use Tukey's ninther to select the pivot. | |
inline constexpr int pdqsort_ninther_threshold = 128; | |
// When we detect an already sorted partition, attempt an insertion sort that | |
// allows this amount of element moves before giving up. | |
inline constexpr int pqdsort_partial_insertion_sort_limit = 8; | |
// Must be multiple of 8 due to loop unrolling, and < 256 to fit in unsigned | |
// char. | |
inline constexpr int pdqsort_block_size = 64; | |
// Cacheline size, assumes power of two. | |
inline constexpr int pdqsort_cacheline_size = 64; | |
template <typename T> | |
inline constexpr bool is_default_compare_v = false; | |
template <> | |
inline constexpr bool is_default_compare_v<std::compare_three_way> = true; | |
template <> | |
inline constexpr bool is_default_compare_v<decltype(flux::cmp::reverse_compare)> = true; | |
template <> | |
inline constexpr bool is_default_compare_v<decltype(flux::cmp::compare_floating_point_unchecked)> = true; | |
// Returns floor(log2(n)), assumes n > 0. | |
template <class T> | |
constexpr int log2(T n) | |
{ | |
int log = 0; | |
while (n >>= 1) | |
++log; | |
return log; | |
} | |
// Sorts [begin, end) using insertion sort with the given comparison function. | |
template <sequence Seq, typename Comp, typename Cur = cursor_t<Seq>> | |
constexpr void insertion_sort(Seq& seq, Cur const begin, Cur const end, Comp& comp) | |
{ | |
using T = value_t<Seq>; | |
if (begin == end) { | |
return; | |
} | |
for (auto cur = next(seq, begin); cur != end; inc(seq, cur)) { | |
cursor_t<Seq> sift = cur; | |
cursor_t<Seq> sift_1 = prev(seq, cur); | |
// Compare first so we can avoid 2 moves for an element already | |
// positioned correctly. | |
if (comp(read_at(seq, sift), read_at(seq, sift_1))) { | |
T tmp = move_at(seq, sift); | |
do { | |
read_at(seq, sift) = move_at(seq, sift_1); | |
dec(seq, sift); | |
} while (sift != begin && comp(tmp, read_at(seq, dec(seq, sift_1)))); | |
read_at(seq, sift) = std::move(tmp); | |
} | |
} | |
} | |
// Sorts [begin, end) using insertion sort with the given comparison function. | |
// Assumes | |
// *(begin - 1) is an element smaller than or equal to any element in [begin, | |
// end). | |
template <sequence Seq, typename Comp, typename Cur = cursor_t<Seq>> | |
constexpr void unguarded_insertion_sort(Seq& seq, Cur const begin, Cur const end, | |
Comp& comp) | |
{ | |
using T = value_t<Seq>; | |
if (begin == end) { | |
return; | |
} | |
for (auto cur = next(seq, begin); cur != end; inc(seq, cur)) { | |
cursor_t<Seq> sift = cur; | |
cursor_t<Seq> sift_1 = prev(seq, cur); | |
// Compare first so we can avoid 2 moves for an element already | |
// positioned correctly. | |
if (comp(read_at(seq, sift), read_at(seq, sift_1))) { | |
T tmp = move_at(seq, sift); | |
do { | |
read_at(seq, sift) = move_at(seq, sift_1); | |
dec(seq, sift); | |
} while (comp(tmp, read_at(seq, dec(seq, sift_1)))); | |
read_at(seq, sift) = std::move(tmp); | |
} | |
} | |
} | |
// Attempts to use insertion sort on [begin, end). Will return false if more | |
// than partial_insertion_sort_limit elements were moved, and abort sorting. | |
// Otherwise it will successfully sort and return true. | |
template <sequence Seq, typename Comp, typename Cur = cursor_t<Seq>> | |
constexpr bool partial_insertion_sort(Seq& seq, Cur const begin, Cur const end, Comp& comp) | |
{ | |
using T = value_t<Seq>; | |
if (begin == end) { | |
return true; | |
} | |
int_t limit = 0; | |
for (auto cur = next(seq, begin); cur != end; inc(seq, cur)) { | |
if (limit > pqdsort_partial_insertion_sort_limit) { | |
return false; | |
} | |
cursor_t<Seq> sift = cur; | |
cursor_t<Seq> sift_1 = prev(seq, cur); | |
// Compare first so we can avoid 2 moves for an element already | |
// positioned correctly. | |
if (comp(read_at(seq, sift), read_at(seq, sift_1))) { | |
T tmp = move_at(seq, sift); | |
do { | |
read_at(seq, sift) = move_at(seq, sift_1); | |
dec(seq, sift); | |
} while (sift != begin && comp(tmp, read_at(seq, dec(seq, sift_1)))); | |
read_at(seq, sift) = std::move(tmp); | |
limit += distance(seq, sift, cur); | |
} | |
} | |
return true; | |
} | |
template <sequence Seq, typename Comp> | |
constexpr void sort2(Seq& seq, cursor_t<Seq> a, cursor_t<Seq> b, Comp& comp) | |
{ | |
if (comp(read_at(seq, b), read_at(seq, a))) { | |
swap_at(seq, a, b); | |
} | |
} | |
// Sorts the elements *a, *b and *c using comparison function comp. | |
template <sequence Seq, typename Comp, typename Cur = cursor_t<Seq>> | |
constexpr void sort3(Seq& seq, Cur a, Cur b, Cur c, Comp& comp) | |
{ | |
sort2(seq, a, b, comp); | |
sort2(seq, b, c, comp); | |
sort2(seq, a, b, comp); | |
} | |
template <typename Seq, typename Cur = cursor_t<Seq>> | |
constexpr void swap_offsets(Seq& seq, Cur const first, Cur const last, | |
unsigned char* offsets_l, | |
unsigned char* offsets_r, int num, bool use_swaps) | |
{ | |
using T = value_t<Seq>; | |
if (use_swaps) { | |
// This case is needed for the descending distribution, where we need | |
// to have proper swapping for pdqsort to remain O(n). | |
for (int i = 0; i < num; ++i) { | |
swap_at(seq, next(seq, first, offsets_l[i]), next(seq, last, -offsets_r[i])); | |
} | |
} else if (num > 0) { | |
Cur l = next(seq, first, offsets_l[0]); | |
Cur r = next(seq, last, -offsets_r[0]); | |
T tmp(move_at(seq, l)); | |
read_at(seq, l) = move_at(seq, r); | |
for (int i = 1; i < num; ++i) { | |
l = next(seq, first, offsets_l[i]); | |
read_at(seq, r) = move_at(seq, l); | |
r = next(seq, last, -offsets_r[i]); | |
read_at(seq, l) = move_at(seq, r); | |
} | |
read_at(seq, r) = std::move(tmp); | |
} | |
} | |
// Partitions [begin, end) around pivot *begin using comparison function comp. | |
// Elements equal to the pivot are put in the right-hand partition. Returns the | |
// position of the pivot after partitioning and whether the passed sequence | |
// already was correctly partitioned. Assumes the pivot is a median of at least | |
// 3 elements and that [begin, end) is at least insertion_sort_threshold long. | |
// Uses branchless partitioning. | |
template <typename Seq, typename Cur = cursor_t<Seq>, typename Comp> | |
constexpr std::pair<Cur, bool> | |
partition_right_branchless(Seq& seq, Cur const begin, Cur const end, Comp& comp) | |
{ | |
using T = value_t<Seq>; | |
// Move pivot into local for speed. | |
T pivot(move_at(seq, begin)); | |
Cur first = begin; | |
Cur last = end; | |
// Find the first element greater than or equal than the pivot (the median | |
// of 3 guarantees this exists). | |
while (comp(read_at(seq, inc(seq, first)), pivot)) | |
; | |
// Find the first element strictly smaller than the pivot. We have to guard | |
// this search if there was no element before *first. | |
if (prev(seq, first) == begin) { | |
while (first < last && !comp(read_at(seq, dec(seq, last)), pivot)) | |
; | |
} else { | |
while (!comp(read_at(seq, dec(seq, last)), pivot)) | |
; | |
} | |
// If the first pair of elements that should be swapped to partition are the | |
// same element, the passed in sequence already was correctly partitioned. | |
bool already_partitioned = first >= last; | |
if (!already_partitioned) { | |
swap_at(seq, first, last); | |
inc(seq, first); | |
} | |
// The following branchless partitioning is derived from "BlockQuicksort: | |
// How Branch Mispredictions don't affect Quicksort" by Stefan Edelkamp and | |
// Armin Weiss. | |
alignas(pdqsort_cacheline_size) unsigned char | |
offsets_l_storage[pdqsort_block_size] = {}; | |
alignas(pdqsort_cacheline_size) unsigned char | |
offsets_r_storage[pdqsort_block_size] = {}; | |
unsigned char* offsets_l = offsets_l_storage; | |
unsigned char* offsets_r = offsets_r_storage; | |
int num_l = 0, num_r = 0, start_l = 0, start_r = 0; | |
while (distance(seq, first, last) > 2 * pdqsort_block_size) { | |
// Fill up offset blocks with elements that are on the wrong side. | |
if (num_l == 0) { | |
start_l = 0; | |
Cur cur = first; | |
for (unsigned char i = 0; i < pdqsort_block_size;) { | |
offsets_l[num_l] = i++; | |
num_l += !comp(read_at(seq, cur), pivot); | |
inc(seq, cur); | |
offsets_l[num_l] = i++; | |
num_l += !comp(read_at(seq, cur), pivot); | |
inc(seq, cur); | |
offsets_l[num_l] = i++; | |
num_l += !comp(read_at(seq, cur), pivot); | |
inc(seq, cur); | |
offsets_l[num_l] = i++; | |
num_l += !comp(read_at(seq, cur), pivot); | |
inc(seq, cur); | |
offsets_l[num_l] = i++; | |
num_l += !comp(read_at(seq, cur), pivot); | |
inc(seq, cur); | |
offsets_l[num_l] = i++; | |
num_l += !comp(read_at(seq, cur), pivot); | |
inc(seq, cur); | |
offsets_l[num_l] = i++; | |
num_l += !comp(read_at(seq, cur), pivot); | |
inc(seq, cur); | |
offsets_l[num_l] = i++; | |
num_l += !comp(read_at(seq, cur), pivot); | |
inc(seq, cur); | |
} | |
} | |
if (num_r == 0) { | |
start_r = 0; | |
Cur cur = last; | |
for (unsigned char i = 0; i < pdqsort_block_size;) { | |
offsets_r[num_r] = ++i; | |
num_r += comp(read_at(seq, dec(seq, cur)), pivot); | |
offsets_r[num_r] = ++i; | |
num_r += comp(read_at(seq, dec(seq, cur)), pivot); | |
offsets_r[num_r] = ++i; | |
num_r += comp(read_at(seq, dec(seq, cur)), pivot); | |
offsets_r[num_r] = ++i; | |
num_r += comp(read_at(seq, dec(seq, cur)), pivot); | |
offsets_r[num_r] = ++i; | |
num_r += comp(read_at(seq, dec(seq, cur)), pivot); | |
offsets_r[num_r] = ++i; | |
num_r += comp(read_at(seq, dec(seq, cur)), pivot); | |
offsets_r[num_r] = ++i; | |
num_r += comp(read_at(seq, dec(seq, cur)), pivot); | |
offsets_r[num_r] = ++i; | |
num_r += comp(read_at(seq, dec(seq, cur)), pivot); | |
} | |
} | |
// Swap elements and update block sizes and first/last boundaries. | |
int num = (cmp::min)(num_l, num_r); | |
swap_offsets(seq, first, last, offsets_l + start_l, offsets_r + start_r, num, | |
num_l == num_r); | |
num_l -= num; | |
num_r -= num; | |
start_l += num; | |
start_r += num; | |
if (num_l == 0) | |
inc(seq, first, pdqsort_block_size); | |
if (num_r == 0) | |
inc(seq, last, -pdqsort_block_size); | |
} | |
int_t l_size = 0, r_size = 0; | |
int_t unknown_left = distance(seq, first, last) - ((num_r || num_l) ? pdqsort_block_size : 0); | |
if (num_r) { | |
// Handle leftover block by assigning the unknown elements to the other | |
// block. | |
l_size = unknown_left; | |
r_size = pdqsort_block_size; | |
} else if (num_l) { | |
l_size = pdqsort_block_size; | |
r_size = unknown_left; | |
} else { | |
// No leftover block, split the unknown elements in two blocks. | |
l_size = unknown_left / 2; | |
r_size = unknown_left - l_size; | |
} | |
// Fill offset buffers if needed. | |
if (unknown_left && !num_l) { | |
start_l = 0; | |
Cur cur = first; | |
for (unsigned char i = 0; static_cast<int_t>(i) < l_size;) { | |
offsets_l[num_l] = i++; | |
num_l += !comp(read_at(seq, cur), pivot); | |
inc(seq, cur); | |
} | |
} | |
if (unknown_left && !num_r) { | |
start_r = 0; | |
Cur cur = last; | |
for (unsigned char i = 0; static_cast<int_t>(i) < r_size;) { | |
offsets_r[num_r] = ++i; | |
num_r += comp(read_at(seq, dec(seq, cur)), pivot); | |
} | |
} | |
int num = (cmp::min)(num_l, num_r); | |
swap_offsets(seq, first, last, offsets_l + start_l, offsets_r + start_r, num, | |
num_l == num_r); | |
num_l -= num; | |
num_r -= num; | |
start_l += num; | |
start_r += num; | |
if (num_l == 0) | |
inc(seq, first, l_size); | |
if (num_r == 0) | |
inc(seq, last, -r_size); | |
// We have now fully identified [first, last)'s proper position. Swap the | |
// last elements. | |
if (num_l) { | |
offsets_l += start_l; | |
while (num_l--) { | |
swap_at(seq, next(seq, first, offsets_l[num_l]), dec(seq, last)); | |
} | |
first = last; | |
} | |
if (num_r) { | |
offsets_r += start_r; | |
while (num_r--) { | |
swap_at(seq, next(seq, last, -offsets_r[num_r]), first); | |
inc(seq, first); | |
} | |
last = first; | |
} | |
// Put the pivot in the right place. | |
Cur pivot_pos = prev(seq, first); | |
read_at(seq, begin) = move_at(seq, pivot_pos); | |
read_at(seq, pivot_pos) = std::move(pivot); | |
return std::make_pair(std::move(pivot_pos), already_partitioned); | |
} | |
// Partitions [begin, end) around pivot *begin using comparison function comp. | |
// Elements equal to the pivot are put in the right-hand partition. Returns the | |
// position of the pivot after partitioning and whether the passed sequence | |
// already was correctly partitioned. Assumes the pivot is a median of at least | |
// 3 elements and that [begin, end) is at least insertion_sort_threshold long. | |
template <sequence Seq, typename Comp, typename Cur = cursor_t<Seq>> | |
constexpr std::pair<cursor_t<Seq>, bool> | |
partition_right(Seq& seq, Cur const begin, Cur const end, Comp& comp) | |
{ | |
using T = value_t<Seq>; | |
// Move pivot into local for speed. | |
T pivot(move_at(seq, begin)); | |
cursor_t<Seq> first = begin; | |
cursor_t<Seq> last = end; | |
// Find the first element greater than or equal than the pivot (the median | |
// of 3 guarantees this exists). | |
while (comp(read_at(seq, inc(seq, first)), pivot)) { | |
} | |
// Find the first element strictly smaller than the pivot. We have to guard | |
// this search if there was no element before *first. | |
if (prev(seq, first) == begin) { | |
while (first < last && !comp(read_at(seq, dec(seq, last)), pivot)) { | |
} | |
} else { | |
while (!comp(read_at(seq, dec(seq, last)), pivot)) { | |
} | |
} | |
// If the first pair of elements that should be swapped to partition are the | |
// same element, the passed in sequence already was correctly partitioned. | |
bool already_partitioned = first >= last; | |
// Keep swapping pairs of elements that are on the wrong side of the pivot. | |
// Previously swapped pairs guard the searches, which is why the first | |
// iteration is special-cased above. | |
while (first < last) { | |
swap_at(seq, first, last); | |
while (comp(read_at(seq, inc(seq, first)), pivot)) | |
; | |
while (!comp(read_at(seq, dec(seq, last)), pivot)) | |
; | |
} | |
// Put the pivot in the right place. | |
cursor_t<Seq> pivot_pos = prev(seq, first); | |
read_at(seq, begin) = move_at(seq, pivot_pos); | |
read_at(seq, pivot_pos) = std::move(pivot); | |
return std::make_pair(std::move(pivot_pos), already_partitioned); | |
} | |
// Similar function to the one above, except elements equal to the pivot are put | |
// to the left of the pivot and it doesn't check or return if the passed | |
// sequence already was partitioned. Since this is rarely used (the many equal | |
// case), and in that case pdqsort already has O(n) performance, no block | |
// quicksort is applied here for simplicity. | |
template <sequence Seq, typename Comp, typename Cur = cursor_t<Seq>> | |
constexpr cursor_t<Seq> partition_left(Seq& seq, Cur const begin, Cur const end, Comp& comp) | |
{ | |
using T = value_t<Seq>; | |
T pivot(move_at(seq, begin)); | |
cursor_t<Seq> first = begin; | |
cursor_t<Seq> last = end; | |
while (comp(pivot, read_at(seq, dec(seq, last)))) | |
; | |
if (next(seq, last) == end) { | |
while (first < last && !comp(pivot, read_at(seq, inc(seq, first)))) | |
; | |
} else { | |
while (!comp(pivot, read_at(seq, inc(seq, first)))) | |
; | |
} | |
while (first < last) { | |
swap_at(seq, first, last); | |
while (comp(pivot, read_at(seq, dec(seq, last)))) | |
; | |
while (!comp(pivot, read_at(seq, inc(seq, first)))) | |
; | |
} | |
cursor_t<Seq> pivot_pos = last; | |
read_at(seq, begin) = move_at(seq, pivot_pos); | |
read_at(seq, pivot_pos) = std::move(pivot); | |
return pivot_pos; | |
} | |
template <bool Branchless, typename Seq, typename Comp, | |
typename Cur = cursor_t<Seq>> | |
constexpr void pdqsort_loop(Seq& seq, Cur begin, Cur end, Comp& comp, | |
int bad_allowed, bool leftmost = true) | |
{ | |
using diff_t = int_t; | |
// Use a while loop for tail recursion elimination. | |
while (true) { | |
diff_t size = flux::distance(seq, begin, end); | |
// Insertion sort is faster for small arrays. | |
if (size < pdqsort_insertion_sort_threshold) { | |
if (leftmost) { | |
insertion_sort(seq, begin, end, comp); | |
} else { | |
unguarded_insertion_sort(seq, begin, end, comp); | |
} | |
return; | |
} | |
// Choose pivot as median of 3 or pseudomedian of 9. | |
diff_t s2 = size / 2; | |
if (size > pdqsort_ninther_threshold) { | |
sort3(seq, begin, next(seq, begin, s2), prev(seq, end), comp); | |
sort3(seq, next(seq, begin), next(seq, begin, s2 - 1), next(seq, end, -2), comp); | |
sort3(seq, next(seq, begin, 2), next(seq, begin, s2 + 1), next(seq, end, -3), comp); | |
sort3(seq, next(seq, begin, s2 - 1), next(seq, begin, s2), next(seq, begin, s2 + 1), comp); | |
swap_at(seq, begin, next(seq, begin, s2)); | |
} else { | |
sort3(seq, next(seq, begin, s2), begin, prev(seq, end), comp); | |
} | |
// If *(begin - 1) is the end of the right partition of a previous | |
// partition operation there is no element in [begin, end) that is | |
// smaller than *(begin - 1). Then if our pivot compares equal to | |
// *(begin - 1) we change strategy, putting equal elements in the left | |
// partition, greater elements in the right partition. We do not have to | |
// recurse on the left partition, since it's sorted (all equal). | |
if (!leftmost && !comp(read_at(seq, prev(seq, begin)), read_at(seq, begin))) { | |
begin = next(seq, partition_left(seq, begin, end, comp)); | |
continue; | |
} | |
// Partition and get results. | |
auto [pivot_pos, already_partitioned] = [&] { | |
if constexpr (Branchless) { | |
return partition_right_branchless(seq, begin, end, comp); | |
} else { | |
return partition_right(seq, begin, end, comp); | |
} | |
}(); | |
// Check for a highly unbalanced partition. | |
diff_t l_size = distance(seq, begin, pivot_pos); | |
diff_t r_size = distance(seq, next(seq, pivot_pos), end); | |
bool highly_unbalanced = l_size < size / 8 || r_size < size / 8; | |
// If we got a highly unbalanced partition we shuffle elements to break | |
// many patterns. | |
if (highly_unbalanced) { | |
// If we had too many bad partitions, switch to heapsort to | |
// guarantee O(n log n). | |
if (--bad_allowed == 0) { | |
auto subseq = flux::slice(seq, begin, end); | |
detail::make_heap(subseq, comp); | |
detail::sort_heap(subseq, comp); | |
return; | |
} | |
if (l_size >= pdqsort_insertion_sort_threshold) { | |
swap_at(seq, begin, next(seq, begin, l_size/4)); | |
swap_at(seq, prev(seq, pivot_pos), next(seq, pivot_pos, -l_size/4)); | |
if (l_size > pdqsort_ninther_threshold) { | |
swap_at(seq, next(seq, begin), next(seq, begin, l_size/4 + 1)); | |
swap_at(seq, next(seq, begin, 2), next(seq, begin, l_size/4 + 2)); | |
swap_at(seq, next(seq, pivot_pos, -2), next(seq, pivot_pos, -(l_size/4 + 1))); | |
swap_at(seq, next(seq, pivot_pos, -3), next(seq, pivot_pos, -(l_size/4 + 2))); | |
} | |
} | |
if (r_size >= pdqsort_insertion_sort_threshold) { | |
swap_at(seq, next(seq, pivot_pos), next(seq, pivot_pos, (1 + r_size/4))); | |
swap_at(seq, prev(seq, end), next(seq, end, -r_size/4)); | |
if (r_size > pdqsort_ninther_threshold) { | |
swap_at(seq, next(seq, pivot_pos, 2), next(seq, pivot_pos, 2 + r_size/4)); | |
swap_at(seq, next(seq, pivot_pos, 3), next(seq, pivot_pos, 3 + r_size/4)); | |
swap_at(seq, next(seq, end, -2), next(seq, end, -(1 + r_size/4))); | |
swap_at(seq, next(seq, end, -3), next(seq, end, -(2 + r_size/4))); | |
} | |
} | |
} else { | |
// If we were decently balanced and we tried to sort an already | |
// partitioned sequence try to use insertion sort. | |
if (already_partitioned && | |
partial_insertion_sort(seq, begin, pivot_pos, comp) && | |
partial_insertion_sort(seq, flux::next(seq, pivot_pos), end, comp)) | |
return; | |
} | |
// Sort the left partition first using recursion and do tail recursion | |
// elimination for the right-hand partition. | |
detail::pdqsort_loop<Branchless>(seq, begin, pivot_pos, comp, | |
bad_allowed, leftmost); | |
begin = next(seq, pivot_pos); | |
leftmost = false; | |
} | |
} | |
template <typename Seq, typename Comp> | |
constexpr void pdqsort(Seq& seq, Comp& comp) | |
{ | |
if (is_empty(seq)) { | |
return; | |
} | |
constexpr bool Branchless = | |
is_default_compare_v<std::remove_const_t<Comp>> && | |
std::is_arithmetic_v<value_t<Seq>>; | |
auto comp_wrapper = [&comp](auto&& lhs, auto&& rhs) -> bool { | |
return std::is_lt(std::invoke(comp, FLUX_FWD(lhs), FLUX_FWD(rhs))); | |
}; | |
detail::pdqsort_loop<Branchless>(seq, | |
first(seq), | |
last(seq), | |
comp_wrapper, | |
detail::log2(size(seq))); | |
} | |
} // namespace flux::detail | |
#endif // FLUX_ALGORITHM_DETAIL_PDQSORT_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
struct sort_fn { | |
template <random_access_sequence Seq, typename Cmp = std::compare_three_way> | |
requires bounded_sequence<Seq> && | |
element_swappable_with<Seq, Seq> && | |
weak_ordering_for<Cmp, Seq> | |
constexpr auto operator()(Seq&& seq, Cmp cmp = {}) const | |
{ | |
auto wrapper = flux::unchecked(flux::from_fwd_ref(FLUX_FWD(seq))); | |
detail::pdqsort(wrapper, cmp); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto sort = detail::sort_fn{}; | |
template <typename D> | |
template <typename Cmp> | |
requires random_access_sequence<D> && | |
bounded_sequence<D> && | |
detail::element_swappable_with<D, D> && | |
weak_ordering_for<Cmp, D> | |
constexpr void inline_sequence_base<D>::sort(Cmp cmp) | |
{ | |
return flux::sort(derived(), std::ref(cmp)); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_SORT_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_SWAP_ELEMENTS_HPP_INCLUDED | |
#define FLUX_ALGORITHM_SWAP_ELEMENTS_HPP_INCLUDED | |
namespace flux { | |
FLUX_EXPORT | |
struct swap_elements_t { | |
template <iterable It1, iterable It2> | |
requires std::swappable_with<iterable_element_t<It1>, iterable_element_t<It2>> | |
constexpr auto operator()(It1&& it1, It2&& it2) const -> void | |
{ | |
iteration_context auto ctx1 = iterate(it1); | |
iteration_context auto ctx2 = iterate(it2); | |
while (true) { | |
auto opt1 = next_element(ctx1); | |
auto opt2 = next_element(ctx2); | |
if (opt1.has_value() && opt2.has_value()) { | |
std::ranges::swap(*std::move(opt1), *std::move(opt2)); | |
} else { | |
break; | |
} | |
} | |
} | |
}; | |
FLUX_EXPORT inline constexpr swap_elements_t swap_elements{}; | |
} | |
#endif // FLUX_ALGORITHM_SWAP_ELEMENTS_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_TO_HPP_INCLUDED | |
#define FLUX_ALGORITHM_TO_HPP_INCLUDED | |
#if defined(__cpp_lib_ranges_to_container) && (__cpp_lib_ranges_to_container >= 202202L) | |
# define FLUX_HAVE_FROM_RANGE_CONSTRUCTORS | |
#endif | |
namespace flux { | |
FLUX_EXPORT | |
struct from_iterable_t { | |
explicit from_iterable_t() = default; | |
}; | |
FLUX_EXPORT inline constexpr auto from_iterable = from_iterable_t{}; | |
namespace detail { | |
template <typename C, typename It, typename... Args> | |
concept direct_iterable_constructible = std::constructible_from<C, It, Args...>; | |
template <typename C, typename It, typename... Args> | |
concept from_iterable_constructible = std::constructible_from<C, from_iterable_t, It, Args...>; | |
#ifdef FLUX_HAVE_FROM_RANGE_CONSTRUCTORS | |
template <typename C, typename It, typename... Args> | |
concept from_range_constructible | |
= std::constructible_from<C, std::from_range_t, decltype(as_range(std::declval<It>())), | |
Args...>; | |
#else | |
template <typename...> | |
concept from_range_constructible = false; | |
#endif | |
template <typename C> | |
using container_value_t = typename C::value_type; // Let's just assume it exists | |
template <iterable It> | |
using common_iterator_t | |
= std::ranges::iterator_t<decltype(std::views::common(as_range(std::declval<It>())))>; | |
template <typename C, typename It, typename... Args> | |
concept cpp17_range_constructible | |
= std::constructible_from<C, common_iterator_t<It>, common_iterator_t<It>, Args...>; | |
template <typename C, typename Elem> | |
concept container_appendable = requires(C& c, Elem&& elem) { | |
requires( | |
requires { c.emplace_back(FLUX_FWD(elem)); } || requires { c.push_back(FLUX_FWD(elem)); } | |
|| requires { c.emplace(c.end(), FLUX_FWD(elem)); } | |
|| requires { c.insert(c.end(), FLUX_FWD(elem)); }); | |
}; | |
template <typename C, typename It, typename... Args> | |
concept container_convertible | |
= direct_iterable_constructible<C, It, Args...> || from_iterable_constructible<C, It, Args...> | |
|| from_range_constructible<C, It, Args...> || cpp17_range_constructible<C, It, Args...> | |
|| (std::constructible_from<C, Args...> && container_appendable<C, iterable_element_t<It>>); | |
template <typename C> | |
concept reservable_container = | |
std::ranges::sized_range<C> && | |
requires (C& c, std::ranges::range_size_t<C> sz) { | |
c.reserve(sz); | |
{ c.max_size() } -> std::same_as<std::ranges::range_size_t<C>>; | |
{ c.capacity() } -> std::same_as<std::ranges::range_size_t<C>>; | |
}; | |
template <typename Container> | |
constexpr auto container_appender(Container& c) | |
{ | |
return [&c](auto&& elem) { | |
if constexpr (requires { c.emplace_back(FLUX_FWD(elem)); }) { | |
c.emplace_back(FLUX_FWD(elem)); | |
} else if constexpr (requires { c.push_back(FLUX_FWD(elem)); }) { | |
c.push_back(FLUX_FWD(elem)); | |
// } else if constexpr (requires { c.emplace(c.end(), FLUX_FWD(elem)); }) { | |
// c.emplace(c.end(), FLUX_FWD(elem)); | |
} else { | |
c.insert(c.end(), FLUX_FWD(elem)); | |
} | |
}; | |
} | |
template <template <typename...> typename C, typename Seq, typename... Args> | |
using ctad_direct_iterable = decltype(C(FLUX_DECLVAL(Seq), FLUX_DECLVAL(Args)...)); | |
template <template <typename...> typename C, typename Seq, typename... Args> | |
using ctad_from_iterable = decltype((C(from_iterable, FLUX_DECLVAL(Seq), FLUX_DECLVAL(Args)...))); | |
#ifdef FLUX_HAVE_FROM_RANGE_CONSTRUCTORS | |
template <template <typename...> typename C, typename It, typename... Args> | |
using ctad_from_range | |
= decltype((C(std::from_range, as_range(std::declval<It>()), std::declval<Args>()...))); | |
#else | |
template <typename...> | |
struct nonesuch { }; | |
template <template <typename...> typename, typename... Args> | |
using ctad_from_range = typename nonesuch<Args...>::type; | |
#endif | |
template <template <typename...> typename C, typename Seq, typename... Args> | |
using ctad_from_iters = decltype(C(FLUX_DECLVAL(common_iterator_t<Seq>), | |
FLUX_DECLVAL(common_iterator_t<Seq>), FLUX_DECLVAL(Args)...)); | |
template <template <typename...> typename C, typename Seq, typename... Args> | |
concept can_deduce_container_type = requires { typename ctad_direct_iterable<C, Seq, Args...>; } | |
|| requires { typename ctad_from_iterable<C, Seq, Args...>; } | |
|| requires { typename ctad_from_range<C, Seq, Args...>; } | |
|| requires { typename ctad_from_iters<C, Seq, Args...>; } | |
|| (sizeof...(Args) == 0 && requires { typename C<iterable_value_t<Seq>>; }); | |
template <typename T> | |
struct type_t { | |
using type = T; | |
}; | |
template <template <typename...> typename C, typename Seq, typename... Args> | |
requires can_deduce_container_type<C, Seq, Args...> | |
consteval auto deduce_container_type() | |
{ | |
if constexpr (requires { typename ctad_direct_iterable<C, Seq, Args...>; }) { | |
return type_t<decltype(C(FLUX_DECLVAL(Seq), FLUX_DECLVAL(Args)...))>{}; | |
} else if constexpr (requires { typename ctad_from_iterable<C, Seq, Args...>; }) { | |
return type_t<decltype((C(from_iterable, FLUX_DECLVAL(Seq), FLUX_DECLVAL(Args)...)))>{}; | |
#ifdef FLUX_HAVE_FROM_RANGE_CONSTRUCTORS | |
} else if constexpr (requires { typename ctad_from_range<C, Seq, Args...>; }) { | |
return type_t<decltype(( | |
C(std::from_range, as_range(std::declval<Seq>()), std::declval<Args>()...)))>{}; | |
#endif | |
} else if constexpr (requires { typename ctad_from_iters<C, Seq, Args...>; }) { | |
using I = common_iterator_t<Seq>; | |
return type_t<decltype(C(FLUX_DECLVAL(I), FLUX_DECLVAL(I), FLUX_DECLVAL(Args)...))>{}; | |
} else { | |
static_assert(requires { typename C<value_t<Seq>>; }); | |
return type_t<C<value_t<Seq>>>{}; | |
} | |
} | |
template <template <typename...> typename C, typename Seq, typename... Args> | |
requires can_deduce_container_type<C, Seq, Args...> | |
using deduced_container_t = typename decltype(deduce_container_type<C, Seq, Args...>())::type; | |
} // namespace detail | |
FLUX_EXPORT | |
template <typename Container, iterable It, typename... Args> | |
requires(std::convertible_to<iterable_element_t<It>, detail::container_value_t<Container>> | |
&& detail::container_convertible<Container, It, Args...>) | |
|| iterable<iterable_element_t<It>> | |
constexpr auto to(It&& it, Args&&... args) -> Container | |
{ | |
if constexpr (std::convertible_to<element_t<It>, detail::container_value_t<Container>>) { | |
if constexpr (detail::direct_iterable_constructible<Container, It, Args...>) { | |
return Container(FLUX_FWD(it), FLUX_FWD(args)...); | |
} else if constexpr (detail::from_iterable_constructible<Container, It, Args...>) { | |
return Container(from_iterable, FLUX_FWD(it), FLUX_FWD(args)...); | |
#ifdef FLUX_HAVE_FROM_RANGE_CONSTRUCTORS | |
} else if constexpr (detail::from_range_constructible<Container, It, Args...>) { | |
return Container(std::from_range, as_range(FLUX_FWD(it)), FLUX_FWD(args)...); | |
#endif | |
} else if constexpr (detail::cpp17_range_constructible<Container, It, Args...>) { | |
auto view_ = std::views::common(as_range(FLUX_FWD(it))); | |
return Container(view_.begin(), view_.end(), FLUX_FWD(args)...); | |
} else { | |
auto c = Container(FLUX_FWD(args)...); | |
if constexpr (sized_iterable<It> && detail::reservable_container<Container>) { | |
c.reserve(num::cast<std::size_t>(flux::iterable_size(it))); | |
} | |
for_each(it, detail::container_appender(c)); | |
return c; | |
} | |
} else { | |
static_assert(iterable<iterable_element_t<It>>); | |
return flux::to<Container>( | |
flux::map(flux::from_fwd_ref(FLUX_FWD(it)), | |
[](auto&& elem) { | |
return flux::to<detail::container_value_t<Container>>(FLUX_FWD(elem)); | |
}), | |
FLUX_FWD(args)...); | |
} | |
} | |
FLUX_EXPORT | |
template <template <typename...> typename Container, sequence Seq, typename... Args> | |
requires detail::can_deduce_container_type<Container, Seq, Args...> && | |
detail::container_convertible< | |
detail::deduced_container_t<Container, Seq, Args...>, Seq, Args...> | |
constexpr auto to(Seq&& seq, Args&&... args) | |
{ | |
using C_ = detail::deduced_container_t<Container, Seq, Args...>; | |
return flux::to<C_>(FLUX_FWD(seq), FLUX_FWD(args)...); | |
} | |
template <typename D> | |
template <typename Container, typename... Args> | |
constexpr auto inline_sequence_base<D>::to(Args&&... args) -> Container | |
{ | |
return flux::to<Container>(derived(), FLUX_FWD(args)...); | |
} | |
template <typename D> | |
template <template <typename...> typename Container, typename... Args> | |
constexpr auto inline_sequence_base<D>::to(Args&&... args) | |
{ | |
return flux::to<Container>(derived(), FLUX_FWD(args)...); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_TO_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_WRITE_TO_HPP_INCLUDED | |
#define FLUX_ALGORITHM_WRITE_TO_HPP_INCLUDED | |
#include <iosfwd> | |
namespace flux { | |
FLUX_EXPORT | |
struct write_to_t { | |
template <iterable It, typename OStream> | |
requires std::derived_from<OStream, std::ostream> | |
auto operator()(It&& it, OStream& os) const -> OStream& | |
{ | |
bool first = true; | |
os << '['; | |
for_each(it, [&os, &first](auto&& elem) { | |
if (first) { | |
first = false; | |
} else { | |
os << ", "; | |
} | |
if constexpr (iterable<iterable_element_t<It>>) { | |
write_to_t{}(FLUX_FWD(elem), os); | |
} else { | |
os << FLUX_FWD(elem); | |
} | |
}); | |
os << ']'; | |
return os; | |
} | |
}; | |
FLUX_EXPORT inline constexpr write_to_t write_to{}; | |
template <typename Derived> | |
auto inline_sequence_base<Derived>::write_to(std::ostream& os) -> std::ostream& | |
{ | |
return flux::write_to(derived(), os); | |
} | |
} // namespace flux | |
#endif // FLUX_ALGORITHM_WRITE_TO_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_ALGORITHM_ZIP_ALGORITHMS_HPP_INCLUDED | |
#define FLUX_ALGORITHM_ZIP_ALGORITHMS_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
struct zip_for_each_while_fn { | |
template <typename Pred, sequence... Seqs> | |
requires std::invocable<Pred&, element_t<Seqs>...> | |
&& boolean_testable<std::invoke_result_t<Pred&, element_t<Seqs>...>> | |
constexpr auto operator()(Pred pred, Seqs&&... seqs) const -> iteration_result | |
{ | |
if constexpr (sizeof...(Seqs) == 0) { | |
return {}; | |
} else if constexpr (sizeof...(Seqs) == 1) { | |
return flux::for_each_while(seqs..., std::ref(pred)); | |
} else { | |
#ifdef _MSC_VER | |
// Do things the sad ugly way :( | |
auto ctxs = std::tuple<iteration_context_t<Seqs>...>( | |
emplace_from([&] { return iterate(seqs); })...); | |
auto opts | |
= std::apply([](auto&... ctx) { return std::tuple(next_element(ctx)...); }, ctxs); | |
while (true) { | |
bool all_have_value | |
= std::apply([](auto&... opt) { return (opt.has_value() && ...); }, opts); | |
if (!all_have_value) { | |
return iteration_result::complete; | |
} | |
auto res = std::apply([&](auto&... opt) { return pred(opt.value_unchecked()...); }, | |
opts); | |
if (!res) { | |
return iteration_result::incomplete; | |
} | |
opts = std::apply([&](auto&... ctx) { return std::tuple(next_element(ctx)...); }, | |
ctxs); | |
} | |
#else | |
return [&pred, ... ctxs = iterate(seqs)]() mutable { | |
return [&pred, &ctxs..., ... opts = step(ctxs, std::identity {})]() mutable { | |
while (true) { | |
if (!(opts.has_value() && ...)) { | |
return iteration_result::complete; | |
} | |
if (!std::invoke(pred, opts.value_unchecked()...)) { | |
return iteration_result::incomplete; | |
} | |
((opts = step(ctxs, std::identity {})), ...); | |
} | |
}(); | |
}(); | |
#endif | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto zip_for_each_while = detail::zip_for_each_while_fn{}; | |
namespace detail { | |
struct zip_for_each_fn { | |
template <std::move_constructible Func, sequence... Seqs> | |
requires std::invocable<Func&, element_t<Seqs>...> | |
constexpr auto operator()(Func func, Seqs&&... seqs) const -> Func | |
{ | |
zip_for_each_while([&func](auto&&... elems) { | |
std::invoke(func, FLUX_FWD(elems)...); | |
return true; | |
}, seqs...); | |
return func; | |
} | |
}; | |
struct zip_find_if_fn { | |
template <typename Pred, sequence... Seqs> | |
requires std::predicate<Pred&, element_t<Seqs>...> | |
constexpr auto operator()(Pred pred, Seqs&&... seqs) const | |
-> std::tuple<cursor_t<Seqs>...> | |
{ | |
if constexpr (sizeof...(Seqs) == 0) { | |
return {}; | |
} else if constexpr (sizeof...(Seqs) == 1) { | |
return std::tuple<cursor_t<Seqs>...>(flux::find_if(seqs..., std::ref(pred))); | |
} else { | |
return [&pred, &... seqs = seqs, ... curs = first(seqs)]() mutable { | |
while ((!is_last(seqs, curs) && ...)) { | |
if (std::invoke(pred, read_at_unchecked(seqs, curs)...)) { | |
break; | |
} | |
((inc(seqs, curs)), ...); | |
} | |
return std::tuple<cursor_t<Seqs>...>(std::move(curs)...); | |
}(); | |
} | |
} | |
}; | |
template <typename Func, typename Init, typename... Seqs> | |
using zip_fold_result_t = std::decay_t<std::invoke_result_t<Func&, Init, element_t<Seqs>...>>; | |
template <typename Func, typename Init, typename R, typename... Seqs> | |
concept zip_foldable = | |
std::invocable<Func&, R, element_t<Seqs>...> && | |
std::convertible_to<Init, R> && | |
std::assignable_from<R&, std::invoke_result_t<Func&, R, element_t<Seqs>...>>; | |
struct zip_fold_fn { | |
template <typename Func, std::movable Init, sequence... Seqs, | |
typename R = zip_fold_result_t<Func, Init, Seqs...>> | |
requires zip_foldable<Func, Init, R, Seqs...> | |
constexpr auto operator()(Func func, Init init, Seqs&&... seqs) const -> R | |
{ | |
R init_ = R(std::move(init)); | |
zip_for_each_while([&func, &init_](auto&&... elems) { | |
init_ = std::invoke(func, std::move(init_), FLUX_FWD(elems)...); | |
return true; | |
}, seqs...); | |
return init_; | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto zip_for_each = detail::zip_for_each_fn{}; | |
FLUX_EXPORT inline constexpr auto zip_find_if = detail::zip_find_if_fn{}; | |
FLUX_EXPORT inline constexpr auto zip_fold = detail::zip_fold_fn{}; | |
} // namespace pred | |
#endif // FLUX_ALGORITHM_ZIP_ALGORITHMS_HPP_INCLUDED | |
#endif // FLUX_ALGORITHM_HPP_INCLUDED | |
// Copyright (c) 2024 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_HPP_INCLUDED | |
#define FLUX_SEQUENCE_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_ARRAY_PTR_HPP_INCLUDED | |
#define FLUX_SEQUENCE_ARRAY_PTR_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename From, typename To> | |
concept non_slicing_ptr_convertible = std::convertible_to<From (*)[], To (*)[]>; | |
struct make_array_ptr_unchecked_fn; | |
} | |
FLUX_EXPORT | |
template <typename T> | |
requires (std::is_object_v<T> && !std::is_abstract_v<T>) | |
struct array_ptr : inline_sequence_base<array_ptr<T>> { | |
private: | |
T* data_ = nullptr; | |
int_t sz_ = 0; | |
friend struct detail::make_array_ptr_unchecked_fn; | |
constexpr array_ptr(T* ptr, int_t sz) noexcept : data_(ptr), sz_(sz) { } | |
public: | |
array_ptr() = default; | |
template <typename U> | |
requires (!std::same_as<U, T> && | |
detail::non_slicing_ptr_convertible<U, T>) | |
constexpr explicit(false) array_ptr(array_ptr<U> const& other) noexcept | |
: data_(flux::data(other)), | |
sz_(flux::size(other)) | |
{} | |
template <typename Seq> | |
requires (!decays_to<Seq, array_ptr> && | |
contiguous_sequence<Seq> && | |
sized_sequence<Seq> && | |
detail::non_slicing_ptr_convertible<std::remove_reference_t<element_t<Seq>>, T>) | |
constexpr explicit array_ptr(Seq& seq) | |
: data_(flux::data(seq)), | |
sz_(flux::size(seq)) | |
{} | |
template <typename Seq> | |
requires (contiguous_sequence<Seq> && | |
sized_sequence<Seq> && | |
detail::non_slicing_ptr_convertible<std::remove_reference_t<element_t<Seq>>, T>) | |
constexpr array_ptr(detail::ref_adaptor<Seq> ref) | |
: data_(flux::data(ref)), | |
sz_(flux::size(ref)) | |
{} | |
array_ptr(array_ptr&&) = default; | |
array_ptr& operator=(array_ptr&&) = default; | |
~array_ptr() = default; | |
array_ptr(array_ptr const&) requires std::is_const_v<T> = default; | |
array_ptr& operator=(array_ptr const&) requires std::is_const_v<T> = default; | |
friend constexpr auto operator==(array_ptr const& lhs, array_ptr const& rhs) -> bool | |
{ | |
return std::ranges::equal_to{}(lhs.data_, rhs.data_) && lhs.sz_ == rhs.sz_; | |
} | |
struct flux_sequence_traits : default_sequence_traits { | |
static constexpr auto first(array_ptr const&) -> index_t { return 0; } | |
static constexpr auto is_last(array_ptr const& self, index_t idx) -> bool | |
{ | |
return idx >= self.sz_; | |
} | |
static constexpr auto inc(array_ptr const& self, index_t& idx) -> void | |
{ | |
FLUX_DEBUG_ASSERT(idx < self.sz_); | |
idx = num::add(idx, int_t {1}); | |
} | |
static constexpr auto read_at(array_ptr const& self, index_t idx) -> T& | |
{ | |
indexed_bounds_check(idx, self.sz_); | |
return self.data_[idx]; | |
} | |
static constexpr auto read_at_unchecked(array_ptr const& self, index_t idx) -> T& | |
{ | |
return self.data_[idx]; | |
} | |
static constexpr auto dec(array_ptr const& , index_t& idx) -> void | |
{ | |
FLUX_DEBUG_ASSERT(idx > 0); | |
--idx; | |
} | |
static constexpr auto last(array_ptr const& self) -> index_t { return self.sz_; } | |
static constexpr auto inc(array_ptr const& self, index_t& idx, int_t offset) -> void | |
{ | |
index_t nxt = num::add(idx, offset); | |
FLUX_DEBUG_ASSERT(nxt >= 0); | |
FLUX_DEBUG_ASSERT(nxt <= self.sz_); | |
idx = nxt; | |
} | |
static constexpr auto distance(array_ptr const&, index_t from, index_t to) -> int_t | |
{ | |
return num::sub(to, from); | |
} | |
static constexpr auto size(array_ptr const& self) -> int_t { return self.sz_; } | |
static constexpr auto data(array_ptr const& self) -> T* { return self.data_; } | |
static constexpr auto for_each_while(array_ptr const& self, auto&& pred) | |
{ | |
index_t idx = 0; | |
for (; idx < self.sz_; idx++) { | |
if (!std::invoke(pred, self.data_[idx])) { | |
break; | |
} | |
} | |
return idx; | |
} | |
}; | |
}; | |
template <contiguous_sequence Seq> | |
array_ptr(Seq&) -> array_ptr<std::remove_reference_t<element_t<Seq>>>; | |
template <contiguous_sequence Seq> | |
array_ptr(detail::ref_adaptor<Seq>) -> array_ptr<std::remove_reference_t<element_t<Seq>>>; | |
namespace detail { | |
struct make_array_ptr_unchecked_fn { | |
template <typename T> | |
requires (std::is_object_v<T> && !std::is_abstract_v<T>) | |
constexpr auto operator()(T* ptr, num::integral auto size) const -> array_ptr<T> | |
{ | |
return array_ptr<T>(ptr, num::checked_cast<int_t>(size)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto make_array_ptr_unchecked = | |
detail::make_array_ptr_unchecked_fn{}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_ARRAY_PTR_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_BITSET_HPP_INCLUDED | |
#define FLUX_SEQUENCE_BITSET_HPP_INCLUDED | |
#include <bitset> | |
namespace flux { | |
template <std::size_t N> | |
struct sequence_traits<std::bitset<N>> : default_sequence_traits { | |
using value_type = bool; | |
using self_t = std::bitset<N>; | |
static constexpr auto first(self_t const&) -> std::size_t { return 0; } | |
static constexpr auto is_last(self_t const&, std::size_t idx) { return idx == N; } | |
static constexpr auto read_at(self_t& self, std::size_t idx) | |
-> typename std::bitset<N>::reference | |
{ | |
return self[idx]; | |
} | |
static constexpr auto read_at(self_t const& self, std::size_t idx) -> bool | |
{ | |
return self[idx]; | |
} | |
static constexpr auto move_at(self_t const& self, std::size_t idx) -> bool | |
{ | |
return self[idx]; | |
} | |
static constexpr auto inc(self_t const&, std::size_t& idx) -> std::size_t& | |
{ | |
return ++idx; | |
} | |
static constexpr auto dec(self_t const&, std::size_t& idx) -> std::size_t& | |
{ | |
return --idx; | |
} | |
static constexpr auto inc(self_t const&, std::size_t& idx, std::ptrdiff_t off) | |
-> std::size_t& | |
{ | |
return idx += static_cast<std::size_t>(off); | |
} | |
static constexpr auto distance(self_t const&, std::size_t from, std::size_t to) | |
-> std::ptrdiff_t | |
{ | |
return static_cast<std::ptrdiff_t>(to) - static_cast<std::ptrdiff_t>(from); | |
} | |
static constexpr auto last(self_t const&) -> std::size_t { return N; } | |
static constexpr auto size(self_t const&) -> std::ptrdiff_t { return N; } | |
}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_BITSET_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_GENERATOR_HPP_INCLUDED | |
#define FLUX_SEQUENCE_GENERATOR_HPP_INCLUDED | |
#include <coroutine> | |
#include <utility> | |
namespace flux { | |
FLUX_EXPORT | |
template <typename ElemT> | |
struct generator : inline_sequence_base<generator<ElemT>> { | |
using yielded_type = std::conditional_t<std::is_reference_v<ElemT>, | |
ElemT, | |
ElemT const&>; | |
struct promise_type; | |
using handle_type = std::coroutine_handle<promise_type>; | |
struct promise_type { | |
auto initial_suspend() { return std::suspend_always{}; } | |
auto final_suspend() noexcept { return std::suspend_always{}; } | |
auto get_return_object() | |
{ | |
return generator(handle_type::from_promise(*this)); | |
} | |
auto yield_value(yielded_type elem) | |
{ | |
ptr_ = std::addressof(elem); | |
return std::suspend_always{}; | |
} | |
auto unhandled_exception() { throw; } | |
void return_void() noexcept {} | |
std::add_pointer_t<yielded_type> ptr_; | |
}; | |
private: | |
handle_type coro_; | |
explicit generator(handle_type&& handle) : coro_(std::move(handle)) {} | |
friend struct sequence_traits<generator>; | |
public: | |
generator(generator&& other) noexcept | |
: coro_(std::exchange(other.coro_, {})) | |
{} | |
generator& operator=(generator&& other) noexcept | |
{ | |
std::swap(coro_, other.coro_); | |
return *this; | |
} | |
~generator() | |
{ | |
if (coro_) { coro_.destroy(); } | |
} | |
}; | |
template <typename T> | |
struct sequence_traits<generator<T>> : default_sequence_traits | |
{ | |
private: | |
struct cursor_type { | |
cursor_type(cursor_type&&) = default; | |
cursor_type& operator=(cursor_type&&) = default; | |
private: | |
cursor_type() = default; | |
friend struct sequence_traits; | |
}; | |
using self_t = generator<T>; | |
public: | |
static auto first(self_t& self) { | |
self.coro_.resume(); | |
return cursor_type{}; | |
} | |
static auto is_last(self_t& self, cursor_type const&) -> bool | |
{ | |
return self.coro_.done(); | |
} | |
static auto inc(self_t& self, cursor_type& cur) -> cursor_type& | |
{ | |
self.coro_.resume(); | |
return cur; | |
} | |
static auto read_at(self_t& self, cursor_type const&) -> decltype(auto) | |
{ | |
return static_cast<typename self_t::yielded_type>(*self.coro_.promise().ptr_); | |
} | |
}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_GENERATOR_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_GETLINES_HPP_INCLUDED | |
#define FLUX_SEQUENCE_GETLINES_HPP_INCLUDED | |
#include <iosfwd> | |
#include <string> | |
namespace flux { | |
namespace detail { | |
template <typename CharT, typename Traits> | |
struct getlines_sequence : inline_sequence_base<getlines_sequence<CharT, Traits>> { | |
private: | |
using istream_type = std::basic_istream<CharT, Traits>; | |
using string_type = std::basic_string<CharT, Traits>; | |
using char_type = CharT; | |
istream_type* is_ = nullptr; | |
string_type str_; | |
char_type delim_{}; | |
public: | |
getlines_sequence() = default; | |
getlines_sequence(istream_type& is, char_type delim) | |
: is_(std::addressof(is)), | |
delim_(delim) | |
{} | |
getlines_sequence(getlines_sequence&&) = default; | |
getlines_sequence& operator=(getlines_sequence&&) = default; | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
explicit cursor_type() = default; | |
cursor_type(cursor_type&&) = default; | |
cursor_type& operator=(cursor_type&&) = default; | |
}; | |
using self_t = getlines_sequence; | |
public: | |
static constexpr auto first(self_t& self) -> cursor_type | |
{ | |
cursor_type cur{}; | |
inc(self, cur); | |
return cur; | |
} | |
static constexpr auto is_last(self_t& self, cursor_type const&) -> bool | |
{ | |
return !(self.is_ && static_cast<bool>(*self.is_)); | |
} | |
static constexpr auto inc(self_t& self, cursor_type& cur) -> cursor_type& | |
{ | |
flux::assert_(self.is_ != nullptr, | |
"flux::getlines::inc(): attempt to iterate after stream EOF"); | |
if (!std::getline(*self.is_, self.str_, self.delim_)) { | |
self.is_ = nullptr; | |
} | |
return cur; | |
} | |
static constexpr auto read_at(self_t& self, cursor_type const&) -> string_type const& | |
{ | |
return self.str_; | |
} | |
}; | |
}; | |
struct getlines_fn { | |
template <typename CharT, typename Traits> | |
constexpr auto operator()(std::basic_istream<CharT, Traits>& istream, CharT delim) const | |
{ | |
return getlines_sequence<CharT, Traits>(istream, delim); | |
} | |
template <typename CharT, typename Traits> | |
constexpr auto operator()(std::basic_istream<CharT, Traits>& istream) const | |
{ | |
return getlines_sequence<CharT, Traits>(istream, istream.widen('\n')); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto getlines = detail::getlines_fn{}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_GETLINES_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_FROM_ISTREAM_HPP_INCLUDED | |
#define FLUX_SEQUENCE_FROM_ISTREAM_HPP_INCLUDED | |
#include <iosfwd> | |
namespace flux { | |
namespace detail { | |
template <typename T, typename CharT, typename Traits> | |
requires std::default_initializable<T> | |
class istream_adaptor : public inline_sequence_base<istream_adaptor<T, CharT, Traits>> { | |
using istream_type = std::basic_istream<CharT, Traits>; | |
istream_type* is_ = nullptr; | |
T val_ = T(); | |
friend struct sequence_traits<istream_adaptor>; | |
public: | |
explicit istream_adaptor(istream_type& is) | |
: is_(std::addressof(is)) | |
{} | |
}; | |
template <std::default_initializable T> | |
struct from_istream_fn { | |
template <typename CharT, typename Traits> | |
[[nodiscard]] | |
auto operator()(std::basic_istream<CharT, Traits>& is) const | |
{ | |
return istream_adaptor<T, CharT, Traits>(is); | |
} | |
}; | |
} // namespace detail | |
template <typename T, typename CharT, typename Traits> | |
struct sequence_traits<detail::istream_adaptor<T, CharT, Traits>> : default_sequence_traits | |
{ | |
private: | |
struct cursor_type { | |
cursor_type(cursor_type&&) = default; | |
cursor_type& operator=(cursor_type&&) = default; | |
private: | |
friend struct sequence_traits; | |
explicit cursor_type() = default; | |
}; | |
using self_t = detail::istream_adaptor<T, CharT, Traits>; | |
public: | |
static auto first(self_t& self) -> cursor_type | |
{ | |
cursor_type cur{}; | |
inc(self, cur); | |
return cur; | |
} | |
static auto is_last(self_t& self, cursor_type const&) -> bool | |
{ | |
return !(self.is_ && static_cast<bool>(*self.is_)); | |
} | |
static auto read_at(self_t& self, cursor_type const&) -> T const& | |
{ | |
return self.val_; | |
} | |
static auto inc(self_t& self, cursor_type& cur) -> cursor_type& | |
{ | |
if (!(self.is_ && (*self.is_ >> self.val_))) { | |
self.is_ = nullptr; | |
} | |
return cur; | |
} | |
}; | |
FLUX_EXPORT | |
template <std::default_initializable T> | |
inline constexpr auto from_istream = detail::from_istream_fn<T>{}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_ISTREAM_HPP_INCLUDED | |
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_ISTREAMBUF_HPP_INCLUDED | |
#define FLUX_SEQUENCE_ISTREAMBUF_HPP_INCLUDED | |
#include <iosfwd> | |
namespace flux { | |
namespace detail { | |
template <typename CharT, typename Traits> | |
void derives_from_streambuf_test(std::basic_streambuf<CharT, Traits>&); | |
template <typename T> | |
concept derives_from_streambuf = requires (T& t) { derives_from_streambuf_test(t); }; | |
struct from_istreambuf_fn { | |
template <typename CharT, typename Traits> | |
[[nodiscard]] | |
auto operator()(std::basic_streambuf<CharT, Traits>* streambuf) const -> sequence auto | |
{ | |
FLUX_ASSERT(streambuf != nullptr); | |
return flux::mut_ref(*streambuf); | |
} | |
template <typename CharT, typename Traits> | |
[[nodiscard]] | |
auto operator()(std::basic_istream<CharT, Traits>& istream) const -> sequence auto | |
{ | |
return flux::mut_ref(*istream.rdbuf()); | |
} | |
}; | |
} // namespace detail | |
template <detail::derives_from_streambuf Streambuf> | |
struct sequence_traits<Streambuf> : default_sequence_traits | |
{ | |
private: | |
struct cursor_type { | |
cursor_type(cursor_type&&) = default; | |
cursor_type& operator=(cursor_type&&) = default; | |
private: | |
friend struct sequence_traits; | |
cursor_type() = default; | |
}; | |
using traits_type = typename Streambuf::traits_type; | |
using char_type = typename Streambuf::char_type; | |
public: | |
static auto first(Streambuf&) -> cursor_type { return {}; } | |
static auto is_last(Streambuf& self, cursor_type const&) -> bool | |
{ | |
return self.sgetc() == traits_type::eof(); | |
} | |
static auto inc(Streambuf& self, cursor_type& cur) -> cursor_type& | |
{ | |
self.sbumpc(); | |
return cur; | |
} | |
static auto read_at(Streambuf& self, cursor_type const&) -> char_type | |
{ | |
return traits_type::to_char_type(self.sgetc()); | |
} | |
}; | |
FLUX_EXPORT | |
inline constexpr auto from_istreambuf = detail::from_istreambuf_fn{}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_ISTREAMBUF_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_RANGE_HPP_INCLUDED | |
#define FLUX_SEQUENCE_RANGE_HPP_INCLUDED | |
#include <ranges> | |
namespace flux { | |
namespace detail { | |
template <typename R> | |
concept can_const_iterate = | |
std::ranges::input_range<R> && std::ranges::input_range<R const> && | |
std::same_as<std::ranges::iterator_t<R>, std::ranges::iterator_t<R const>>; | |
template <typename R, bool IsConst> | |
struct range_sequence : inline_sequence_base<range_sequence<R, IsConst>> { | |
private: | |
R rng_; | |
using V = std::conditional_t<IsConst, R const, R>; | |
public: | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
class cursor_type { | |
std::ranges::iterator_t<V> iter; | |
friend struct flux_sequence_traits; | |
constexpr explicit cursor_type(std::ranges::iterator_t<V> iter) : iter(std::move(iter)) {} | |
public: | |
cursor_type() = default; | |
cursor_type(cursor_type&&) = default; | |
cursor_type& operator=(cursor_type&&) = default; | |
cursor_type(cursor_type const&) requires std::ranges::forward_range<V> | |
= default; | |
cursor_type& operator=(cursor_type const&) requires std::ranges::forward_range<V> | |
= default; | |
friend auto operator==(cursor_type const&, cursor_type const&) -> bool = default; | |
friend auto operator<=>(cursor_type const& lhs, cursor_type const& rhs) | |
-> std::strong_ordering | |
= default; | |
}; | |
public: | |
using value_type = std::ranges::range_value_t<R>; | |
template <typename Self> | |
requires (!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto first(Self& self) -> cursor_type | |
{ | |
if constexpr (IsConst) { | |
return cursor_type{std::ranges::cbegin(self.rng_)}; | |
} else { | |
return cursor_type{std::ranges::begin(self.rng_)}; | |
} | |
} | |
template <typename Self> | |
requires (!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto is_last(Self& self, cursor_type const& cur) -> bool | |
{ | |
if constexpr (IsConst) { | |
return cur.iter == std::ranges::cend(self.rng_); | |
} else { | |
return cur.iter == std::ranges::end(self.rng_); | |
} | |
} | |
template <typename Self> | |
requires (!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto inc(Self& self, cursor_type& cur) | |
{ | |
bounds_check(!is_last(self, cur)); | |
++cur.iter; | |
} | |
template <typename Self> | |
requires (!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto read_at(Self& self, cursor_type const& cur) -> decltype(auto) | |
{ | |
bounds_check(!is_last(self, cur)); | |
return *cur.iter; | |
} | |
template <typename Self> | |
requires (!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto move_at(Self& self, cursor_type const& cur) -> decltype(auto) | |
{ | |
bounds_check(!is_last(self, cur)); | |
return std::ranges::iter_move(cur.iter); | |
} | |
template <typename Self> | |
requires (!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto dec(Self& self, cursor_type& cur) | |
requires std::ranges::bidirectional_range<R> | |
{ | |
bounds_check(cur != first(self)); | |
--cur.iter; | |
} | |
template <typename Self> | |
requires(!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto inc(Self& self, cursor_type& cur, int_t offset) | |
requires std::ranges::random_access_range<R> | |
{ | |
if (offset < 0) { | |
bounds_check(num::add(offset, distance(self, first(self), cur)) >= 0); | |
} else if (offset > 0) { | |
bounds_check(offset <= distance(self, cur, last(self))); | |
} | |
cur.iter += num::cast<std::ranges::range_difference_t<V>>(offset); | |
} | |
template <typename Self> | |
requires(!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto distance(Self&, cursor_type const& from, cursor_type const& to) | |
-> int_t | |
requires std::ranges::random_access_range<R> | |
{ | |
return num::cast<int_t>(std::ranges::distance(from.iter, to.iter)); | |
} | |
template <typename Self> | |
requires (!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto data(Self& self) | |
requires std::ranges::contiguous_range<R> | |
{ | |
if constexpr (IsConst) { | |
return std::ranges::cdata(self.rng_); | |
} else { | |
return std::ranges::data(self.rng_); | |
} | |
} | |
template <typename Self> | |
requires (!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto last(Self& self) -> cursor_type | |
requires std::ranges::common_range<R> | |
{ | |
return cursor_type{std::ranges::end(self.rng_)}; | |
} | |
template <typename Self> | |
requires(!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto size(Self& self) -> int_t | |
requires std::ranges::sized_range<R> | |
{ | |
return num::cast<int_t>(std::ranges::ssize(self.rng_)); | |
} | |
template <typename Self> | |
requires (!std::is_const_v<Self> || can_const_iterate<V>) | |
static constexpr auto for_each_while(Self& self, auto&& pred) -> cursor_type | |
{ | |
auto iter = std::ranges::begin(self.rng_); | |
auto const end = std::ranges::end(self.rng_); | |
while (iter != end) { | |
if (!std::invoke(pred, *iter)) { | |
break; | |
} | |
++iter; | |
} | |
return cursor_type{std::move(iter)}; | |
} | |
}; | |
constexpr explicit range_sequence(R rng) : rng_(std::move(rng)) {} | |
constexpr auto begin() | |
{ | |
if constexpr (IsConst) { | |
return std::ranges::cbegin(rng_); | |
} else { | |
return std::ranges::begin(rng_); | |
} | |
} | |
constexpr auto begin() const requires std::ranges::range<R const> | |
{ | |
return std::ranges::begin(rng_); | |
} | |
constexpr auto end() | |
{ | |
if constexpr (IsConst) { | |
return std::ranges::cend(rng_); | |
} else { | |
return std::ranges::end(rng_); | |
} | |
} | |
constexpr auto end() const requires std::ranges::range<R const> | |
{ | |
return std::ranges::end(rng_); | |
} | |
}; | |
struct from_range_fn { | |
template <std::ranges::viewable_range R> | |
requires std::ranges::input_range<R> | |
constexpr auto operator()(R&& rng) const | |
{ | |
return range_sequence<std::views::all_t<R>, false>(std::views::all(FLUX_FWD(rng))); | |
} | |
}; | |
struct from_crange_fn { | |
template <typename R, typename C = std::remove_reference_t<R> const&> | |
requires std::ranges::input_range<C> && | |
std::ranges::viewable_range<C> | |
constexpr auto operator()(R&& rng) const | |
{ | |
if constexpr (std::is_lvalue_reference_v<R>) { | |
return range_sequence<std::views::all_t<C>, true>(std::views::all(static_cast<C>(rng))); | |
} else { | |
return range_sequence<std::views::all_t<R>, true>(std::views::all(FLUX_FWD(rng))); | |
} | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto from_range = detail::from_range_fn{}; | |
FLUX_EXPORT inline constexpr auto from_crange = detail::from_crange_fn{}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_RANGE_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_REPEAT_HPP_INCLUDED | |
#define FLUX_SEQUENCE_REPEAT_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <bool> | |
struct repeat_data {}; | |
template <> | |
struct repeat_data<false> { std::size_t count; }; | |
template <std::movable T, bool IsInfinite> | |
struct repeat_sequence : inline_sequence_base<repeat_sequence<T, IsInfinite>> | |
{ | |
private: | |
T obj_; | |
FLUX_NO_UNIQUE_ADDRESS repeat_data<IsInfinite> data_; | |
public: | |
constexpr explicit repeat_sequence(decays_to<T> auto&& obj) | |
requires IsInfinite | |
: obj_(FLUX_FWD(obj)) | |
{} | |
constexpr repeat_sequence(decays_to<T> auto&& obj, std::size_t count) | |
requires (!IsInfinite) | |
: obj_(FLUX_FWD(obj)), | |
data_{count} | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
using self_t = repeat_sequence; | |
public: | |
static inline constexpr bool is_infinite = IsInfinite; | |
static constexpr auto first(self_t const&) -> std::size_t { return 0; } | |
static constexpr auto is_last(self_t const& self, std::size_t cur) -> bool | |
{ | |
if constexpr (IsInfinite) { | |
return false; | |
} else { | |
return cur >= self.data_.count; | |
} | |
} | |
static constexpr auto inc(self_t const&, std::size_t& cur) -> void | |
{ | |
++cur; | |
} | |
static constexpr auto read_at(self_t const& self, std::size_t) -> T const& | |
{ | |
return self.obj_; | |
} | |
static constexpr auto dec(self_t const&, std::size_t& cur) -> void | |
{ | |
--cur; | |
} | |
static constexpr auto inc(self_t const&, std::size_t& cur, int_t offset) -> void | |
{ | |
cur += static_cast<std::size_t>(offset); | |
} | |
static constexpr auto distance(self_t const&, std::size_t from, std::size_t to) -> int_t | |
{ | |
return num::cast<int_t>(to) - num::cast<int_t>(from); | |
} | |
static constexpr auto for_each_while(self_t const& self, auto&& pred) -> std::size_t | |
{ | |
if constexpr (IsInfinite) { | |
std::size_t idx = 0; | |
while (true) { | |
if (!std::invoke(pred, std::as_const(self.obj_))) { | |
return idx; | |
} | |
++idx; | |
} | |
} else { | |
std::size_t idx = 0; | |
for ( ; idx < self.data_.count; ++idx) { | |
if (!std::invoke(pred, std::as_const(self.obj_))) { | |
break; | |
} | |
} | |
return idx; | |
} | |
} | |
static constexpr auto last(self_t const& self) -> std::size_t | |
requires (!IsInfinite) | |
{ | |
return self.data_.count; | |
} | |
static constexpr auto size(self_t const& self) -> int_t | |
requires(!IsInfinite) | |
{ | |
return num::cast<int_t>(self.data_.count); | |
} | |
}; | |
}; | |
struct repeat_fn { | |
template <typename T> | |
requires std::movable<std::decay_t<T>> | |
constexpr auto operator()(T&& obj) const | |
{ | |
return repeat_sequence<std::decay_t<T>, true>(FLUX_FWD(obj)); | |
} | |
template <typename T> | |
requires std::movable<std::decay_t<T>> | |
constexpr auto operator()(T&& obj, num::integral auto count) const | |
{ | |
auto c = num::checked_cast<int_t>(count); | |
if (c < 0) { | |
runtime_error("Negative count passed to repeat()"); | |
} | |
return repeat_sequence<std::decay_t<T>, false>( | |
FLUX_FWD(obj), num::checked_cast<std::size_t>(c)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto repeat = detail::repeat_fn{}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_REPEAT_HPP_INCLUDED | |
// Copyright (c) 2023 Tristan Brindle (tcbrindle at gmail 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 FLUX_SEQUENCE_UNFOLD_HPP_INCLUDED | |
#define FLUX_SEQUENCE_UNFOLD_HPP_INCLUDED | |
namespace flux { | |
namespace detail { | |
template <typename R, typename Func> | |
struct unfold_sequence : inline_sequence_base<unfold_sequence<R, Func>> { | |
private: | |
R state_; | |
Func func_; | |
public: | |
template <typename T> | |
requires std::constructible_from<R, T> | |
constexpr explicit unfold_sequence(Func&& func, T&& seed) | |
: state_(FLUX_FWD(seed)), | |
func_(std::move(func)) | |
{} | |
struct flux_sequence_traits : default_sequence_traits { | |
private: | |
struct cursor_type { | |
friend struct flux_sequence_traits; | |
cursor_type(cursor_type&&) = default; | |
cursor_type& operator=(cursor_type&&) = default; | |
private: | |
cursor_type() = default; | |
}; | |
using self_t = unfold_sequence; | |
public: | |
static constexpr bool is_infinite = true; | |
static constexpr auto first(self_t&) -> cursor_type { return {}; } | |
static constexpr auto is_last(self_t&, cursor_type const&) -> bool { return false; } | |
static constexpr auto inc(self_t& self, cursor_type&) -> void | |
{ | |
self.state_ = std::invoke(self.func_, std::move(self.state_)); | |
} | |
static constexpr auto read_at(self_t& self, cursor_type const&) -> R const& | |
{ | |
return self.state_; | |
} | |
static constexpr auto for_each_while(self_t& self, auto&& pred) -> cursor_type | |
{ | |
while (true) { | |
if (!std::invoke(pred, self.state_)) { | |
break; | |
} | |
self.state_ = std::invoke(self.func_, std::move(self.state_)); | |
} | |
return {}; | |
} | |
}; | |
}; | |
struct unfold_fn { | |
template <typename Func, typename Seed, | |
typename R = std::decay_t<std::invoke_result_t<Func&, Seed>>> | |
requires std::constructible_from<R, Seed> && | |
std::invocable<Func&, R> && | |
std::assignable_from<R&, std::invoke_result_t<Func&, R>> | |
[[nodiscard]] | |
constexpr auto operator()(Func func, Seed&& seed) const -> sequence auto | |
{ | |
return unfold_sequence<R, Func>(std::move(func), FLUX_FWD(seed)); | |
} | |
}; | |
} // namespace detail | |
FLUX_EXPORT inline constexpr auto unfold = detail::unfold_fn{}; | |
} // namespace flux | |
#endif // FLUX_SEQUENCE_UNFOLD_INCLUDED | |
#endif // FLUX_SEQUENCE_HPP_INCLUDED | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment