From a6f903126c957f9bd29a2f34eb91b12abcb0a9ab Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 02:31:10 +0900 Subject: [PATCH 01/14] Implement `run_length_sequence` --- .editorconfig | 4 + include/iris/error/throwf.hpp | 50 +++ include/iris/error/throwf_format.hpp | 64 ++++ include/iris/exception.hpp | 86 +---- include/iris/indexed_value.hpp | 178 +++++++++ include/iris/interval_set.hpp | 17 +- include/iris/iterator.hpp | 89 ++--- include/iris/ranges.hpp | 156 ++++++++ include/iris/requirements.hpp | 6 + include/iris/run_length_sequence.hpp | 523 +++++++++++++++++++++++++++ include/iris/type_traits.hpp | 19 + test/CMakeLists.txt | 2 + test/indexed_value.cpp | 102 ++++++ test/run_length_sequence.cpp | 243 +++++++++++++ test/type_traits.cpp | 23 +- 15 files changed, 1416 insertions(+), 146 deletions(-) create mode 100644 .editorconfig create mode 100644 include/iris/error/throwf.hpp create mode 100644 include/iris/error/throwf_format.hpp create mode 100644 include/iris/indexed_value.hpp create mode 100644 include/iris/run_length_sequence.hpp create mode 100644 test/indexed_value.cpp create mode 100644 test/run_length_sequence.cpp diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..137266e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*.{cpp,h,hpp,ixx,ipp}] +charset = utf-8 diff --git a/include/iris/error/throwf.hpp b/include/iris/error/throwf.hpp new file mode 100644 index 0000000..eac5e0c --- /dev/null +++ b/include/iris/error/throwf.hpp @@ -0,0 +1,50 @@ +#ifndef IRIS_ZZ_ERROR_THROWF_HPP +#define IRIS_ZZ_ERROR_THROWF_HPP + +#include // IWYU pragma: keep + +#include // IWYU pragma: export +#include // IWYU pragma: export +#include +#include + +#ifndef IRIS_CONFIG_THROW_IMPL +# define IRIS_CONFIG_THROW_IMPL(...) throw __VA_ARGS__ +#endif + +#ifndef IRIS_CONFIG_THROW_NORETURN +# define IRIS_CONFIG_THROW_NORETURN [[noreturn]] +#endif + +namespace iris { + +inline namespace error_functions { + +// This function can be used to strongly assume optimization in some performance- +// critical paths as some compilers fail to optimize the plain `throw` statement +// even though the statement itself should imply `[[noreturn]]`. +template +IRIS_CONFIG_THROW_NORETURN void throwf() +{ + static_assert(std::is_base_of_v); + static_assert(std::is_constructible_v); + IRIS_CONFIG_THROW_IMPL(E{}); +} + +// This function can be used to strongly assume optimization in some performance- +// critical paths as some compilers fail to optimize the plain `throw` statement +// even though the statement itself should imply `[[noreturn]]`. +template + requires std::is_constructible_v +IRIS_CONFIG_THROW_NORETURN void throwf(Arg&& arg, Rest&&... rest) +{ + static_assert(std::is_base_of_v); + static_assert(!std::is_base_of_v>, "don't copy/move construct exception types directly"); + IRIS_CONFIG_THROW_IMPL(E{std::forward(arg), std::forward(rest)...}); +} + +} // error_functions + +} // iris + +#endif diff --git a/include/iris/error/throwf_format.hpp b/include/iris/error/throwf_format.hpp new file mode 100644 index 0000000..0403227 --- /dev/null +++ b/include/iris/error/throwf_format.hpp @@ -0,0 +1,64 @@ +#ifndef IRIS_ZZ_ERROR_THROWF_FORMAT_HPP +#define IRIS_ZZ_ERROR_THROWF_FORMAT_HPP + +// SPDX-License-Identifier: MIT + +#include // IWYU pragma: keep +#include // IWYU pragma: export +#include + +#include // IWYU pragma: export +#include +#include +#include +#include +#include + +namespace iris { + +namespace detail { + +template +concept constructible_from_string_like_types = + std::is_constructible_v || + std::is_constructible_v || + std::is_constructible_v; + +} // detail + +inline namespace error_functions { + +template + requires detail::constructible_from_string_like_types +IRIS_CONFIG_THROW_NORETURN void throwf(std::format_string fmt, Args&&... args) +{ + static_assert(std::is_base_of_v); + IRIS_CONFIG_THROW_IMPL(E{std::format(std::move(fmt), std::forward(args)...)}); +} + +template + requires detail::constructible_from_string_like_types +IRIS_CONFIG_THROW_NORETURN void throwf(Arg0&& arg0, std::format_string fmt, Args&&... args) +{ + static_assert(std::is_base_of_v); + IRIS_CONFIG_THROW_IMPL(E{std::forward(arg0), std::format(std::move(fmt), std::forward(args)...)}); +} + +template + requires detail::constructible_from_string_like_types +IRIS_CONFIG_THROW_NORETURN void throwf(Arg0&& arg0, Arg1&& arg1, std::format_string fmt, Args&&... args) +{ + static_assert(std::is_base_of_v); + IRIS_CONFIG_THROW_IMPL( + E{ + std::forward(arg0), std::forward(arg1), + std::format(std::move(fmt), std::forward(args)...) + } + ); +} + +} // error_functions + +} // iris + +#endif diff --git a/include/iris/exception.hpp b/include/iris/exception.hpp index 058644d..990b788 100644 --- a/include/iris/exception.hpp +++ b/include/iris/exception.hpp @@ -1,88 +1,14 @@ #ifndef IRIS_ZZ_EXCEPTION_HPP #define IRIS_ZZ_EXCEPTION_HPP -#include // IWYU pragma: keep - -#include - -#include -#include -#include -#include -#include -#include -#include - -#ifndef IRIS_CONFIG_THROW_IMPL -# define IRIS_CONFIG_THROW_IMPL(...) throw __VA_ARGS__ -#endif - -#ifndef IRIS_CONFIG_THROW_NORETURN -# define IRIS_CONFIG_THROW_NORETURN [[noreturn]] -#endif - -namespace iris { - -namespace detail { +// SPDX-License-Identifier: MIT -template -concept constructible_from_string_like_types = - std::is_constructible_v || - std::is_constructible_v || - std::is_constructible_v; - -} // detail - -inline namespace error_functions { - -template -IRIS_CONFIG_THROW_NORETURN void throwf() -{ - static_assert(std::is_base_of_v); - static_assert(std::is_constructible_v); - IRIS_CONFIG_THROW_IMPL(E{}); -} - -template - requires std::is_constructible_v -IRIS_CONFIG_THROW_NORETURN void throwf(Arg&& arg, Rest&&... rest) -{ - static_assert(std::is_base_of_v); - static_assert(!std::is_base_of_v>, "don't copy/move construct exception types directly"); - IRIS_CONFIG_THROW_IMPL(E{std::forward(arg), std::forward(rest)...}); -} - -template - requires detail::constructible_from_string_like_types -IRIS_CONFIG_THROW_NORETURN void throwf(std::format_string fmt, Args&&... args) -{ - static_assert(std::is_base_of_v); - IRIS_CONFIG_THROW_IMPL(E{std::format(std::move(fmt), std::forward(args)...)}); -} - -template - requires detail::constructible_from_string_like_types -IRIS_CONFIG_THROW_NORETURN void throwf(Arg0&& arg0, std::format_string fmt, Args&&... args) -{ - static_assert(std::is_base_of_v); - IRIS_CONFIG_THROW_IMPL(E{std::forward(arg0), std::format(std::move(fmt), std::forward(args)...)}); -} - -template - requires detail::constructible_from_string_like_types -IRIS_CONFIG_THROW_NORETURN void throwf(Arg0&& arg0, Arg1&& arg1, std::format_string fmt, Args&&... args) -{ - static_assert(std::is_base_of_v); - IRIS_CONFIG_THROW_IMPL( - E{ - std::forward(arg0), std::forward(arg1), - std::format(std::move(fmt), std::forward(args)...) - } - ); -} +#include // IWYU pragma: keep -} // error_functions +#include // IWYU pragma: export +#include // IWYU pragma: export -} // iris +#include // IWYU pragma: export +#include // IWYU pragma: export #endif diff --git a/include/iris/indexed_value.hpp b/include/iris/indexed_value.hpp new file mode 100644 index 0000000..aed5466 --- /dev/null +++ b/include/iris/indexed_value.hpp @@ -0,0 +1,178 @@ +#ifndef IRIS_ZZ_INDEXED_VALUE_HPP +#define IRIS_ZZ_INDEXED_VALUE_HPP + +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include + +#include + +namespace iris { + +template +struct indexed_value +{ + using index_type = IndexT; + using value_type = T; + + IndexT index; + T value; + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator indexed_value() & + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return { + static_cast(index), + static_cast(value) + }; + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator indexed_value() const& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return { + static_cast(index), + static_cast(value) + }; + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator indexed_value() && + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return { + static_cast(static_cast(index)), + static_cast(static_cast(value)) + }; + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator indexed_value() const&& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return { + static_cast(static_cast(index)), + static_cast(static_cast(value)) + }; + } +}; + +template +[[nodiscard]] constexpr decltype(auto) get(indexed_value& elem) noexcept +{ + static_assert(I < 2); + if constexpr (I == 0) { return (elem.index); } + else if constexpr (I == 1) { return (elem.value); } +} + +template +[[nodiscard]] constexpr decltype(auto) get(indexed_value const& elem) noexcept +{ + static_assert(I < 2); + if constexpr (I == 0) { return (elem.index); } + else if constexpr (I == 1) { return (elem.value); } +} + +template +[[nodiscard]] constexpr decltype(auto) get(indexed_value&& elem) noexcept +{ + static_assert(I < 2); + if constexpr (I == 0) { return static_cast(elem.index); } + else if constexpr (I == 1) { return static_cast(elem.value); } +} + +template +[[nodiscard]] constexpr decltype(auto) get(indexed_value const&& elem) noexcept +{ + static_assert(I < 2); + if constexpr (I == 0) { return static_cast(elem.index); } + else if constexpr (I == 1) { return static_cast(elem.value); } +} + +} // iris + +template + requires requires { + typename std::common_type_t; + typename std::common_type_t; + } +struct std::common_type< + iris::indexed_value, + iris::indexed_value +> +{ + using type = iris::indexed_value< + std::common_type_t, + std::common_type_t + >; +}; + +template< + class IndexT, class T, + class IndexU, class U, + template class TQual, + template class UQual +> + requires requires { + typename std::common_reference_t, UQual>; + typename std::common_reference_t, UQual>; + } +struct std::basic_common_reference< + iris::indexed_value, + iris::indexed_value, + TQual, UQual +> +{ + using type = iris::indexed_value< + std::common_reference_t, UQual>, + std::common_reference_t, UQual> + >; +}; + +template +struct std::tuple_size> + : std::integral_constant +{}; + +template +struct std::tuple_element<0, iris::indexed_value> +{ + using type = IndexT; +}; +template +struct std::tuple_element<1, iris::indexed_value> +{ + using type = T; +}; + +#endif diff --git a/include/iris/interval_set.hpp b/include/iris/interval_set.hpp index 8746d73..d7797e6 100644 --- a/include/iris/interval_set.hpp +++ b/include/iris/interval_set.hpp @@ -4,7 +4,6 @@ // SPDX-License-Identifier: MIT #include // IWYU pragma: keep -#include #include #include @@ -29,20 +28,24 @@ class interval_set using map_type = MapT; using offset_type = IntervalT::value_type; - class const_iterator : public iterator_base + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + class const_iterator { - using typename iterator_base::iterator_base_type; - static_assert(std::bidirectional_iterator); - iterator_base_type it_; + map_type::const_iterator it_; public: + using iterator_concept = std::bidirectional_iterator_tag; + using iterator_category = std::bidirectional_iterator_tag; + using value_type = IntervalT; - using pointer = IntervalT const*; using reference = IntervalT; + using difference_type = interval_set::difference_type; constexpr const_iterator() noexcept = default; - constexpr explicit const_iterator(iterator_base_type it) noexcept + constexpr explicit const_iterator(map_type::const_iterator it) noexcept : it_(std::move(it)) {} diff --git a/include/iris/iterator.hpp b/include/iris/iterator.hpp index 47aecc7..51d5edc 100644 --- a/include/iris/iterator.hpp +++ b/include/iris/iterator.hpp @@ -6,64 +6,43 @@ #include // IWYU pragma: keep #include -#include +#include +#include namespace iris { -template -struct iterator_tags_base; - -template - requires requires { - typename std::iterator_traits::iterator_category; - typename std::iterator_traits::iterator_concept; - } -struct iterator_tags_base -{ - using iterator_base_type = It; - using iterator_category = std::iterator_traits::iterator_category; - using iterator_concept = std::iterator_traits::iterator_concept; - - [[nodiscard]] constexpr bool operator==(iterator_tags_base const&) const noexcept = default; - [[nodiscard]] constexpr std::strong_ordering operator<=>(iterator_tags_base const&) const noexcept = default; -}; - -template - requires - requires { typename std::iterator_traits::iterator_category; } && - (!requires { typename std::iterator_traits::iterator_concept; }) -struct iterator_tags_base -{ - using iterator_base_type = It; - using iterator_category = std::iterator_traits::iterator_category; - - [[nodiscard]] constexpr bool operator==(iterator_tags_base const&) const noexcept = default; - [[nodiscard]] constexpr std::strong_ordering operator<=>(iterator_tags_base const&) const noexcept = default; -}; - -template - requires - (!requires { typename std::iterator_traits::iterator_category; }) && - requires { typename std::iterator_traits::iterator_concept; } -struct iterator_tags_base -{ - using iterator_base_type = It; - using iterator_concept = std::iterator_traits::iterator_concept; - - [[nodiscard]] constexpr bool operator==(iterator_tags_base const&) const noexcept = default; - [[nodiscard]] constexpr std::strong_ordering operator<=>(iterator_tags_base const&) const noexcept = default; -}; - -// ---------------------------------------------- - -template -struct iterator_base : iterator_tags_base -{ - using difference_type = std::iterator_traits::difference_type; - - [[nodiscard]] constexpr bool operator==(iterator_base const&) const noexcept = default; - [[nodiscard]] constexpr std::strong_ordering operator<=>(iterator_base const&) const noexcept = default; -}; +namespace detail { + +template +std::input_iterator_tag iter_concept_of(); + +template +std::forward_iterator_tag iter_concept_of(); + +template +std::bidirectional_iterator_tag iter_concept_of(); + +template +std::random_access_iterator_tag iter_concept_of(); + +template +std::contiguous_iterator_tag iter_concept_of(); + +} // detail + +template +using iter_concept_t = decltype(detail::iter_concept_of()); + +template +using iter_cat_t = std::iterator_traits::iterator_category; + +template +concept has_iter_cat = requires { typename iter_cat_t; }; + +// min(Tag, Limit) on the standard tag hierarchy +template + requires std::derived_from || std::derived_from +using clamp_iter_tag_t = std::conditional_t, Limit, Tag>; } // iris diff --git a/include/iris/ranges.hpp b/include/iris/ranges.hpp index 89fd5a8..a8509a6 100644 --- a/include/iris/ranges.hpp +++ b/include/iris/ranges.hpp @@ -203,6 +203,162 @@ concept back_emplaceable = default_back_emplaceable ); +template +concept default_front_emplaceable = requires(T c) { + std::forward(c).emplace_front(); +}; + +template +concept front_emplaceable = + requires(T c, std::ranges::range_value_t> v) { + std::forward(c).emplace_front(std::move(v)); + } && + ( + !std::default_initializable>> || + default_front_emplaceable + ); + +namespace detail { + +struct front_fn +{ + template + static constexpr bool has_front = requires(R&& r) { std::forward(r).front(); }; + + template + requires has_front + [[nodiscard]] static constexpr auto&& operator()(R&& r) noexcept + { + return std::forward(r).front(); + } + + template + requires (!has_front) + [[nodiscard]] static constexpr auto&& operator()(R&& r) noexcept + { + return *std::ranges::begin(std::forward(r)); + } +}; + +struct back_fn +{ + template + static constexpr bool has_back = requires(R&& r) { std::forward(r).back(); }; + + template + requires has_back + [[nodiscard]] static constexpr auto&& operator()(R&& r) noexcept + { + return std::forward(r).back(); + } + + template + requires (!has_back) + [[nodiscard]] static constexpr auto&& operator()(R&& r) noexcept + { + return *std::ranges::prev(std::ranges::end(std::forward(r))); + } +}; + +} // detail + +[[nodiscard]] inline constexpr detail::front_fn front{}; +[[nodiscard]] inline constexpr detail::back_fn back{}; + +namespace detail { + +struct emplace_front_ref_fn +{ + template + [[nodiscard]] static constexpr auto& operator()(ContainerT& cont, Args&&... args) + noexcept(noexcept(cont.emplace_front(std::forward(args)...))) + { + if constexpr (std::is_void_v(args)...))>) { + cont.emplace_front(std::forward(args)...); + return front(cont); + } else { + return cont.emplace_front(std::forward(args)...); + } + } +}; + +struct emplace_back_ref_fn +{ + template + [[nodiscard]] static constexpr auto& operator()(ContainerT& cont, Args&&... args) + noexcept(noexcept(cont.emplace_back(std::forward(args)...))) + { + if constexpr (std::is_void_v(args)...))>) { + cont.emplace_back(std::forward(args)...); + return back(cont); + } else { + return cont.emplace_back(std::forward(args)...); + } + } +}; + +} // detail + +[[nodiscard]] inline constexpr detail::emplace_front_ref_fn emplace_front_ref{}; +[[nodiscard]] inline constexpr detail::emplace_back_ref_fn emplace_back_ref{}; + + +namespace detail { + +struct weak_pop_front_fn +{ + template + static constexpr bool has_pop_front = requires (ContainerT& cont) { + cont.pop_front(); + }; + + template + requires has_pop_front + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.pop_front())) + { + cont.pop_front(); + } + + template + requires (!has_pop_front) + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.erase(std::ranges::begin(cont)))) + { + cont.erase(std::ranges::begin(cont)); + } +}; + +struct weak_pop_back_fn +{ + template + static constexpr bool has_pop_back = requires (ContainerT& cont) { + cont.pop_back(); + }; + + template + requires has_pop_back + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.pop_back())) + { + cont.pop_back(); + } + + template + requires (!has_pop_back) + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.erase(std::ranges::prev(std::ranges::end(cont))))) + { + cont.erase(std::ranges::prev(std::ranges::end(cont))); + } +}; + +} // detail + +[[nodiscard]] inline constexpr detail::weak_pop_front_fn weak_pop_front{}; +[[nodiscard]] inline constexpr detail::weak_pop_back_fn weak_pop_back{}; + + template concept default_emplaceable = requires(T c) { std::forward(c).emplace(); diff --git a/include/iris/requirements.hpp b/include/iris/requirements.hpp index 6a43bda..c120633 100644 --- a/include/iris/requirements.hpp +++ b/include/iris/requirements.hpp @@ -28,6 +28,12 @@ concept boolean_testable = detail::boolean_testable_impl && requires(T&& t) { { !std::forward(t) } -> detail::boolean_testable_impl; }; +template +concept half_equality_comparable = requires(std::remove_reference_t const& a, std::remove_reference_t const& b) { + { a == b } -> boolean_testable; + { a != b } -> boolean_testable; +}; + // https://eel.is/c++draft/utility.arg.requirements#tab:cpp17.equalitycomparable template concept Cpp17EqualityComparable = diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp new file mode 100644 index 0000000..0316354 --- /dev/null +++ b/include/iris/run_length_sequence.hpp @@ -0,0 +1,523 @@ +#ifndef IRIS_ZZ_RUN_LENGTH_SEQUENCE_HPP +#define IRIS_ZZ_RUN_LENGTH_SEQUENCE_HPP + +// SPDX-License-Identifier: MIT + +#include // IWYU pragma: keep +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace iris::detail { +struct run_length_sequence_comp; +} // iris::detail + +namespace iris { + +// A compressed bidirectional container that holds only one instance of `T` +// per each adjacent equivalent elements. +// +// Iterator invalidation does not follow `ListT`: any insertion that starts a +// new segment may invalidate all iterators, regardless of `ListT`. Reference +// stability of the elements follows `ListT`. +// +// `run_length_sequence` can be either mutable or immutable, depending on the user's +// preference. By default, `run_length_sequence` allows access to the mutable reference +// of the underlying elements. When the user chooses to modify the element, +// `run_length_sequence` inevitably becomes "uncompressed," as the adjacent equivalence +// would no longer hold. However, `run_length_sequence` still can be used even in such +// partially-altered state, since all other invariants shall remain intact. +// +// Supports heterogeneous comparison iff `T` supports heterogeneous comparison +// for arbitrary type `U`: +// - Equivalence is checked by `operator==(T const& e, U const& value)`, where +// `e` is the reference to the existing item and `value` is the object +// that is passed to the relevant member function. +// +// - For example, when `value` is a parameter given to `run_length_sequence:: +// emplace_back(U&& value)`, the list directly compares `value` with the +// existing element without creating a temporary `T` object. Then, if +// the comparison failed, a new instance of `T` will be directly inserted +// into `ListT` via `std::forward(value)`. +// +// When inserting an element, `run_length_sequence` does lazy construction of the +// actual element object iff heterogeneous comparison is supported for the given +// type `U`; otherwise, +// - If `ListT` supports `list_.pop_back()` or `list_.erase(iterator to the +// last inserted element)`, the element is first constructed in-place and +// the resulting object is used for comparison; when the comparison holds, the +// last element is popped back or erased. Otherwise, +// +// - The element is first constructed as a local variable `temp` and is used for +// comparison; when the comparison does not hold, the object is move-inserted +// into `ListT` as if by `list_.emplace_back(std::forward(temp))`. +// +// The semantics described in the previous section applies similarly to `emplace_front`. +// +// Internal structure is maintained with two independent lists, where the first +// list holds one element instance per each segment and the other list holds the +// *offsets* that represent each insertion of elements. +// +// For example, when items are emplaced in this order: +// A A A B B C C C C +// +// then the internal state is: +// list_: +// [A] [B] [C] +// +// offsets_: +// [0] [3] [5] [9 /* sentinel */] +template< + class T, + class IndexT = unsigned, + class ListT = std::vector> +> +class run_length_sequence +{ +public: + static_assert(req::Cpp17Destructible && !std::is_const_v); + static_assert(std::equality_comparable); + + static_assert(unsigned_numeric_integral && !std::is_const_v); + + static_assert(!std::is_const_v); + static_assert(std::ranges::bidirectional_range); + static_assert(std::ranges::sized_range); + static_assert(std::same_as, T>); + static_assert(std::same_as, T&>); + static_assert(std::same_as, T const&>); + + static_assert(requires (ListT& list) { + { list.back() } -> std::same_as; + { list.front() } -> std::same_as; + }); + + using size_type = std::common_type_t; + using difference_type = std::ptrdiff_t; + + [[nodiscard]] static constexpr size_type max_size() noexcept + { + constexpr auto a = std::numeric_limits::max(); + constexpr auto b = std::numeric_limits::max(); + return std::cmp_less(b, a) ? b : a; + } + +private: + using offsets_type = std::vector>; + + template + struct iterator_impl + { + private: + using value_iterator = std::ranges::iterator_t>; + using offset_iterator = offsets_type::const_iterator; + + public: + using iterator_concept = std::bidirectional_iterator_tag; + using iterator_category = std::input_iterator_tag; // Our reference type is proxy, so this can only be input + + using value_type = indexed_value< + IndexT, + std::iter_value_t + >; + using reference = indexed_value< + IndexT, + std::iter_reference_t + >; + using rvalue_reference = indexed_value< + IndexT, + std::iter_rvalue_reference_t + >; + using difference_type = run_length_sequence::difference_type; + + constexpr iterator_impl() noexcept = default; + + constexpr explicit iterator_impl(value_iterator value_it, offset_iterator ofs_it) noexcept + : value_it_(std::move(value_it)) + , ofs_it_(std::move(ofs_it)) + {} + + constexpr explicit iterator_impl(value_iterator value_it, offset_iterator ofs_it, IndexT pos) noexcept + : value_it_(std::move(value_it)) + , ofs_it_(std::move(ofs_it)) + , rel_pos_(pos) + {} + + [[nodiscard]] constexpr operator iterator_impl() const noexcept requires (IsConst == false) + { + return iterator_impl{value_it_, ofs_it_, rel_pos_}; + } + + [[nodiscard]] constexpr reference operator*() const noexcept + { + return {index(), *value_it_}; + } + + constexpr iterator_impl& operator++() noexcept + { + auto ofs_next = std::next(ofs_it_); + if (++rel_pos_ == *ofs_next - *ofs_it_) { + ofs_it_ = std::move(ofs_next); + rel_pos_ = static_cast(0u); + ++value_it_; + } + return *this; + } + + [[nodiscard]] constexpr iterator_impl operator++(int) noexcept + { + auto temp{*this}; + ++*this; + return temp; + } + + constexpr iterator_impl& operator--() noexcept + { + if (rel_pos_ != static_cast(0u)) { + --rel_pos_; + return *this; + } + + auto const current_ofs = *ofs_it_--; + rel_pos_ = current_ofs - *ofs_it_ - static_cast(1u); + --value_it_; + return *this; + } + + [[nodiscard]] constexpr iterator_impl operator--(int) noexcept + { + auto temp{*this}; + --*this; + return temp; + } + + [[nodiscard]] constexpr bool operator==(iterator_impl const&) const noexcept = default; + + [[nodiscard]] friend constexpr difference_type operator-(iterator_impl const& a, iterator_impl const& b) noexcept + { + if (a == b) return 0; + return static_cast(a.index()) - static_cast(b.index()); + } + + [[nodiscard]] friend constexpr auto iter_move(iterator_impl const& it) + noexcept(noexcept(std::ranges::iter_move(it.value_it_))) + { + return rvalue_reference{ + it.index(), + std::ranges::iter_move(it.value_it_) + }; + } + + private: + [[nodiscard]] constexpr IndexT index() const noexcept + { + return static_cast(*ofs_it_ + rel_pos_); + } + + value_iterator value_it_{}; + offset_iterator ofs_it_{}; + IndexT rel_pos_{}; + }; + +#ifdef NDEBUG +# define IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD +#else +# define IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD [[maybe_unused]] check_invariant_guard invariant_guard_{this}; +#endif + +public: + using item_type = T; + using index_type = IndexT; + using list_type = ListT; + + using value_type = indexed_value; + using reference = indexed_value; + using const_reference = indexed_value; + + using iterator = iterator_impl; + using const_iterator = iterator_impl; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + [[nodiscard]] constexpr iterator begin() noexcept { return iterator{std::ranges::begin(list_), offsets_.begin()}; } + [[nodiscard]] constexpr const_iterator begin() const noexcept { return const_iterator{std::ranges::begin(list_), offsets_.begin()}; } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } + + [[nodiscard]] constexpr iterator end() noexcept + { + if (offsets_.empty()) { + assert(std::ranges::empty(list_)); + return iterator{std::ranges::end(list_), offsets_.end()}; + } else { + assert(offsets_.size() >= 2); + return iterator{std::ranges::end(list_), std::prev(offsets_.end())}; + } + } + [[nodiscard]] constexpr const_iterator end() const noexcept + { + if (offsets_.empty()) { + assert(std::ranges::empty(list_)); + return const_iterator{std::ranges::end(list_), offsets_.end()}; + } else { + assert(offsets_.size() >= 2); + return const_iterator{std::ranges::end(list_), std::prev(offsets_.end())}; + } + } + [[nodiscard]] constexpr const_iterator cend() const noexcept { return end(); } + + [[nodiscard]] constexpr reverse_iterator rbegin() noexcept { return reverse_iterator{end()}; } + [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{end()}; } + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + [[nodiscard]] constexpr reverse_iterator rend() noexcept { return reverse_iterator{begin()}; } + [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator{begin()}; } + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + [[nodiscard]] constexpr bool empty() const noexcept + { + assert(std::ranges::empty(list_) == offsets_.empty()); + return offsets_.empty(); + } + + [[nodiscard]] constexpr size_type size() const noexcept + { + if (offsets_.empty()) return 0uz; + assert(!std::ranges::empty(list_)); + assert(offsets_.size() >= 2); + return static_cast(offsets_.back()); + } + + [[nodiscard]] constexpr size_type segment_count() const noexcept + { + return static_cast(std::ranges::size(list_)); + } + + constexpr void clear() noexcept + requires requires(ListT& list) { list.clear(); } + { + IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD + list_.clear(); + offsets_.clear(); + } + + template + requires std::is_constructible_v + constexpr reference emplace_back(U&& value) IRIS_LIFETIMEBOUND + { + IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD + + if (offsets_.empty()) { + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; + offsets_.emplace_back(static_cast(0u)); + offsets_.emplace_back(static_cast(1u)); // sentinel + auto& elem = ranges::emplace_back_ref(list_, std::forward(value)); + ofs_insertion_guard.clear(); + return {static_cast(0u), elem}; + + } else { + assert(!std::ranges::empty(list_)); + assert(offsets_.size() >= 2); + if (offsets_.back() == max_size()) { + throwf("run_length_sequence capacity exceeded"); + } + if constexpr (req::half_equality_comparable) { + if (ranges::back(std::as_const(list_)) == std::as_const(value)) { + // Equivalent element already exists; no need to insert. + return {offsets_.back()++, ranges::back(list_)}; + } + // Need to insert new element + auto const new_pos = offsets_.back(); + offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; + auto& elem = ranges::emplace_back_ref(list_, std::forward(value)); + ofs_insertion_guard.clear(); + return {new_pos, elem}; + + } else if constexpr (requires { ranges::weak_pop_back(list_); }) { + auto& elem = ranges::emplace_back_ref(list_, std::forward(value)); + [[maybe_unused]] elem_insertion_guard elem_insertion_guard{this}; + + auto const prev_it = std::ranges::prev(std::ranges::end(list_), 2); + + if (std::as_const(*prev_it) == std::as_const(elem)) { + // Equivalent element already exists; no need to insert. + elem_insertion_guard.clear(); + ranges::weak_pop_back(list_); + return {offsets_.back()++, ranges::back(list_)}; + } + + // Need to insert new element + auto const new_pos = offsets_.back(); + offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel + elem_insertion_guard.clear(); + return {new_pos, elem}; + + } else { + T temp(std::forward(value)); + if (ranges::back(std::as_const(list_)) == std::as_const(temp)) { + // Equivalent element already exists; no need to insert. + return {offsets_.back()++, ranges::back(list_)}; + } + // Need to insert new element + auto const new_pos = offsets_.back(); + offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; + auto& elem = ranges::emplace_back_ref(list_, std::move(temp)); + ofs_insertion_guard.clear(); + return {new_pos, elem}; + } + } + } + +private: + template + struct [[nodiscard]] ofs_insertion_guard + { + constexpr explicit ofs_insertion_guard(run_length_sequence* self) noexcept + : self_(self) + {} + + constexpr void clear() noexcept + { + self_ = nullptr; + } + + constexpr ~ofs_insertion_guard() noexcept + { + if (!self_) return; + if constexpr (IsBack) { + if constexpr (WasEmpty) { + self_->offsets_.clear(); + } else { + self_->offsets_.pop_back(); + } + } else { + static_assert(false, "sorry, not implemented"); + } + } + + private: + run_length_sequence* self_; + }; + template + friend struct ofs_insertion_guard; + + template + struct [[nodiscard]] elem_insertion_guard + { + constexpr explicit elem_insertion_guard(run_length_sequence* self) noexcept + : self_(self) + {} + + constexpr void clear() noexcept + { + self_ = nullptr; + } + + constexpr ~elem_insertion_guard() noexcept + { + if (!self_) return; + if constexpr (IsBack) { + ranges::weak_pop_back(self_->list_); + } else { + static_assert(false, "sorry, not implemented"); + } + } + + private: + run_length_sequence* self_; + }; + template + friend struct elem_insertion_guard; + +#ifndef NDEBUG + struct [[nodiscard]] check_invariant_guard + { + constexpr explicit check_invariant_guard(run_length_sequence const* self) noexcept + : self_(self) + {} + + constexpr ~check_invariant_guard() noexcept + { + if (self_->offsets_.empty()) { + assert(std::ranges::empty(self_->list_)); + } else { + assert(self_->offsets_.size() == std::ranges::size(self_->list_) + 1); + assert(self_->offsets_[0] == 0); + for (std::size_t i = 0; i < self_->offsets_.size() - 1; ++i) { + assert(self_->offsets_[i] < self_->offsets_[i + 1]); + } + } + } + + private: + run_length_sequence const* self_; + }; + friend struct check_invariant_guard; +#endif + + friend struct detail::run_length_sequence_comp; + + ListT list_; + offsets_type offsets_; + +#undef IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD +}; + + +namespace detail { + +struct run_length_sequence_comp +{ + template + [[nodiscard]] static constexpr bool equals(run_length_sequence const& a, run_length_sequence const& b) noexcept + { + return a.offsets_ == b.offsets_ && a.list_ == b.list_; + } + + template + [[nodiscard]] static constexpr cmp::synth_three_way_result + compare(run_length_sequence const& a, run_length_sequence const& b) noexcept + { + if (auto const comp = a.offsets_ <=> b.offsets_; comp != 0) { + return comp; + } + return cmp::synth_three_way{}(a.list_, b.list_); + } +}; + +} // detail + +template +[[nodiscard]] constexpr bool operator==(run_length_sequence const& a, run_length_sequence const& b) noexcept +{ + return detail::run_length_sequence_comp::equals(a, b); +} + +template +[[nodiscard]] constexpr cmp::synth_three_way_result +operator<=>(run_length_sequence const& a, run_length_sequence const& b) noexcept +{ + return detail::run_length_sequence_comp::compare(a, b); +} + +} // iris + +#endif diff --git a/include/iris/type_traits.hpp b/include/iris/type_traits.hpp index 06b93cb..6f0a51f 100644 --- a/include/iris/type_traits.hpp +++ b/include/iris/type_traits.hpp @@ -18,6 +18,25 @@ namespace iris { +template +concept signed_numeric_integral = + std::signed_integral && + !std::same_as, char> && + !std::same_as, wchar_t>; + +template +concept unsigned_numeric_integral = + std::unsigned_integral && + !std::same_as, bool> && + !std::same_as, char> && + !std::same_as, wchar_t> && + !std::same_as, char8_t> && + !std::same_as, char16_t> && + !std::same_as, char32_t>; + +template +concept numeric_integral = signed_numeric_integral || unsigned_numeric_integral; + template struct remove_cv { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6d72e40..bce894f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -196,6 +196,8 @@ if(PROJECT_IS_TOP_LEVEL) alloy alloy_visit marshal + indexed_value + run_length_sequence ) foreach(test_name IN LISTS IRIS_TEST_IRIS_TESTS) iris_define_internal_test(${test_name} ${test_name}.cpp) diff --git a/test/indexed_value.cpp b/test/indexed_value.cpp new file mode 100644 index 0000000..e598e48 --- /dev/null +++ b/test/indexed_value.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT + +#include "iris_test.hpp" + +#include + +#include +#include +#include + +TEST_CASE("indexed_value") +{ + using iris::indexed_value; + + using V = indexed_value; + //using CV = indexed_value; + using LR = indexed_value; + using CLR = indexed_value; + using RR = indexed_value; + using CRR = indexed_value; + + // --------------------------------------------------- + + STATIC_CHECK(std::is_aggregate_v); + STATIC_CHECK(std::is_aggregate_v); + + STATIC_CHECK(std::is_trivially_copyable_v>); + STATIC_CHECK(std::is_trivially_copyable_v>); + STATIC_CHECK(!std::is_trivially_copyable_v>); + + // --------------------------------------------------- + + STATIC_CHECK(std::same_as, V>); + STATIC_CHECK(std::same_as, V>); + STATIC_CHECK(std::same_as, V>); + STATIC_CHECK(std::same_as, V>); + + // --------------------------------------------------- + + STATIC_CHECK(std::same_as, std::common_reference_t>); + STATIC_CHECK(std::same_as, std::common_reference_t>); + STATIC_CHECK(std::same_as, std::common_reference_t>); + + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::same_as, indexed_value>); + STATIC_CHECK(std::same_as, indexed_value>); + STATIC_CHECK(std::same_as, indexed_value>); + + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::same_as, indexed_value>); + STATIC_CHECK(std::same_as, indexed_value>); + STATIC_CHECK(std::same_as, indexed_value>); + + // --------------------------------------------------- + + STATIC_CHECK(std::same_as< + std::common_type_t, indexed_value>, + indexed_value + >); + + // --------------------------------------------------- + + STATIC_CHECK(std::convertible_to>); + STATIC_CHECK(std::convertible_to>); + STATIC_CHECK(std::convertible_to>); + STATIC_CHECK(std::convertible_to>); + + // --------------------------------------------------- + + STATIC_CHECK(std::same_as(std::declval())), int&>); + STATIC_CHECK(std::same_as(std::declval())), int const&>); + STATIC_CHECK(std::same_as(std::declval())), int&&>); + + STATIC_CHECK(std::same_as(std::declval())), std::string&>); + STATIC_CHECK(std::same_as(std::declval())), std::string const&>); + STATIC_CHECK(std::same_as(std::declval())), std::string&&>); + + STATIC_CHECK(std::same_as(std::declval())), std::string&>); + STATIC_CHECK(std::same_as(std::declval())), std::string&&>); + STATIC_CHECK(std::same_as(std::declval())), std::string const&>); + + // --------------------------------------------------- + + STATIC_CHECK(std::tuple_size_v == 2); + STATIC_CHECK(std::same_as, int>); + STATIC_CHECK(std::same_as, std::string>); + + STATIC_CHECK(std::same_as, std::string&>); + STATIC_CHECK(std::same_as, std::string const&>); + + { + std::string value{"foo"}; + LR indexed{42, value}; + auto [index, element] = indexed; + CHECK(index == 42); + CHECK(&element == &value); + } +} diff --git a/test/run_length_sequence.cpp b/test/run_length_sequence.cpp new file mode 100644 index 0000000..489283f --- /dev/null +++ b/test/run_length_sequence.cpp @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: MIT + +#include "iris_test.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +TEST_CASE("run_length_sequence") +{ + using namespace std::string_view_literals; + + using List = iris::run_length_sequence; + STATIC_CHECK(std::same_as); + STATIC_CHECK(std::same_as); + + STATIC_CHECK(std::same_as>); + STATIC_CHECK(std::is_trivially_copyable_v); + + STATIC_CHECK(std::same_as>); + STATIC_CHECK(std::same_as>); + STATIC_CHECK(std::same_as>); + + STATIC_CHECK(std::indirectly_readable); + STATIC_CHECK(std::bidirectional_iterator); + + STATIC_CHECK(std::indirectly_readable); + STATIC_CHECK(std::bidirectional_iterator); + + STATIC_CHECK(std::ranges::bidirectional_range); + STATIC_CHECK(std::ranges::bidirectional_range); + + STATIC_CHECK(std::sized_sentinel_for); + + STATIC_CHECK(std::default_initializable); + STATIC_CHECK(std::is_copy_constructible_v); + STATIC_CHECK(std::is_copy_assignable_v); + STATIC_CHECK(std::is_move_constructible_v); + STATIC_CHECK(std::is_move_assignable_v); + + { + List::value_type iv{}; + auto&& [index, value] = iv; + index = 42; + value = 1.0; + CHECK(iv.index == 42); + CHECK(iv.value == 1.0); + } + { + List::value_type iv{}; + auto [index, value] = iv; + index = 42; + value = 1.0; + CHECK(iv.index == 0); + CHECK(iv.value == 0.0); // NOLINT(readability-container-size-empty) + } + + List list; + CHECK(list.empty()); + CHECK(list.size() == 0); // NOLINT(readability-container-size-empty) + CHECK(list.begin() == list.end()); + CHECK(list == list); + CHECK((list <=> list) == std::strong_ordering::equal); + + CHECK_NOTHROW((void)(list.cbegin() = list.begin())); + + { + auto&& elem = list.emplace_back(1.0); + REQUIRE(list.size() == 1); + REQUIRE(list.segment_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == 1.0); + + auto it = list.begin(); + + REQUIRE(it != list.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it == list.end()); + } + { + auto&& elem = list.emplace_back(1.0); + REQUIRE(list.size() == 2); + REQUIRE(list.segment_count() == 1); + CHECK(elem.index == 1); + CHECK(elem.value == 1.0); + + auto it = list.begin(); + + REQUIRE(it != list.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it != list.end()); + auto&& e1 = *it++; + CHECK(e1.index == 1); + CHECK(e1.value == 1.0); + + REQUIRE(it == list.end()); + } + { + auto&& elem = list.emplace_back(1.1); + REQUIRE(list.size() == 3); + REQUIRE(list.segment_count() == 2); + CHECK(elem.index == 2); + CHECK(elem.value == 1.1); + + auto it = list.begin(); + + REQUIRE(it != list.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it != list.end()); + auto&& e1 = *it++; + CHECK(e1.index == 1); + CHECK(e1.value == 1.0); + + REQUIRE(it != list.end()); + auto&& e2 = *it++; + CHECK(e2.index == 2); + CHECK(e2.value == 1.1); + + REQUIRE(it == list.end()); + } + { + auto&& elem = list.emplace_back(1.1); + REQUIRE(list.size() == 4); + REQUIRE(list.segment_count() == 2); + CHECK(elem.index == 3); + CHECK(elem.value == 1.1); + + auto it = list.begin(); + + REQUIRE(it != list.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it != list.end()); + auto&& e1 = *it++; + CHECK(e1.index == 1); + CHECK(e1.value == 1.0); + + REQUIRE(it != list.end()); + auto&& e2 = *it++; + CHECK(e2.index == 2); + CHECK(e2.value == 1.1); + + REQUIRE(it != list.end()); + auto&& e3 = *it++; + CHECK(e3.index == 3); + CHECK(e3.value == 1.1); + + REQUIRE(it == list.end()); + } + { + auto&& elem = list.emplace_back(1.1); + REQUIRE(list.size() == 5); + REQUIRE(list.segment_count() == 2); + CHECK(elem.index == 4); + CHECK(elem.value == 1.1); + + auto it = list.begin(); + + REQUIRE(it != list.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it != list.end()); + auto&& e1 = *it++; + CHECK(e1.index == 1); + CHECK(e1.value == 1.0); + + REQUIRE(it != list.end()); + auto&& e2 = *it++; + CHECK(e2.index == 2); + CHECK(e2.value == 1.1); + + REQUIRE(it != list.end()); + auto&& e3 = *it++; + CHECK(e3.index == 3); + CHECK(e3.value == 1.1); + + REQUIRE(it != list.end()); + auto&& e4 = *it++; + CHECK(e4.index == 4); + CHECK(e4.value == 1.1); + + REQUIRE(it == list.end()); + } + { + auto&& elem = list.emplace_back(1.2); + REQUIRE(list.size() == 6); + REQUIRE(list.segment_count() == 3); + CHECK(elem.index == 5); + CHECK(elem.value == 1.2); + + auto it = list.begin(); + + REQUIRE(it != list.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it != list.end()); + auto&& e1 = *it++; + CHECK(e1.index == 1); + CHECK(e1.value == 1.0); + + REQUIRE(it != list.end()); + auto&& e2 = *it++; + CHECK(e2.index == 2); + CHECK(e2.value == 1.1); + + REQUIRE(it != list.end()); + auto&& e3 = *it++; + CHECK(e3.index == 3); + CHECK(e3.value == 1.1); + + REQUIRE(it != list.end()); + auto&& e4 = *it++; + CHECK(e4.index == 4); + CHECK(e4.value == 1.1); + + REQUIRE(it != list.end()); + auto&& e5 = *it++; + CHECK(e5.index == 5); + CHECK(e5.value == 1.2); + + REQUIRE(it == list.end()); + } +} diff --git a/test/type_traits.cpp b/test/type_traits.cpp index a489ea5..c30cc89 100644 --- a/test/type_traits.cpp +++ b/test/type_traits.cpp @@ -4,7 +4,7 @@ #include -namespace { +#include template struct tuple; @@ -12,7 +12,6 @@ struct tuple; template struct type_list; - template struct n_tuple; @@ -41,8 +40,24 @@ struct member_ptr_test { }; -} // anonymous - +TEST_CASE("type traits") +{ + STATIC_CHECK(iris::signed_numeric_integral); + STATIC_CHECK(iris::signed_numeric_integral); + STATIC_CHECK(iris::signed_numeric_integral); + STATIC_CHECK(iris::signed_numeric_integral); + STATIC_CHECK(!iris::signed_numeric_integral); + STATIC_CHECK(!iris::signed_numeric_integral); + STATIC_CHECK(!iris::signed_numeric_integral); + + STATIC_CHECK(iris::unsigned_numeric_integral); + STATIC_CHECK(iris::unsigned_numeric_integral); + STATIC_CHECK(iris::unsigned_numeric_integral); + STATIC_CHECK(iris::unsigned_numeric_integral); + STATIC_CHECK(!iris::unsigned_numeric_integral); + STATIC_CHECK(!iris::unsigned_numeric_integral); + STATIC_CHECK(!iris::unsigned_numeric_integral); +} TEST_CASE("is_convertible_without_narrowing: same type identity") { From c5c12c6a0d924b1102fe0a324a01f6c45316ba58 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 02:45:39 +0900 Subject: [PATCH 02/14] Fix typo --- include/iris/ranges.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/iris/ranges.hpp b/include/iris/ranges.hpp index a8509a6..4394ff8 100644 --- a/include/iris/ranges.hpp +++ b/include/iris/ranges.hpp @@ -262,8 +262,8 @@ struct back_fn } // detail -[[nodiscard]] inline constexpr detail::front_fn front{}; -[[nodiscard]] inline constexpr detail::back_fn back{}; +[[maybe_unused]] inline constexpr detail::front_fn front{}; +[[maybe_unused]] inline constexpr detail::back_fn back{}; namespace detail { @@ -299,8 +299,8 @@ struct emplace_back_ref_fn } // detail -[[nodiscard]] inline constexpr detail::emplace_front_ref_fn emplace_front_ref{}; -[[nodiscard]] inline constexpr detail::emplace_back_ref_fn emplace_back_ref{}; +[[maybe_unused]] inline constexpr detail::emplace_front_ref_fn emplace_front_ref{}; +[[maybe_unused]] inline constexpr detail::emplace_back_ref_fn emplace_back_ref{}; namespace detail { @@ -355,8 +355,8 @@ struct weak_pop_back_fn } // detail -[[nodiscard]] inline constexpr detail::weak_pop_front_fn weak_pop_front{}; -[[nodiscard]] inline constexpr detail::weak_pop_back_fn weak_pop_back{}; +[[maybe_unused]] inline constexpr detail::weak_pop_front_fn weak_pop_front{}; +[[maybe_unused]] inline constexpr detail::weak_pop_back_fn weak_pop_back{}; template From afd2517d16ad922475bc076c366fb486f09e5583 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 03:08:50 +0900 Subject: [PATCH 03/14] Move assertions to function body --- include/iris/run_length_sequence.hpp | 57 ++++++++++++++++------------ 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index 0316354..7ff9be0 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -93,22 +93,9 @@ template< class run_length_sequence { public: - static_assert(req::Cpp17Destructible && !std::is_const_v); - static_assert(std::equality_comparable); - + static_assert(!std::is_const_v); static_assert(unsigned_numeric_integral && !std::is_const_v); - static_assert(!std::is_const_v); - static_assert(std::ranges::bidirectional_range); - static_assert(std::ranges::sized_range); - static_assert(std::same_as, T>); - static_assert(std::same_as, T&>); - static_assert(std::same_as, T const&>); - - static_assert(requires (ListT& list) { - { list.back() } -> std::same_as; - { list.front() } -> std::same_as; - }); using size_type = std::common_type_t; using difference_type = std::ptrdiff_t; @@ -257,12 +244,13 @@ class run_length_sequence using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - [[nodiscard]] constexpr iterator begin() noexcept { return iterator{std::ranges::begin(list_), offsets_.begin()}; } - [[nodiscard]] constexpr const_iterator begin() const noexcept { return const_iterator{std::ranges::begin(list_), offsets_.begin()}; } - [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } + [[nodiscard]] constexpr iterator begin() noexcept { check_range_concepts(); return iterator{std::ranges::begin(list_), offsets_.begin()}; } + [[nodiscard]] constexpr const_iterator begin() const noexcept { check_range_concepts(); return const_iterator{std::ranges::begin(list_), offsets_.begin()}; } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { check_range_concepts(); return begin(); } [[nodiscard]] constexpr iterator end() noexcept { + check_range_concepts(); if (offsets_.empty()) { assert(std::ranges::empty(list_)); return iterator{std::ranges::end(list_), offsets_.end()}; @@ -273,6 +261,7 @@ class run_length_sequence } [[nodiscard]] constexpr const_iterator end() const noexcept { + check_range_concepts(); if (offsets_.empty()) { assert(std::ranges::empty(list_)); return const_iterator{std::ranges::end(list_), offsets_.end()}; @@ -281,23 +270,25 @@ class run_length_sequence return const_iterator{std::ranges::end(list_), std::prev(offsets_.end())}; } } - [[nodiscard]] constexpr const_iterator cend() const noexcept { return end(); } + [[nodiscard]] constexpr const_iterator cend() const noexcept { check_range_concepts(); return end(); } - [[nodiscard]] constexpr reverse_iterator rbegin() noexcept { return reverse_iterator{end()}; } - [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{end()}; } - [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } - [[nodiscard]] constexpr reverse_iterator rend() noexcept { return reverse_iterator{begin()}; } - [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator{begin()}; } - [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return rend(); } + [[nodiscard]] constexpr reverse_iterator rbegin() noexcept { check_range_concepts(); return reverse_iterator{end()}; } + [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { check_range_concepts(); return const_reverse_iterator{end()}; } + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { check_range_concepts(); return rbegin(); } + [[nodiscard]] constexpr reverse_iterator rend() noexcept { check_range_concepts(); return reverse_iterator{begin()}; } + [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { check_range_concepts(); return const_reverse_iterator{begin()}; } + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { check_range_concepts(); return rend(); } [[nodiscard]] constexpr bool empty() const noexcept { + static_assert(std::ranges::sized_range); assert(std::ranges::empty(list_) == offsets_.empty()); return offsets_.empty(); } [[nodiscard]] constexpr size_type size() const noexcept { + static_assert(std::ranges::sized_range); if (offsets_.empty()) return 0uz; assert(!std::ranges::empty(list_)); assert(offsets_.size() >= 2); @@ -306,12 +297,14 @@ class run_length_sequence [[nodiscard]] constexpr size_type segment_count() const noexcept { + static_assert(std::ranges::sized_range); return static_cast(std::ranges::size(list_)); } constexpr void clear() noexcept requires requires(ListT& list) { list.clear(); } { + static_assert(req::Cpp17Destructible); IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD list_.clear(); offsets_.clear(); @@ -321,6 +314,9 @@ class run_length_sequence requires std::is_constructible_v constexpr reference emplace_back(U&& value) IRIS_LIFETIMEBOUND { + check_range_concepts(); + static_assert(std::equality_comparable); + IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD if (offsets_.empty()) { @@ -387,6 +383,19 @@ class run_length_sequence } private: + constexpr void check_range_concepts() const noexcept + { + static_assert(std::ranges::bidirectional_range); + static_assert(std::same_as, T>); + static_assert(std::same_as, T&>); + static_assert(std::same_as, T const&>); + + static_assert(requires (ListT& list) { + { list.back() } -> std::same_as; + { list.front() } -> std::same_as; + }); + } + template struct [[nodiscard]] ofs_insertion_guard { From 5675c7bdb9ca1d7ce3639bdf3a5808ccd03371c1 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 03:13:39 +0900 Subject: [PATCH 04/14] Rename variable --- include/iris/run_length_sequence.hpp | 122 ++++++++++---------- test/run_length_sequence.cpp | 160 +++++++++++++-------------- 2 files changed, 141 insertions(+), 141 deletions(-) diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index 7ff9be0..3d55a68 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -35,9 +35,9 @@ namespace iris { // A compressed bidirectional container that holds only one instance of `T` // per each adjacent equivalent elements. // -// Iterator invalidation does not follow `ListT`: any insertion that starts a -// new segment may invalidate all iterators, regardless of `ListT`. Reference -// stability of the elements follows `ListT`. +// Iterator invalidation does not follow `RunContainerT`: any insertion that starts a +// new segment may invalidate all iterators, regardless of `RunContainerT`. Reference +// stability of the elements follows `RunContainerT`. // // `run_length_sequence` can be either mutable or immutable, depending on the user's // preference. By default, `run_length_sequence` allows access to the mutable reference @@ -56,19 +56,19 @@ namespace iris { // emplace_back(U&& value)`, the list directly compares `value` with the // existing element without creating a temporary `T` object. Then, if // the comparison failed, a new instance of `T` will be directly inserted -// into `ListT` via `std::forward(value)`. +// into `RunContainerT` via `std::forward(value)`. // // When inserting an element, `run_length_sequence` does lazy construction of the // actual element object iff heterogeneous comparison is supported for the given // type `U`; otherwise, -// - If `ListT` supports `list_.pop_back()` or `list_.erase(iterator to the +// - If `RunContainerT` supports `runs_.pop_back()` or `runs_.erase(iterator to the // last inserted element)`, the element is first constructed in-place and // the resulting object is used for comparison; when the comparison holds, the // last element is popped back or erased. Otherwise, // // - The element is first constructed as a local variable `temp` and is used for // comparison; when the comparison does not hold, the object is move-inserted -// into `ListT` as if by `list_.emplace_back(std::forward(temp))`. +// into `RunContainerT` as if by `runs_.emplace_back(std::forward(temp))`. // // The semantics described in the previous section applies similarly to `emplace_front`. // @@ -80,7 +80,7 @@ namespace iris { // A A A B B C C C C // // then the internal state is: -// list_: +// runs_: // [A] [B] [C] // // offsets_: @@ -88,14 +88,14 @@ namespace iris { template< class T, class IndexT = unsigned, - class ListT = std::vector> + class RunContainerT = std::vector> > class run_length_sequence { public: static_assert(!std::is_const_v); static_assert(unsigned_numeric_integral && !std::is_const_v); - static_assert(!std::is_const_v); + static_assert(!std::is_const_v); using size_type = std::common_type_t; using difference_type = std::ptrdiff_t; @@ -114,7 +114,7 @@ class run_length_sequence struct iterator_impl { private: - using value_iterator = std::ranges::iterator_t>; + using value_iterator = std::ranges::iterator_t>; using offset_iterator = offsets_type::const_iterator; public: @@ -233,7 +233,7 @@ class run_length_sequence public: using item_type = T; using index_type = IndexT; - using list_type = ListT; + using run_container_type = RunContainerT; using value_type = indexed_value; using reference = indexed_value; @@ -244,30 +244,30 @@ class run_length_sequence using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - [[nodiscard]] constexpr iterator begin() noexcept { check_range_concepts(); return iterator{std::ranges::begin(list_), offsets_.begin()}; } - [[nodiscard]] constexpr const_iterator begin() const noexcept { check_range_concepts(); return const_iterator{std::ranges::begin(list_), offsets_.begin()}; } + [[nodiscard]] constexpr iterator begin() noexcept { check_range_concepts(); return iterator{std::ranges::begin(runs_), offsets_.begin()}; } + [[nodiscard]] constexpr const_iterator begin() const noexcept { check_range_concepts(); return const_iterator{std::ranges::begin(runs_), offsets_.begin()}; } [[nodiscard]] constexpr const_iterator cbegin() const noexcept { check_range_concepts(); return begin(); } [[nodiscard]] constexpr iterator end() noexcept { check_range_concepts(); if (offsets_.empty()) { - assert(std::ranges::empty(list_)); - return iterator{std::ranges::end(list_), offsets_.end()}; + assert(std::ranges::empty(runs_)); + return iterator{std::ranges::end(runs_), offsets_.end()}; } else { assert(offsets_.size() >= 2); - return iterator{std::ranges::end(list_), std::prev(offsets_.end())}; + return iterator{std::ranges::end(runs_), std::prev(offsets_.end())}; } } [[nodiscard]] constexpr const_iterator end() const noexcept { check_range_concepts(); if (offsets_.empty()) { - assert(std::ranges::empty(list_)); - return const_iterator{std::ranges::end(list_), offsets_.end()}; + assert(std::ranges::empty(runs_)); + return const_iterator{std::ranges::end(runs_), offsets_.end()}; } else { assert(offsets_.size() >= 2); - return const_iterator{std::ranges::end(list_), std::prev(offsets_.end())}; + return const_iterator{std::ranges::end(runs_), std::prev(offsets_.end())}; } } [[nodiscard]] constexpr const_iterator cend() const noexcept { check_range_concepts(); return end(); } @@ -281,32 +281,32 @@ class run_length_sequence [[nodiscard]] constexpr bool empty() const noexcept { - static_assert(std::ranges::sized_range); - assert(std::ranges::empty(list_) == offsets_.empty()); + static_assert(std::ranges::sized_range); + assert(std::ranges::empty(runs_) == offsets_.empty()); return offsets_.empty(); } [[nodiscard]] constexpr size_type size() const noexcept { - static_assert(std::ranges::sized_range); + static_assert(std::ranges::sized_range); if (offsets_.empty()) return 0uz; - assert(!std::ranges::empty(list_)); + assert(!std::ranges::empty(runs_)); assert(offsets_.size() >= 2); return static_cast(offsets_.back()); } [[nodiscard]] constexpr size_type segment_count() const noexcept { - static_assert(std::ranges::sized_range); - return static_cast(std::ranges::size(list_)); + static_assert(std::ranges::sized_range); + return static_cast(std::ranges::size(runs_)); } constexpr void clear() noexcept - requires requires(ListT& list) { list.clear(); } + requires requires(RunContainerT& list) { list.clear(); } { static_assert(req::Cpp17Destructible); IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD - list_.clear(); + runs_.clear(); offsets_.clear(); } @@ -323,40 +323,40 @@ class run_length_sequence [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; offsets_.emplace_back(static_cast(0u)); offsets_.emplace_back(static_cast(1u)); // sentinel - auto& elem = ranges::emplace_back_ref(list_, std::forward(value)); + auto& elem = ranges::emplace_back_ref(runs_, std::forward(value)); ofs_insertion_guard.clear(); return {static_cast(0u), elem}; } else { - assert(!std::ranges::empty(list_)); + assert(!std::ranges::empty(runs_)); assert(offsets_.size() >= 2); if (offsets_.back() == max_size()) { throwf("run_length_sequence capacity exceeded"); } if constexpr (req::half_equality_comparable) { - if (ranges::back(std::as_const(list_)) == std::as_const(value)) { + if (ranges::back(std::as_const(runs_)) == std::as_const(value)) { // Equivalent element already exists; no need to insert. - return {offsets_.back()++, ranges::back(list_)}; + return {offsets_.back()++, ranges::back(runs_)}; } // Need to insert new element auto const new_pos = offsets_.back(); offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; - auto& elem = ranges::emplace_back_ref(list_, std::forward(value)); + auto& elem = ranges::emplace_back_ref(runs_, std::forward(value)); ofs_insertion_guard.clear(); return {new_pos, elem}; - } else if constexpr (requires { ranges::weak_pop_back(list_); }) { - auto& elem = ranges::emplace_back_ref(list_, std::forward(value)); + } else if constexpr (requires { ranges::weak_pop_back(runs_); }) { + auto& elem = ranges::emplace_back_ref(runs_, std::forward(value)); [[maybe_unused]] elem_insertion_guard elem_insertion_guard{this}; - auto const prev_it = std::ranges::prev(std::ranges::end(list_), 2); + auto const prev_it = std::ranges::prev(std::ranges::end(runs_), 2); if (std::as_const(*prev_it) == std::as_const(elem)) { // Equivalent element already exists; no need to insert. elem_insertion_guard.clear(); - ranges::weak_pop_back(list_); - return {offsets_.back()++, ranges::back(list_)}; + ranges::weak_pop_back(runs_); + return {offsets_.back()++, ranges::back(runs_)}; } // Need to insert new element @@ -367,15 +367,15 @@ class run_length_sequence } else { T temp(std::forward(value)); - if (ranges::back(std::as_const(list_)) == std::as_const(temp)) { + if (ranges::back(std::as_const(runs_)) == std::as_const(temp)) { // Equivalent element already exists; no need to insert. - return {offsets_.back()++, ranges::back(list_)}; + return {offsets_.back()++, ranges::back(runs_)}; } // Need to insert new element auto const new_pos = offsets_.back(); offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; - auto& elem = ranges::emplace_back_ref(list_, std::move(temp)); + auto& elem = ranges::emplace_back_ref(runs_, std::move(temp)); ofs_insertion_guard.clear(); return {new_pos, elem}; } @@ -385,12 +385,12 @@ class run_length_sequence private: constexpr void check_range_concepts() const noexcept { - static_assert(std::ranges::bidirectional_range); - static_assert(std::same_as, T>); - static_assert(std::same_as, T&>); - static_assert(std::same_as, T const&>); + static_assert(std::ranges::bidirectional_range); + static_assert(std::same_as, T>); + static_assert(std::same_as, T&>); + static_assert(std::same_as, T const&>); - static_assert(requires (ListT& list) { + static_assert(requires (RunContainerT& list) { { list.back() } -> std::same_as; { list.front() } -> std::same_as; }); @@ -444,7 +444,7 @@ class run_length_sequence { if (!self_) return; if constexpr (IsBack) { - ranges::weak_pop_back(self_->list_); + ranges::weak_pop_back(self_->runs_); } else { static_assert(false, "sorry, not implemented"); } @@ -466,9 +466,9 @@ class run_length_sequence constexpr ~check_invariant_guard() noexcept { if (self_->offsets_.empty()) { - assert(std::ranges::empty(self_->list_)); + assert(std::ranges::empty(self_->runs_)); } else { - assert(self_->offsets_.size() == std::ranges::size(self_->list_) + 1); + assert(self_->offsets_.size() == std::ranges::size(self_->runs_) + 1); assert(self_->offsets_[0] == 0); for (std::size_t i = 0; i < self_->offsets_.size() - 1; ++i) { assert(self_->offsets_[i] < self_->offsets_[i + 1]); @@ -484,7 +484,7 @@ class run_length_sequence friend struct detail::run_length_sequence_comp; - ListT list_; + RunContainerT runs_; offsets_type offsets_; #undef IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD @@ -495,34 +495,34 @@ namespace detail { struct run_length_sequence_comp { - template - [[nodiscard]] static constexpr bool equals(run_length_sequence const& a, run_length_sequence const& b) noexcept + template + [[nodiscard]] static constexpr bool equals(run_length_sequence const& a, run_length_sequence const& b) noexcept { - return a.offsets_ == b.offsets_ && a.list_ == b.list_; + return a.offsets_ == b.offsets_ && a.runs_ == b.runs_; } - template - [[nodiscard]] static constexpr cmp::synth_three_way_result - compare(run_length_sequence const& a, run_length_sequence const& b) noexcept + template + [[nodiscard]] static constexpr cmp::synth_three_way_result + compare(run_length_sequence const& a, run_length_sequence const& b) noexcept { if (auto const comp = a.offsets_ <=> b.offsets_; comp != 0) { return comp; } - return cmp::synth_three_way{}(a.list_, b.list_); + return cmp::synth_three_way{}(a.runs_, b.runs_); } }; } // detail -template -[[nodiscard]] constexpr bool operator==(run_length_sequence const& a, run_length_sequence const& b) noexcept +template +[[nodiscard]] constexpr bool operator==(run_length_sequence const& a, run_length_sequence const& b) noexcept { return detail::run_length_sequence_comp::equals(a, b); } -template -[[nodiscard]] constexpr cmp::synth_three_way_result -operator<=>(run_length_sequence const& a, run_length_sequence const& b) noexcept +template +[[nodiscard]] constexpr cmp::synth_three_way_result +operator<=>(run_length_sequence const& a, run_length_sequence const& b) noexcept { return detail::run_length_sequence_comp::compare(a, b); } diff --git a/test/run_length_sequence.cpp b/test/run_length_sequence.cpp index 489283f..981802f 100644 --- a/test/run_length_sequence.cpp +++ b/test/run_length_sequence.cpp @@ -15,36 +15,36 @@ TEST_CASE("run_length_sequence") { using namespace std::string_view_literals; - using List = iris::run_length_sequence; - STATIC_CHECK(std::same_as); - STATIC_CHECK(std::same_as); + using RLS = iris::run_length_sequence; + STATIC_CHECK(std::same_as); + STATIC_CHECK(std::same_as); - STATIC_CHECK(std::same_as>); - STATIC_CHECK(std::is_trivially_copyable_v); + STATIC_CHECK(std::same_as>); + STATIC_CHECK(std::is_trivially_copyable_v); - STATIC_CHECK(std::same_as>); - STATIC_CHECK(std::same_as>); - STATIC_CHECK(std::same_as>); + STATIC_CHECK(std::same_as>); + STATIC_CHECK(std::same_as>); + STATIC_CHECK(std::same_as>); - STATIC_CHECK(std::indirectly_readable); - STATIC_CHECK(std::bidirectional_iterator); + STATIC_CHECK(std::indirectly_readable); + STATIC_CHECK(std::bidirectional_iterator); - STATIC_CHECK(std::indirectly_readable); - STATIC_CHECK(std::bidirectional_iterator); + STATIC_CHECK(std::indirectly_readable); + STATIC_CHECK(std::bidirectional_iterator); - STATIC_CHECK(std::ranges::bidirectional_range); - STATIC_CHECK(std::ranges::bidirectional_range); + STATIC_CHECK(std::ranges::bidirectional_range); + STATIC_CHECK(std::ranges::bidirectional_range); - STATIC_CHECK(std::sized_sentinel_for); + STATIC_CHECK(std::sized_sentinel_for); - STATIC_CHECK(std::default_initializable); - STATIC_CHECK(std::is_copy_constructible_v); - STATIC_CHECK(std::is_copy_assignable_v); - STATIC_CHECK(std::is_move_constructible_v); - STATIC_CHECK(std::is_move_assignable_v); + STATIC_CHECK(std::default_initializable); + STATIC_CHECK(std::is_copy_constructible_v); + STATIC_CHECK(std::is_copy_assignable_v); + STATIC_CHECK(std::is_move_constructible_v); + STATIC_CHECK(std::is_move_assignable_v); { - List::value_type iv{}; + RLS::value_type iv{}; auto&& [index, value] = iv; index = 42; value = 1.0; @@ -52,7 +52,7 @@ TEST_CASE("run_length_sequence") CHECK(iv.value == 1.0); } { - List::value_type iv{}; + RLS::value_type iv{}; auto [index, value] = iv; index = 42; value = 1.0; @@ -60,184 +60,184 @@ TEST_CASE("run_length_sequence") CHECK(iv.value == 0.0); // NOLINT(readability-container-size-empty) } - List list; - CHECK(list.empty()); - CHECK(list.size() == 0); // NOLINT(readability-container-size-empty) - CHECK(list.begin() == list.end()); - CHECK(list == list); - CHECK((list <=> list) == std::strong_ordering::equal); + RLS seq; + CHECK(seq.empty()); + CHECK(seq.size() == 0); // NOLINT(readability-container-size-empty) + CHECK(seq.begin() == seq.end()); + CHECK(seq == seq); + CHECK((seq <=> seq) == std::strong_ordering::equal); - CHECK_NOTHROW((void)(list.cbegin() = list.begin())); + CHECK_NOTHROW((void)(seq.cbegin() = seq.begin())); { - auto&& elem = list.emplace_back(1.0); - REQUIRE(list.size() == 1); - REQUIRE(list.segment_count() == 1); + auto&& elem = seq.emplace_back(1.0); + REQUIRE(seq.size() == 1); + REQUIRE(seq.segment_count() == 1); CHECK(elem.index == 0); CHECK(elem.value == 1.0); - auto it = list.begin(); + auto it = seq.begin(); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e0 = *it++; CHECK(e0.index == 0); CHECK(e0.value == 1.0); - REQUIRE(it == list.end()); + REQUIRE(it == seq.end()); } { - auto&& elem = list.emplace_back(1.0); - REQUIRE(list.size() == 2); - REQUIRE(list.segment_count() == 1); + auto&& elem = seq.emplace_back(1.0); + REQUIRE(seq.size() == 2); + REQUIRE(seq.segment_count() == 1); CHECK(elem.index == 1); CHECK(elem.value == 1.0); - auto it = list.begin(); + auto it = seq.begin(); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e0 = *it++; CHECK(e0.index == 0); CHECK(e0.value == 1.0); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e1 = *it++; CHECK(e1.index == 1); CHECK(e1.value == 1.0); - REQUIRE(it == list.end()); + REQUIRE(it == seq.end()); } { - auto&& elem = list.emplace_back(1.1); - REQUIRE(list.size() == 3); - REQUIRE(list.segment_count() == 2); + auto&& elem = seq.emplace_back(1.1); + REQUIRE(seq.size() == 3); + REQUIRE(seq.segment_count() == 2); CHECK(elem.index == 2); CHECK(elem.value == 1.1); - auto it = list.begin(); + auto it = seq.begin(); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e0 = *it++; CHECK(e0.index == 0); CHECK(e0.value == 1.0); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e1 = *it++; CHECK(e1.index == 1); CHECK(e1.value == 1.0); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e2 = *it++; CHECK(e2.index == 2); CHECK(e2.value == 1.1); - REQUIRE(it == list.end()); + REQUIRE(it == seq.end()); } { - auto&& elem = list.emplace_back(1.1); - REQUIRE(list.size() == 4); - REQUIRE(list.segment_count() == 2); + auto&& elem = seq.emplace_back(1.1); + REQUIRE(seq.size() == 4); + REQUIRE(seq.segment_count() == 2); CHECK(elem.index == 3); CHECK(elem.value == 1.1); - auto it = list.begin(); + auto it = seq.begin(); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e0 = *it++; CHECK(e0.index == 0); CHECK(e0.value == 1.0); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e1 = *it++; CHECK(e1.index == 1); CHECK(e1.value == 1.0); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e2 = *it++; CHECK(e2.index == 2); CHECK(e2.value == 1.1); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e3 = *it++; CHECK(e3.index == 3); CHECK(e3.value == 1.1); - REQUIRE(it == list.end()); + REQUIRE(it == seq.end()); } { - auto&& elem = list.emplace_back(1.1); - REQUIRE(list.size() == 5); - REQUIRE(list.segment_count() == 2); + auto&& elem = seq.emplace_back(1.1); + REQUIRE(seq.size() == 5); + REQUIRE(seq.segment_count() == 2); CHECK(elem.index == 4); CHECK(elem.value == 1.1); - auto it = list.begin(); + auto it = seq.begin(); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e0 = *it++; CHECK(e0.index == 0); CHECK(e0.value == 1.0); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e1 = *it++; CHECK(e1.index == 1); CHECK(e1.value == 1.0); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e2 = *it++; CHECK(e2.index == 2); CHECK(e2.value == 1.1); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e3 = *it++; CHECK(e3.index == 3); CHECK(e3.value == 1.1); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e4 = *it++; CHECK(e4.index == 4); CHECK(e4.value == 1.1); - REQUIRE(it == list.end()); + REQUIRE(it == seq.end()); } { - auto&& elem = list.emplace_back(1.2); - REQUIRE(list.size() == 6); - REQUIRE(list.segment_count() == 3); + auto&& elem = seq.emplace_back(1.2); + REQUIRE(seq.size() == 6); + REQUIRE(seq.segment_count() == 3); CHECK(elem.index == 5); CHECK(elem.value == 1.2); - auto it = list.begin(); + auto it = seq.begin(); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e0 = *it++; CHECK(e0.index == 0); CHECK(e0.value == 1.0); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e1 = *it++; CHECK(e1.index == 1); CHECK(e1.value == 1.0); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e2 = *it++; CHECK(e2.index == 2); CHECK(e2.value == 1.1); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e3 = *it++; CHECK(e3.index == 3); CHECK(e3.value == 1.1); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e4 = *it++; CHECK(e4.index == 4); CHECK(e4.value == 1.1); - REQUIRE(it != list.end()); + REQUIRE(it != seq.end()); auto&& e5 = *it++; CHECK(e5.index == 5); CHECK(e5.value == 1.2); - REQUIRE(it == list.end()); + REQUIRE(it == seq.end()); } } From e791fe80a0245babbda6491125756c3d90baa8ce Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 03:27:01 +0900 Subject: [PATCH 05/14] Reimplement `operator==` --- include/iris/run_length_sequence.hpp | 55 ++++++++++++++++++---------- test/run_length_sequence.cpp | 1 - 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index 3d55a68..bb3fbde 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -10,7 +10,6 @@ #include #include -#include #include #include @@ -496,37 +495,55 @@ namespace detail { struct run_length_sequence_comp { template - [[nodiscard]] static constexpr bool equals(run_length_sequence const& a, run_length_sequence const& b) noexcept + [[nodiscard]] static constexpr bool equals(run_length_sequence const& a, run_length_sequence const& b) + noexcept(noexcept(std::declval() == std::declval())) { - return a.offsets_ == b.offsets_ && a.runs_ == b.runs_; - } + static_assert(std::equality_comparable); - template - [[nodiscard]] static constexpr cmp::synth_three_way_result - compare(run_length_sequence const& a, run_length_sequence const& b) noexcept - { - if (auto const comp = a.offsets_ <=> b.offsets_; comp != 0) { - return comp; + if (a.size() != b.size()) return false; + if (a.empty()) return true; + + auto ra = std::ranges::begin(a.runs_); + auto rb = std::ranges::begin(b.runs_); + auto oa = a.offsets_.begin(); // current run's start; *(oa + 1) is its end + auto ob = b.offsets_.begin(); + auto const ra_end = std::ranges::end(a.runs_); + + while (true) { + if (!(*ra == *rb)) return false; + + auto const next_a = *(oa + 1); + auto const next_b = *(ob + 1); + + if (next_a == next_b) { + ++ra; ++oa; + ++rb; ++ob; + + } else if (next_a < next_b) { + ++ra; ++oa; + + } else { + ++rb; ++ob; + } + + if (ra == ra_end) { + // Logical sizes are equal, so b must be exhausted here too. + assert(rb == std::ranges::end(b.runs_)); + return true; + } } - return cmp::synth_three_way{}(a.runs_, b.runs_); } }; } // detail template -[[nodiscard]] constexpr bool operator==(run_length_sequence const& a, run_length_sequence const& b) noexcept +[[nodiscard]] constexpr bool operator==(run_length_sequence const& a, run_length_sequence const& b) + noexcept(noexcept(detail::run_length_sequence_comp::equals(a, b))) { return detail::run_length_sequence_comp::equals(a, b); } -template -[[nodiscard]] constexpr cmp::synth_three_way_result -operator<=>(run_length_sequence const& a, run_length_sequence const& b) noexcept -{ - return detail::run_length_sequence_comp::compare(a, b); -} - } // iris #endif diff --git a/test/run_length_sequence.cpp b/test/run_length_sequence.cpp index 981802f..6bbdfbb 100644 --- a/test/run_length_sequence.cpp +++ b/test/run_length_sequence.cpp @@ -65,7 +65,6 @@ TEST_CASE("run_length_sequence") CHECK(seq.size() == 0); // NOLINT(readability-container-size-empty) CHECK(seq.begin() == seq.end()); CHECK(seq == seq); - CHECK((seq <=> seq) == std::strong_ordering::equal); CHECK_NOTHROW((void)(seq.cbegin() = seq.begin())); From 7e58da30d26fe331e4a15be229dacad05dde963c Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 03:58:09 +0900 Subject: [PATCH 06/14] Refactoring --- include/iris/run_length_sequence.hpp | 110 +++++++++++++++++---------- test/run_length_sequence.cpp | 12 +-- 2 files changed, 75 insertions(+), 47 deletions(-) diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index bb3fbde..aa4b115 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -94,6 +94,7 @@ class run_length_sequence public: static_assert(!std::is_const_v); static_assert(unsigned_numeric_integral && !std::is_const_v); + static_assert(sizeof(IndexT) >= sizeof(int), "Small index type has no practical benefit"); static_assert(!std::is_const_v); using size_type = std::common_type_t; @@ -103,7 +104,7 @@ class run_length_sequence { constexpr auto a = std::numeric_limits::max(); constexpr auto b = std::numeric_limits::max(); - return std::cmp_less(b, a) ? b : a; + return std::cmp_less(b, a) ? static_cast(b) : static_cast(a); } private: @@ -245,7 +246,7 @@ class run_length_sequence [[nodiscard]] constexpr iterator begin() noexcept { check_range_concepts(); return iterator{std::ranges::begin(runs_), offsets_.begin()}; } [[nodiscard]] constexpr const_iterator begin() const noexcept { check_range_concepts(); return const_iterator{std::ranges::begin(runs_), offsets_.begin()}; } - [[nodiscard]] constexpr const_iterator cbegin() const noexcept { check_range_concepts(); return begin(); } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } [[nodiscard]] constexpr iterator end() noexcept { @@ -269,14 +270,14 @@ class run_length_sequence return const_iterator{std::ranges::end(runs_), std::prev(offsets_.end())}; } } - [[nodiscard]] constexpr const_iterator cend() const noexcept { check_range_concepts(); return end(); } + [[nodiscard]] constexpr const_iterator cend() const noexcept { return end(); } - [[nodiscard]] constexpr reverse_iterator rbegin() noexcept { check_range_concepts(); return reverse_iterator{end()}; } - [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { check_range_concepts(); return const_reverse_iterator{end()}; } - [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { check_range_concepts(); return rbegin(); } - [[nodiscard]] constexpr reverse_iterator rend() noexcept { check_range_concepts(); return reverse_iterator{begin()}; } - [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { check_range_concepts(); return const_reverse_iterator{begin()}; } - [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { check_range_concepts(); return rend(); } + [[nodiscard]] constexpr reverse_iterator rbegin() noexcept { return reverse_iterator{end()}; } + [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{end()}; } + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + [[nodiscard]] constexpr reverse_iterator rend() noexcept { return reverse_iterator{begin()}; } + [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator{begin()}; } + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return rend(); } [[nodiscard]] constexpr bool empty() const noexcept { @@ -294,7 +295,7 @@ class run_length_sequence return static_cast(offsets_.back()); } - [[nodiscard]] constexpr size_type segment_count() const noexcept + [[nodiscard]] constexpr size_type run_count() const noexcept { static_assert(std::ranges::sized_range); return static_cast(std::ranges::size(runs_)); @@ -310,21 +311,47 @@ class run_length_sequence } template - requires std::is_constructible_v + requires std::constructible_from && req::half_equality_comparable constexpr reference emplace_back(U&& value) IRIS_LIFETIMEBOUND { check_range_concepts(); static_assert(std::equality_comparable); - IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD if (offsets_.empty()) { - [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; - offsets_.emplace_back(static_cast(0u)); - offsets_.emplace_back(static_cast(1u)); // sentinel + return this->emplace_back_on_empty(std::forward(value)); + + } else { + assert(!std::ranges::empty(runs_)); + assert(offsets_.size() >= 2); + if (offsets_.back() == max_size()) { + throwf("run_length_sequence capacity exceeded"); + } + + if (ranges::back(std::as_const(runs_)) == std::as_const(value)) { + // Equivalent element already exists; no need to insert. + return {offsets_.back()++, ranges::back(runs_)}; + } + // Need to insert new element + auto const new_pos = offsets_.back(); + offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; auto& elem = ranges::emplace_back_ref(runs_, std::forward(value)); ofs_insertion_guard.clear(); - return {static_cast(0u), elem}; + return {new_pos, elem}; + } + } + + template + requires std::constructible_from + constexpr reference emplace_back(Args&&... args) IRIS_LIFETIMEBOUND + { + check_range_concepts(); + static_assert(std::equality_comparable); + IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD + + if (offsets_.empty()) { + return this->emplace_back_on_empty(std::forward(args)...); } else { assert(!std::ranges::empty(runs_)); @@ -332,21 +359,8 @@ class run_length_sequence if (offsets_.back() == max_size()) { throwf("run_length_sequence capacity exceeded"); } - if constexpr (req::half_equality_comparable) { - if (ranges::back(std::as_const(runs_)) == std::as_const(value)) { - // Equivalent element already exists; no need to insert. - return {offsets_.back()++, ranges::back(runs_)}; - } - // Need to insert new element - auto const new_pos = offsets_.back(); - offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel - [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; - auto& elem = ranges::emplace_back_ref(runs_, std::forward(value)); - ofs_insertion_guard.clear(); - return {new_pos, elem}; - - } else if constexpr (requires { ranges::weak_pop_back(runs_); }) { - auto& elem = ranges::emplace_back_ref(runs_, std::forward(value)); + if constexpr (requires { ranges::weak_pop_back(runs_); }) { + auto& elem = ranges::emplace_back_ref(runs_, std::forward(args)...); [[maybe_unused]] elem_insertion_guard elem_insertion_guard{this}; auto const prev_it = std::ranges::prev(std::ranges::end(runs_), 2); @@ -365,7 +379,7 @@ class run_length_sequence return {new_pos, elem}; } else { - T temp(std::forward(value)); + T temp(std::forward(args)...); if (ranges::back(std::as_const(runs_)) == std::as_const(temp)) { // Equivalent element already exists; no need to insert. return {offsets_.back()++, ranges::back(runs_)}; @@ -382,7 +396,7 @@ class run_length_sequence } private: - constexpr void check_range_concepts() const noexcept + static constexpr void check_range_concepts() noexcept { static_assert(std::ranges::bidirectional_range); static_assert(std::same_as, T>); @@ -395,6 +409,18 @@ class run_length_sequence }); } + template + constexpr reference emplace_back_on_empty(Args&&... args) IRIS_LIFETIMEBOUND + { + assert(this->empty()); + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; + offsets_.emplace_back(static_cast(0u)); + offsets_.emplace_back(static_cast(1u)); // sentinel + auto& elem = ranges::emplace_back_ref(runs_, std::forward(args)...); + ofs_insertion_guard.clear(); + return {static_cast(0u), elem}; + } + template struct [[nodiscard]] ofs_insertion_guard { @@ -464,15 +490,17 @@ class run_length_sequence constexpr ~check_invariant_guard() noexcept { - if (self_->offsets_.empty()) { - assert(std::ranges::empty(self_->runs_)); - } else { - assert(self_->offsets_.size() == std::ranges::size(self_->runs_) + 1); - assert(self_->offsets_[0] == 0); - for (std::size_t i = 0; i < self_->offsets_.size() - 1; ++i) { - assert(self_->offsets_[i] < self_->offsets_[i + 1]); - } + auto const& offsets = self_->offsets_; + auto const& runs = self_->runs_; + if (offsets.empty()) { + assert(std::ranges::empty(runs)); + return; } + assert(offsets.size() >= 2); + assert(offsets.size() == std::ranges::size(runs) + 1); + assert(offsets.front() == static_cast(0u)); + auto const n = offsets.size(); + assert(offsets[n - 2] < offsets[n - 1]); } private: diff --git a/test/run_length_sequence.cpp b/test/run_length_sequence.cpp index 6bbdfbb..c16b6c8 100644 --- a/test/run_length_sequence.cpp +++ b/test/run_length_sequence.cpp @@ -71,7 +71,7 @@ TEST_CASE("run_length_sequence") { auto&& elem = seq.emplace_back(1.0); REQUIRE(seq.size() == 1); - REQUIRE(seq.segment_count() == 1); + REQUIRE(seq.run_count() == 1); CHECK(elem.index == 0); CHECK(elem.value == 1.0); @@ -87,7 +87,7 @@ TEST_CASE("run_length_sequence") { auto&& elem = seq.emplace_back(1.0); REQUIRE(seq.size() == 2); - REQUIRE(seq.segment_count() == 1); + REQUIRE(seq.run_count() == 1); CHECK(elem.index == 1); CHECK(elem.value == 1.0); @@ -108,7 +108,7 @@ TEST_CASE("run_length_sequence") { auto&& elem = seq.emplace_back(1.1); REQUIRE(seq.size() == 3); - REQUIRE(seq.segment_count() == 2); + REQUIRE(seq.run_count() == 2); CHECK(elem.index == 2); CHECK(elem.value == 1.1); @@ -134,7 +134,7 @@ TEST_CASE("run_length_sequence") { auto&& elem = seq.emplace_back(1.1); REQUIRE(seq.size() == 4); - REQUIRE(seq.segment_count() == 2); + REQUIRE(seq.run_count() == 2); CHECK(elem.index == 3); CHECK(elem.value == 1.1); @@ -165,7 +165,7 @@ TEST_CASE("run_length_sequence") { auto&& elem = seq.emplace_back(1.1); REQUIRE(seq.size() == 5); - REQUIRE(seq.segment_count() == 2); + REQUIRE(seq.run_count() == 2); CHECK(elem.index == 4); CHECK(elem.value == 1.1); @@ -201,7 +201,7 @@ TEST_CASE("run_length_sequence") { auto&& elem = seq.emplace_back(1.2); REQUIRE(seq.size() == 6); - REQUIRE(seq.segment_count() == 3); + REQUIRE(seq.run_count() == 3); CHECK(elem.index == 5); CHECK(elem.value == 1.2); From 8434d9ea0275babe2f4eabd765d9db7503d638aa Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 04:06:27 +0900 Subject: [PATCH 07/14] Make runs immutable --- include/iris/run_length_sequence.hpp | 48 ++++++++++------------------ 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index aa4b115..2944e2e 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -38,24 +38,9 @@ namespace iris { // new segment may invalidate all iterators, regardless of `RunContainerT`. Reference // stability of the elements follows `RunContainerT`. // -// `run_length_sequence` can be either mutable or immutable, depending on the user's -// preference. By default, `run_length_sequence` allows access to the mutable reference -// of the underlying elements. When the user chooses to modify the element, -// `run_length_sequence` inevitably becomes "uncompressed," as the adjacent equivalence -// would no longer hold. However, `run_length_sequence` still can be used even in such -// partially-altered state, since all other invariants shall remain intact. -// -// Supports heterogeneous comparison iff `T` supports heterogeneous comparison -// for arbitrary type `U`: -// - Equivalence is checked by `operator==(T const& e, U const& value)`, where -// `e` is the reference to the existing item and `value` is the object -// that is passed to the relevant member function. -// -// - For example, when `value` is a parameter given to `run_length_sequence:: -// emplace_back(U&& value)`, the list directly compares `value` with the -// existing element without creating a temporary `T` object. Then, if -// the comparison failed, a new instance of `T` will be directly inserted -// into `RunContainerT` via `std::forward(value)`. +// Precondition for heterogeneous comparison: `e == value` must hold iff +// `e == T(value)`; otherwise the run structure would depend on which overload +// of `emplace_back` (or relevant insertion members) was chosen. // // When inserting an element, `run_length_sequence` does lazy construction of the // actual element object iff heterogeneous comparison is supported for the given @@ -110,11 +95,10 @@ class run_length_sequence private: using offsets_type = std::vector>; - template struct iterator_impl { private: - using value_iterator = std::ranges::iterator_t>; + using value_iterator = std::ranges::iterator_t; using offset_iterator = offsets_type::const_iterator; public: @@ -148,11 +132,6 @@ class run_length_sequence , rel_pos_(pos) {} - [[nodiscard]] constexpr operator iterator_impl() const noexcept requires (IsConst == false) - { - return iterator_impl{value_it_, ofs_it_, rel_pos_}; - } - [[nodiscard]] constexpr reference operator*() const noexcept { return {index(), *value_it_}; @@ -236,11 +215,11 @@ class run_length_sequence using run_container_type = RunContainerT; using value_type = indexed_value; - using reference = indexed_value; using const_reference = indexed_value; + using reference = const_reference; - using iterator = iterator_impl; - using const_iterator = iterator_impl; + using const_iterator = iterator_impl; + using iterator = const_iterator; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; @@ -312,7 +291,7 @@ class run_length_sequence template requires std::constructible_from && req::half_equality_comparable - constexpr reference emplace_back(U&& value) IRIS_LIFETIMEBOUND + constexpr const_reference emplace_back(U&& value) IRIS_LIFETIMEBOUND { check_range_concepts(); static_assert(std::equality_comparable); @@ -344,7 +323,7 @@ class run_length_sequence template requires std::constructible_from - constexpr reference emplace_back(Args&&... args) IRIS_LIFETIMEBOUND + constexpr const_reference emplace_back(Args&&... args) IRIS_LIFETIMEBOUND { check_range_concepts(); static_assert(std::equality_comparable); @@ -410,7 +389,7 @@ class run_length_sequence } template - constexpr reference emplace_back_on_empty(Args&&... args) IRIS_LIFETIMEBOUND + constexpr const_reference emplace_back_on_empty(Args&&... args) IRIS_LIFETIMEBOUND { assert(this->empty()); [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; @@ -501,6 +480,13 @@ class run_length_sequence assert(offsets.front() == static_cast(0u)); auto const n = offsets.size(); assert(offsets[n - 2] < offsets[n - 1]); + + // Adjacent runs must differ + if (n >= 3) { + auto const last = std::ranges::prev(std::ranges::end(runs)); + auto const before_last = std::ranges::prev(last); + assert(!(*before_last == *last)); + } } private: From e59d4f535682e5d2f07501702a22816d86208c24 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 04:09:35 +0900 Subject: [PATCH 08/14] Refactoring --- include/iris/run_length_sequence.hpp | 51 ++++++---------------------- 1 file changed, 11 insertions(+), 40 deletions(-) diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index 2944e2e..1e89e80 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -35,7 +35,7 @@ namespace iris { // per each adjacent equivalent elements. // // Iterator invalidation does not follow `RunContainerT`: any insertion that starts a -// new segment may invalidate all iterators, regardless of `RunContainerT`. Reference +// new run may invalidate all iterators, regardless of `RunContainerT`. Reference // stability of the elements follows `RunContainerT`. // // Precondition for heterogeneous comparison: `e == value` must hold iff @@ -56,8 +56,8 @@ namespace iris { // // The semantics described in the previous section applies similarly to `emplace_front`. // -// Internal structure is maintained with two independent lists, where the first -// list holds one element instance per each segment and the other list holds the +// Internal structure is maintained with two independent containers, where the first +// container holds one element instance per each run and the other one holds the // *offsets* that represent each insertion of elements. // // For example, when items are emplaced in this order: @@ -281,7 +281,7 @@ class run_length_sequence } constexpr void clear() noexcept - requires requires(RunContainerT& list) { list.clear(); } + requires requires(RunContainerT& runs) { runs.clear(); } { static_assert(req::Cpp17Destructible); IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD @@ -382,9 +382,9 @@ class run_length_sequence static_assert(std::same_as, T&>); static_assert(std::same_as, T const&>); - static_assert(requires (RunContainerT& list) { - { list.back() } -> std::same_as; - { list.front() } -> std::same_as; + static_assert(requires (RunContainerT& runs) { + { runs.back() } -> std::same_as; + { runs.front() } -> std::same_as; }); } @@ -513,39 +513,10 @@ struct run_length_sequence_comp noexcept(noexcept(std::declval() == std::declval())) { static_assert(std::equality_comparable); - - if (a.size() != b.size()) return false; - if (a.empty()) return true; - - auto ra = std::ranges::begin(a.runs_); - auto rb = std::ranges::begin(b.runs_); - auto oa = a.offsets_.begin(); // current run's start; *(oa + 1) is its end - auto ob = b.offsets_.begin(); - auto const ra_end = std::ranges::end(a.runs_); - - while (true) { - if (!(*ra == *rb)) return false; - - auto const next_a = *(oa + 1); - auto const next_b = *(ob + 1); - - if (next_a == next_b) { - ++ra; ++oa; - ++rb; ++ob; - - } else if (next_a < next_b) { - ++ra; ++oa; - - } else { - ++rb; ++ob; - } - - if (ra == ra_end) { - // Logical sizes are equal, so b must be exhausted here too. - assert(rb == std::ranges::end(b.runs_)); - return true; - } - } + static_assert(std::equality_comparable); + // Adjacent runs never compare equal (class invariant), so the representation + // is canonical and representational equality is logical equality. + return a.offsets_ == b.offsets_ && a.runs_ == b.runs_; } }; From 4ff475a5f367e81ae469eb130df446b8853c1b37 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 04:13:34 +0900 Subject: [PATCH 09/14] Remove redundant functions --- include/iris/run_length_sequence.hpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index 1e89e80..8f36fbd 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -223,21 +223,9 @@ class run_length_sequence using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - [[nodiscard]] constexpr iterator begin() noexcept { check_range_concepts(); return iterator{std::ranges::begin(runs_), offsets_.begin()}; } [[nodiscard]] constexpr const_iterator begin() const noexcept { check_range_concepts(); return const_iterator{std::ranges::begin(runs_), offsets_.begin()}; } [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } - [[nodiscard]] constexpr iterator end() noexcept - { - check_range_concepts(); - if (offsets_.empty()) { - assert(std::ranges::empty(runs_)); - return iterator{std::ranges::end(runs_), offsets_.end()}; - } else { - assert(offsets_.size() >= 2); - return iterator{std::ranges::end(runs_), std::prev(offsets_.end())}; - } - } [[nodiscard]] constexpr const_iterator end() const noexcept { check_range_concepts(); @@ -251,10 +239,8 @@ class run_length_sequence } [[nodiscard]] constexpr const_iterator cend() const noexcept { return end(); } - [[nodiscard]] constexpr reverse_iterator rbegin() noexcept { return reverse_iterator{end()}; } [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{end()}; } [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } - [[nodiscard]] constexpr reverse_iterator rend() noexcept { return reverse_iterator{begin()}; } [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator{begin()}; } [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return rend(); } From 2b8bc22478e6453acf27ac0b78289a7dfc27e471 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 04:17:17 +0900 Subject: [PATCH 10/14] Remove emplace_front --- include/iris/run_length_sequence.hpp | 35 +++++++++++----------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index 8f36fbd..baed0fa 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -34,6 +34,9 @@ namespace iris { // A compressed bidirectional container that holds only one instance of `T` // per each adjacent equivalent elements. // +// Invariant: adjacent runs never compare equal. This makes the representation +// canonical, which `operator==` relies on. +// // Iterator invalidation does not follow `RunContainerT`: any insertion that starts a // new run may invalidate all iterators, regardless of `RunContainerT`. Reference // stability of the elements follows `RunContainerT`. @@ -54,8 +57,6 @@ namespace iris { // comparison; when the comparison does not hold, the object is move-inserted // into `RunContainerT` as if by `runs_.emplace_back(std::forward(temp))`. // -// The semantics described in the previous section applies similarly to `emplace_front`. -// // Internal structure is maintained with two independent containers, where the first // container holds one element instance per each run and the other one holds the // *offsets* that represent each insertion of elements. @@ -300,7 +301,7 @@ class run_length_sequence // Need to insert new element auto const new_pos = offsets_.back(); offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel - [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; auto& elem = ranges::emplace_back_ref(runs_, std::forward(value)); ofs_insertion_guard.clear(); return {new_pos, elem}; @@ -326,7 +327,7 @@ class run_length_sequence } if constexpr (requires { ranges::weak_pop_back(runs_); }) { auto& elem = ranges::emplace_back_ref(runs_, std::forward(args)...); - [[maybe_unused]] elem_insertion_guard elem_insertion_guard{this}; + [[maybe_unused]] elem_insertion_guard elem_insertion_guard{this}; auto const prev_it = std::ranges::prev(std::ranges::end(runs_), 2); @@ -352,7 +353,7 @@ class run_length_sequence // Need to insert new element auto const new_pos = offsets_.back(); offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel - [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; auto& elem = ranges::emplace_back_ref(runs_, std::move(temp)); ofs_insertion_guard.clear(); return {new_pos, elem}; @@ -378,7 +379,7 @@ class run_length_sequence constexpr const_reference emplace_back_on_empty(Args&&... args) IRIS_LIFETIMEBOUND { assert(this->empty()); - [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; offsets_.emplace_back(static_cast(0u)); offsets_.emplace_back(static_cast(1u)); // sentinel auto& elem = ranges::emplace_back_ref(runs_, std::forward(args)...); @@ -386,7 +387,7 @@ class run_length_sequence return {static_cast(0u), elem}; } - template + template struct [[nodiscard]] ofs_insertion_guard { constexpr explicit ofs_insertion_guard(run_length_sequence* self) noexcept @@ -401,24 +402,19 @@ class run_length_sequence constexpr ~ofs_insertion_guard() noexcept { if (!self_) return; - if constexpr (IsBack) { - if constexpr (WasEmpty) { - self_->offsets_.clear(); - } else { - self_->offsets_.pop_back(); - } + if constexpr (WasEmpty) { + self_->offsets_.clear(); } else { - static_assert(false, "sorry, not implemented"); + self_->offsets_.pop_back(); } } private: run_length_sequence* self_; }; - template + template friend struct ofs_insertion_guard; - template struct [[nodiscard]] elem_insertion_guard { constexpr explicit elem_insertion_guard(run_length_sequence* self) noexcept @@ -433,17 +429,12 @@ class run_length_sequence constexpr ~elem_insertion_guard() noexcept { if (!self_) return; - if constexpr (IsBack) { - ranges::weak_pop_back(self_->runs_); - } else { - static_assert(false, "sorry, not implemented"); - } + ranges::weak_pop_back(self_->runs_); } private: run_length_sequence* self_; }; - template friend struct elem_insertion_guard; #ifndef NDEBUG From 299401ce7e54c04a0aaf775c638e4bbaab6866eb Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 04:38:21 +0900 Subject: [PATCH 11/14] Fix relops --- include/iris/indexed_value.hpp | 33 +++++++++++++++++++++++++++- include/iris/run_length_sequence.hpp | 11 ++++++++++ test/indexed_value.cpp | 2 ++ test/run_length_sequence.cpp | 9 ++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/include/iris/indexed_value.hpp b/include/iris/indexed_value.hpp index aed5466..6958173 100644 --- a/include/iris/indexed_value.hpp +++ b/include/iris/indexed_value.hpp @@ -3,7 +3,9 @@ // SPDX-License-Identifier: MIT -#include +#include // IWYU pragma: keep +#include +#include #include #include @@ -87,6 +89,35 @@ struct indexed_value } }; +template + requires req::half_equality_comparable && req::half_equality_comparable +[[nodiscard]] constexpr bool operator==(indexed_value const& a, indexed_value const& b) + noexcept(noexcept(a.index == b.index) && noexcept(a.value == b.value)) +{ + return a.index == b.index && a.value == b.value; +} + +template + requires requires (IndexT const& i, IndexU const& j, T const& t, U const& u) { + cmp::synth_three_way{}(i, j); + cmp::synth_three_way{}(t, u); + } +[[nodiscard]] constexpr auto operator<=>(indexed_value const& a, indexed_value const& b) + noexcept( + noexcept(cmp::synth_three_way{}(a.index, b.index)) && + noexcept(cmp::synth_three_way{}(a.value, b.value)) + ) + -> std::common_comparison_category_t< + cmp::synth_three_way_result, + cmp::synth_three_way_result + > +{ + if (auto const comp = cmp::synth_three_way{}(a.index, b.index); comp != 0) { + return comp; + } + return cmp::synth_three_way{}(a.value, b.value); +} + template [[nodiscard]] constexpr decltype(auto) get(indexed_value& elem) noexcept { diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index baed0fa..8b37897 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -276,6 +276,17 @@ class run_length_sequence offsets_.clear(); } + [[nodiscard]] constexpr const_reference front() const noexcept IRIS_LIFETIMEBOUND + { + assert(!this->empty()); + return {static_cast(0u), ranges::front(runs_)}; + } + [[nodiscard]] constexpr const_reference back() const noexcept IRIS_LIFETIMEBOUND + { + assert(!this->empty()); + return {static_cast(offsets_.back() - static_cast(1u)), ranges::back(runs_)}; + } + template requires std::constructible_from && req::half_equality_comparable constexpr const_reference emplace_back(U&& value) IRIS_LIFETIMEBOUND diff --git a/test/indexed_value.cpp b/test/indexed_value.cpp index e598e48..acfbddb 100644 --- a/test/indexed_value.cpp +++ b/test/indexed_value.cpp @@ -95,6 +95,8 @@ TEST_CASE("indexed_value") { std::string value{"foo"}; LR indexed{42, value}; + CHECK(indexed == indexed); + CHECK((indexed <=> indexed) == std::strong_ordering::equal); auto [index, element] = indexed; CHECK(index == 42); CHECK(&element == &value); diff --git a/test/run_length_sequence.cpp b/test/run_length_sequence.cpp index c16b6c8..221d808 100644 --- a/test/run_length_sequence.cpp +++ b/test/run_length_sequence.cpp @@ -74,6 +74,8 @@ TEST_CASE("run_length_sequence") REQUIRE(seq.run_count() == 1); CHECK(elem.index == 0); CHECK(elem.value == 1.0); + CHECK(seq.front() == elem); + CHECK(seq.back() == elem); auto it = seq.begin(); @@ -81,6 +83,8 @@ TEST_CASE("run_length_sequence") auto&& e0 = *it++; CHECK(e0.index == 0); CHECK(e0.value == 1.0); + CHECK(seq.front() == elem); + CHECK(seq.back() == e0); REQUIRE(it == seq.end()); } @@ -90,6 +94,7 @@ TEST_CASE("run_length_sequence") REQUIRE(seq.run_count() == 1); CHECK(elem.index == 1); CHECK(elem.value == 1.0); + CHECK(seq.back() == elem); auto it = seq.begin(); @@ -111,6 +116,7 @@ TEST_CASE("run_length_sequence") REQUIRE(seq.run_count() == 2); CHECK(elem.index == 2); CHECK(elem.value == 1.1); + CHECK(seq.back() == elem); auto it = seq.begin(); @@ -137,6 +143,7 @@ TEST_CASE("run_length_sequence") REQUIRE(seq.run_count() == 2); CHECK(elem.index == 3); CHECK(elem.value == 1.1); + CHECK(seq.back() == elem); auto it = seq.begin(); @@ -168,6 +175,7 @@ TEST_CASE("run_length_sequence") REQUIRE(seq.run_count() == 2); CHECK(elem.index == 4); CHECK(elem.value == 1.1); + CHECK(seq.back() == elem); auto it = seq.begin(); @@ -204,6 +212,7 @@ TEST_CASE("run_length_sequence") REQUIRE(seq.run_count() == 3); CHECK(elem.index == 5); CHECK(elem.value == 1.2); + CHECK(seq.back() == elem); auto it = seq.begin(); From e36756b6c3ae2cfc2b37febc30083beb51aa3d57 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 04:42:44 +0900 Subject: [PATCH 12/14] Add swap --- include/iris/run_length_sequence.hpp | 15 +++++++++++++++ test/run_length_sequence.cpp | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index 8b37897..62c98d2 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -372,6 +372,14 @@ class run_length_sequence } } + constexpr void swap(run_length_sequence& other) + noexcept(std::is_nothrow_swappable_v && std::is_nothrow_swappable_v) + { + using std::swap; + swap(runs_, other.runs_); + swap(offsets_, other.offsets_); + } + private: static constexpr void check_range_concepts() noexcept { @@ -491,6 +499,13 @@ class run_length_sequence #undef IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD }; +template +constexpr void swap(run_length_sequence& a, run_length_sequence& b) + noexcept(noexcept(a.swap(b))) +{ + a.swap(b); +} + namespace detail { diff --git a/test/run_length_sequence.cpp b/test/run_length_sequence.cpp index 221d808..71d14d2 100644 --- a/test/run_length_sequence.cpp +++ b/test/run_length_sequence.cpp @@ -66,6 +66,11 @@ TEST_CASE("run_length_sequence") CHECK(seq.begin() == seq.end()); CHECK(seq == seq); + { + using std::swap; + swap(seq, seq); + } + CHECK_NOTHROW((void)(seq.cbegin() = seq.begin())); { From e69669c951d74f483b0e735b814743b65b6c2b84 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 4 Sep 2026 06:43:14 +0900 Subject: [PATCH 13/14] Add tuple/pair interop --- include/iris/indexed_value.hpp | 333 +++++++++++++++++++++++++-- include/iris/ranges.hpp | 6 + include/iris/run_length_sequence.hpp | 12 + test/indexed_value.cpp | 16 +- test/run_length_sequence.cpp | 18 ++ 5 files changed, 366 insertions(+), 19 deletions(-) diff --git a/include/iris/indexed_value.hpp b/include/iris/indexed_value.hpp index 6958173..9a1a44c 100644 --- a/include/iris/indexed_value.hpp +++ b/include/iris/indexed_value.hpp @@ -7,9 +7,12 @@ #include #include +#include + #include #include #include +#include #include @@ -24,6 +27,8 @@ struct indexed_value IndexT index; T value; + // ---------------------------------------------------- + template requires std::convertible_to && @@ -87,6 +92,138 @@ struct indexed_value static_cast(static_cast(value)) }; } + + // ---------------------------------------------------- + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::tuple() & + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::tuple( + static_cast(index), + static_cast(value) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::tuple() const& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::tuple( + static_cast(index), + static_cast(value) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::tuple() && + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::tuple( + static_cast(static_cast(index)), + static_cast(static_cast(value)) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::tuple() const&& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::tuple( + static_cast(static_cast(index)), + static_cast(static_cast(value)) + ); + } + + // ---------------------------------------------------- + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::pair() & + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::pair( + static_cast(index), + static_cast(value) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::pair() const& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::pair( + static_cast(index), + static_cast(value) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::pair() && + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::pair( + static_cast(static_cast(index)), + static_cast(static_cast(value)) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::pair() const&& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::pair( + static_cast(static_cast(index)), + static_cast(static_cast(value)) + ); + } }; template @@ -97,6 +234,19 @@ template return a.index == b.index && a.value == b.value; } +template + requires + (!is_ttp_specialization_of_v) && + requires { + requires std::tuple_size>::value == 2; + } && + req::half_equality_comparable>> && + req::half_equality_comparable>> +[[nodiscard]] constexpr bool operator==(indexed_value const& a, U const& b) +{ + return a.index == std::get<0>(b) && a.value == std::get<1>(b); +} + template requires requires (IndexT const& i, IndexU const& j, T const& t, U const& u) { cmp::synth_three_way{}(i, j); @@ -152,6 +302,24 @@ template } // iris +template +struct std::tuple_size> + : std::integral_constant +{}; + +template +struct std::tuple_element<0, iris::indexed_value> +{ + using type = IndexT; +}; +template +struct std::tuple_element<1, iris::indexed_value> +{ + using type = T; +}; + + +// indexed_value + indexed_value template requires requires { typename std::common_type_t; @@ -168,20 +336,17 @@ struct std::common_type< >; }; +// indexed_value + indexed_value template< - class IndexT, class T, - class IndexU, class U, - template class TQual, - template class UQual + class IndexT, class T, class IndexU, class U, + template class TQual, template class UQual > requires requires { typename std::common_reference_t, UQual>; typename std::common_reference_t, UQual>; } struct std::basic_common_reference< - iris::indexed_value, - iris::indexed_value, - TQual, UQual + iris::indexed_value, iris::indexed_value, TQual, UQual > { using type = iris::indexed_value< @@ -190,20 +355,154 @@ struct std::basic_common_reference< >; }; -template -struct std::tuple_size> - : std::integral_constant -{}; +// ----------------------------------------------------------- -template -struct std::tuple_element<0, iris::indexed_value> +// indexed_value + std::tuple +template + requires requires { + typename std::common_type_t; + typename std::common_type_t; + } +struct std::common_type< + iris::indexed_value, + std::tuple +> { - using type = IndexT; + using type = std::tuple< + std::common_type_t, + std::common_type_t + >; }; -template -struct std::tuple_element<1, iris::indexed_value> + +// std::tuple + indexed_value +template + requires requires { + typename std::common_type_t; + typename std::common_type_t; + } +struct std::common_type< + std::tuple, + iris::indexed_value +> { - using type = T; + using type = std::tuple< + std::common_type_t, + std::common_type_t + >; +}; + + +// indexed_value + std::tuple +template< + class IndexT, class T, class Tup0, class Tup1, + template class TQual, template class UQual +> + requires requires { + typename std::common_reference_t, UQual>; + typename std::common_reference_t, UQual>; + } +struct std::basic_common_reference< + iris::indexed_value, std::tuple, TQual, UQual +> +{ + using type = std::tuple< + std::common_reference_t, UQual>, + std::common_reference_t, UQual> + >; +}; + +// std::tuple + indexed_value +template< + class Tup0, class Tup1, class IndexU, class U, + template class TQual, template class UQual +> + requires requires { + typename std::common_reference_t, UQual>; + typename std::common_reference_t, UQual>; + } +struct std::basic_common_reference< + std::tuple, iris::indexed_value, TQual, UQual +> +{ + using type = std::tuple< + std::common_reference_t, UQual>, + std::common_reference_t, UQual> + >; +}; + +// ----------------------------------------------------------- + +// indexed_value + std::pair +template + requires requires { + typename std::common_type_t; + typename std::common_type_t; + } +struct std::common_type< + iris::indexed_value, + std::pair +> +{ + using type = std::pair< + std::common_type_t, + std::common_type_t + >; +}; + +// std::pair + indexed_value +template + requires requires { + typename std::common_type_t; + typename std::common_type_t; + } +struct std::common_type< + std::pair, + iris::indexed_value +> +{ + using type = std::pair< + std::common_type_t, + std::common_type_t + >; +}; + + +// indexed_value + std::pair +template< + class IndexT, class T, class Tup0, class Tup1, + template class TQual, template class UQual +> + requires requires { + typename std::common_reference_t, UQual>; + typename std::common_reference_t, UQual>; + } +struct std::basic_common_reference< + iris::indexed_value, std::pair, TQual, UQual +> +{ + using type = std::pair< + std::common_reference_t, UQual>, + std::common_reference_t, UQual> + >; +}; + +// std::pair + indexed_value +template< + class Tup0, class Tup1, class IndexU, class U, + template class TQual, template class UQual +> + requires requires { + typename std::common_reference_t, UQual>; + typename std::common_reference_t, UQual>; + } +struct std::basic_common_reference< + std::pair, iris::indexed_value, TQual, UQual +> +{ + using type = std::pair< + std::common_reference_t, UQual>, + std::common_reference_t, UQual> + >; }; #endif diff --git a/include/iris/ranges.hpp b/include/iris/ranges.hpp index 4394ff8..fa057a5 100644 --- a/include/iris/ranges.hpp +++ b/include/iris/ranges.hpp @@ -398,6 +398,12 @@ concept fixed_array_writable = template using dummy_fixed_array = T[1]; + +template +concept container_compatible_range = + std::ranges::input_range && + std::convertible_to, ElemT>; + } // iris::ranges #endif diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index 62c98d2..cd45b28 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -224,6 +224,18 @@ class run_length_sequence using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; + + constexpr run_length_sequence() = default; + + template R> + constexpr run_length_sequence(std::from_range_t, R&& r) + { + for (auto&& elem : r) { + this->emplace_back(std::forward(elem)); + } + } + + [[nodiscard]] constexpr const_iterator begin() const noexcept { check_range_concepts(); return const_iterator{std::ranges::begin(runs_), offsets_.begin()}; } [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } diff --git a/test/indexed_value.cpp b/test/indexed_value.cpp index acfbddb..412d41e 100644 --- a/test/indexed_value.cpp +++ b/test/indexed_value.cpp @@ -4,14 +4,15 @@ #include +#include #include #include #include +using iris::indexed_value; + TEST_CASE("indexed_value") { - using iris::indexed_value; - using V = indexed_value; //using CV = indexed_value; using LR = indexed_value; @@ -102,3 +103,14 @@ TEST_CASE("indexed_value") CHECK(&element == &value); } } + +TEST_CASE("indexed_value: tuple/pair") +{ + STATIC_CHECK(std::equal_to{}(indexed_value{}, indexed_value{})); + + STATIC_CHECK(std::equal_to{}(indexed_value{}, std::tuple{})); + STATIC_CHECK(std::equal_to{}(std::tuple{}, indexed_value{})); + + STATIC_CHECK(std::equal_to{}(indexed_value{}, std::pair{})); + STATIC_CHECK(std::equal_to{}(std::pair{}, indexed_value{})); +} diff --git a/test/run_length_sequence.cpp b/test/run_length_sequence.cpp index 71d14d2..de52533 100644 --- a/test/run_length_sequence.cpp +++ b/test/run_length_sequence.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -59,7 +60,24 @@ TEST_CASE("run_length_sequence") CHECK(iv.index == 0); CHECK(iv.value == 0.0); // NOLINT(readability-container-size-empty) } +} + +TEST_CASE("run_length_sequence: misc") +{ + { + // TODO + //iris::run_length_sequence seq{std::from_range, std::initializer_list{2, 3, 3, 4, 2, 2, 3, 4, 4}}; + //constexpr auto expected_arr = std::array{2, 3, 4}; + //auto expected = std::views::zip(std::views::iota(0u), expected_arr); + //auto aaa = seq | std::ranges::to(); + //auto bbb = expected | std::ranges::to(); + //CHECK(aaa == bbb); + } +} +TEST_CASE("run_length_sequence: sequential insertion") +{ + using RLS = iris::run_length_sequence; RLS seq; CHECK(seq.empty()); CHECK(seq.size() == 0); // NOLINT(readability-container-size-empty) From e54a6f0d3678e75b62cf2a156cfaaf3a69ff5f5a Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 5 Sep 2026 16:39:33 +0900 Subject: [PATCH 14/14] Add more public interface --- include/iris/container_traits.hpp | 671 ++++++++++++++++++ include/iris/indexed_value.hpp | 4 +- include/iris/interval.hpp | 20 +- include/iris/marshal/serialize.hpp | 13 +- include/iris/marshal/serialize_traits.hpp | 7 +- include/iris/ranges.hpp | 286 +------- include/iris/run_length_sequence.hpp | 231 +++++-- test/CMakeLists.txt | 1 + test/container_traits.cpp | 800 ++++++++++++++++++++++ test/ranges.cpp | 127 +--- test/run_length_sequence.cpp | 304 +++++++- 11 files changed, 1988 insertions(+), 476 deletions(-) create mode 100644 include/iris/container_traits.hpp create mode 100644 test/container_traits.cpp diff --git a/include/iris/container_traits.hpp b/include/iris/container_traits.hpp new file mode 100644 index 0000000..eaa095f --- /dev/null +++ b/include/iris/container_traits.hpp @@ -0,0 +1,671 @@ +#ifndef IRIS_ZZ_CONTAINER_TRAITS_HPP +#define IRIS_ZZ_CONTAINER_TRAITS_HPP + +// SPDX-License-Identifier: MIT + +#include // IWYU pragma: keep + +#include // IWYU pragma: keep +#include // IWYU pragma: keep + +#include +#include +#include +#include +#include + +namespace iris::container { + +template +concept mapping_container = + ranges::mapping_range && + std::default_initializable> && + requires( + std::remove_cvref_t& c, + typename std::remove_cvref_t::key_type k, + typename std::remove_cvref_t::mapped_type v + ) { + c.emplace(std::move(k), std::move(v)); + }; + +template +concept unique_mapping_container = + mapping_container && + requires( + std::remove_cvref_t& c, + typename std::remove_cvref_t::key_type k, + typename std::remove_cvref_t::mapped_type v + ) { + { c.try_emplace(std::move(k), std::move(v)).second } -> std::convertible_to; + { c.insert_or_assign(std::move(k), std::move(v)).second } -> std::convertible_to; + }; + + +namespace detail { + +template +concept has_front = requires(ContainerT& cont) { + { cont.front() } -> std::same_as>; +}; + +template +concept has_back = requires(ContainerT& cont) { + { cont.back() } -> std::same_as>; +}; + +struct front_fn +{ + template + requires has_front + [[nodiscard]] static constexpr decltype(auto) operator()(R&& r) + noexcept(noexcept(r.front())) + { + return r.front(); + } + + template + requires (!has_front) && std::ranges::input_range + [[nodiscard]] static constexpr decltype(auto) operator()(R&& r) + noexcept(noexcept(*std::ranges::begin(r))) + { + return *std::ranges::begin(r); + } +}; + +struct back_fn +{ + template + requires has_back + [[nodiscard]] static constexpr decltype(auto) operator()(R&& r) + noexcept(noexcept(r.back())) + { + return r.back(); + } + + template + requires + (!has_back) && + std::ranges::bidirectional_range && + std::ranges::common_range + [[nodiscard]] static constexpr decltype(auto) operator()(R&& r) + noexcept(noexcept(*std::ranges::prev(std::ranges::end(r)))) + { + return *std::ranges::prev(std::ranges::end(r)); + } +}; + +} // detail + +[[maybe_unused]] inline constexpr detail::front_fn front{}; +[[maybe_unused]] inline constexpr detail::back_fn back{}; + +template concept front_accessible = requires(R& r) { front(r); }; +template concept back_accessible = requires(R& r) { back(r); }; + + +// ------------------------------------------------------------ + +namespace detail { + +template +concept has_emplace_front = + requires(ContainerT& cont) { + cont.emplace_front(std::declval()...); + }; + +template +concept has_push_front = + sizeof...(Args) == 1 && + requires(ContainerT& cont) { + cont.push_front(std::declval()...); + }; + +template +concept has_begin_emplace = + std::ranges::range && + requires(ContainerT& cont) { + cont.emplace(std::ranges::begin(cont), std::declval()...); + }; + +template +concept has_begin_insert = + sizeof...(Args) == 1 && + std::ranges::range && + requires(ContainerT& cont) { + cont.insert(std::ranges::begin(cont), std::declval()...); + }; + +} // detail + +template +concept front_pushable = + detail::has_emplace_front || + detail::has_push_front; + +template +concept default_prependable = + std::ranges::range && + std::default_initializable> && + ( + detail::has_emplace_front || detail::has_begin_emplace || + ( + std::move_constructible> && + ( + detail::has_push_front> || + detail::has_begin_insert> + ) + ) + ); + +template +concept prependable = + front_pushable || + detail::has_begin_emplace || + detail::has_begin_insert || + (sizeof...(Args) == 0 && default_prependable); + +namespace detail { + +template +concept front_emplace_returns = + has_emplace_front && + !std::is_void_v().emplace_front(std::declval()...))>; + +template +concept default_front_emplace_front_accessible = + !NeedReturn || + front_emplace_returns || + front_accessible || + ( + !has_emplace_front && + ( + has_begin_emplace || + ( + !has_push_front> && + has_begin_insert> + ) + ) + ); + +template +concept front_emplace_front_accessible = + !NeedReturn || + front_emplace_returns || + front_accessible || + ( + !has_emplace_front && + !has_push_front && + (has_begin_emplace || has_begin_insert) + ); + +template +struct prepend_fn +{ + template + requires default_prependable && default_front_emplace_front_accessible + static constexpr decltype(auto) + operator()(ContainerT& cont) + { + if constexpr (has_emplace_front) { + if constexpr (NeedReturn) { + if constexpr (std::is_void_v) { + cont.emplace_front(); + return front(cont); + } else { + return cont.emplace_front(); + } + } else { + (void)cont.emplace_front(); + } + } else { + using ValueT = std::ranges::range_value_t; + if constexpr (has_begin_emplace) { // prefer most ergonomic insertion for immovable types + if constexpr (NeedReturn) { + return *cont.emplace(std::ranges::begin(cont)); + } else { + (void)cont.emplace(std::ranges::begin(cont)); + } + } else if constexpr (has_push_front) { + cont.push_front(ValueT{}); + if constexpr (NeedReturn) { + return front(cont); + } + } else if constexpr (has_begin_insert) { + if constexpr (NeedReturn) { + return *cont.insert(std::ranges::begin(cont), ValueT{}); + } else { + (void)cont.insert(std::ranges::begin(cont), ValueT{}); + } + } else { + static_assert(false); + } + } + } + + template + requires prependable && front_emplace_front_accessible + static constexpr decltype(auto) + operator()(ContainerT& cont, FirstT&& first, Rest&&... rest) + { + if constexpr (has_emplace_front) { + if constexpr (NeedReturn) { + if constexpr (std::is_void_v(first), std::forward(rest)...))>) { + cont.emplace_front(std::forward(first), std::forward(rest)...); + return front(cont); + } else { + return cont.emplace_front(std::forward(first), std::forward(rest)...); + } + } else { + (void)cont.emplace_front(std::forward(first), std::forward(rest)...); + } + } else { + if constexpr (has_push_front) { + static_assert(sizeof...(Rest) == 0); + cont.push_front(std::forward(first)); + if constexpr (NeedReturn) { + return front(cont); + } + } else if constexpr (has_begin_emplace) { + if constexpr (NeedReturn) { + return *cont.emplace(std::ranges::begin(cont), std::forward(first), std::forward(rest)...); + } else { + (void)cont.emplace(std::ranges::begin(cont), std::forward(first), std::forward(rest)...); + } + } else if constexpr (has_begin_insert) { + static_assert(sizeof...(Rest) == 0); + if constexpr (NeedReturn) { + return *cont.insert(std::ranges::begin(cont), std::forward(first)); + } else { + (void)cont.insert(std::ranges::begin(cont), std::forward(first)); + } + } else { + static_assert(false); + } + } + } +}; + +} // detail + +[[maybe_unused]] inline constexpr detail::prepend_fn prepend_return{}; +[[maybe_unused]] inline constexpr detail::prepend_fn prepend{}; + + +// ------------------------------------------------------------ + +namespace detail { + +template +concept has_emplace_back = + requires(ContainerT& cont) { + cont.emplace_back(std::declval()...); + }; + +template +concept has_push_back = + sizeof...(Args) == 1 && + requires(ContainerT& cont) { + cont.push_back(std::declval()...); + }; + +template +concept has_end_emplace = + std::ranges::range && + requires(ContainerT& cont) { + cont.emplace(std::ranges::end(cont), std::declval()...); + }; + +template +concept has_end_insert = + sizeof...(Args) == 1 && + std::ranges::range && + requires(ContainerT& cont) { + cont.insert(std::ranges::end(cont), std::declval()...); + }; + +} // detail + +template +concept back_pushable = + detail::has_emplace_back || + detail::has_push_back; + +template +concept default_appendable = + std::ranges::range && + std::default_initializable> && + ( + detail::has_emplace_back || detail::has_end_emplace || + ( + std::move_constructible> && + ( + detail::has_push_back> || + detail::has_end_insert> + ) + ) + ); + +template +concept appendable = + back_pushable || + detail::has_end_emplace || + detail::has_end_insert || + (sizeof...(Args) == 0 && default_appendable); + +namespace detail { + +template +concept back_emplace_returns = + has_emplace_back && + !std::is_void_v().emplace_back(std::declval()...))>; + +template +concept default_back_emplace_back_accessible = + !NeedReturn || + back_emplace_returns || + back_accessible || + ( + !has_emplace_back && + ( + has_end_emplace || + ( + !has_push_back> && + has_end_insert> + ) + ) + ); + +template +concept back_emplace_back_accessible = + !NeedReturn || + back_emplace_returns || + back_accessible || + ( + !has_emplace_back && + !has_push_back && + (has_end_emplace || has_end_insert) + ); + +template +struct append_fn +{ + template + requires default_appendable && default_back_emplace_back_accessible + static constexpr decltype(auto) + operator()(ContainerT& cont) + { + if constexpr (has_emplace_back) { + if constexpr (NeedReturn) { + if constexpr (std::is_void_v) { + cont.emplace_back(); + return back(cont); + } else { + return cont.emplace_back(); + } + } else { + (void)cont.emplace_back(); + } + } else { + using ValueT = std::ranges::range_value_t; + if constexpr (has_end_emplace) { // prefer most ergonomic insertion for immovable types + if constexpr (NeedReturn) { + return *cont.emplace(std::ranges::end(cont)); + } else { + (void)cont.emplace(std::ranges::end(cont)); + } + } else if constexpr (has_push_back) { + cont.push_back(ValueT{}); + if constexpr (NeedReturn) { + return back(cont); + } + } else if constexpr (has_end_insert) { + if constexpr (NeedReturn) { + return *cont.insert(std::ranges::end(cont), ValueT{}); + } else { + (void)cont.insert(std::ranges::end(cont), ValueT{}); + } + } else { + static_assert(false); + } + } + } + + template + requires appendable && back_emplace_back_accessible + static constexpr decltype(auto) + operator()(ContainerT& cont, FirstT&& first, Rest&&... rest) + { + if constexpr (has_emplace_back) { + if constexpr (NeedReturn) { + if constexpr (std::is_void_v(first), std::forward(rest)...))>) { + cont.emplace_back(std::forward(first), std::forward(rest)...); + return back(cont); + } else { + return cont.emplace_back(std::forward(first), std::forward(rest)...); + } + } else { + (void)cont.emplace_back(std::forward(first), std::forward(rest)...); + } + } else { + if constexpr (has_push_back) { + static_assert(sizeof...(Rest) == 0); + cont.push_back(std::forward(first)); + if constexpr (NeedReturn) { + return back(cont); + } + } else if constexpr (has_end_emplace) { + if constexpr (NeedReturn) { + return *cont.emplace(std::ranges::end(cont), std::forward(first), std::forward(rest)...); + } else { + (void)cont.emplace(std::ranges::end(cont), std::forward(first), std::forward(rest)...); + } + } else if constexpr (has_end_insert) { + static_assert(sizeof...(Rest) == 0); + if constexpr (NeedReturn) { + return *cont.insert(std::ranges::end(cont), std::forward(first)); + } else { + (void)cont.insert(std::ranges::end(cont), std::forward(first)); + } + } else { + static_assert(false); + } + } + } +}; + +} // detail + +[[maybe_unused]] inline constexpr detail::append_fn append_return{}; +[[maybe_unused]] inline constexpr detail::append_fn append{}; + +// ------------------------------------------------------------ + +namespace detail { + +template +concept begin_erasable = + std::ranges::range && + requires(ContainerT& cont) { + cont.erase(std::ranges::begin(cont)); + }; + +template +concept end_erasable = + std::ranges::bidirectional_range && + std::ranges::common_range && + requires(ContainerT& cont) { + cont.erase(std::ranges::prev(std::ranges::end(cont))); + }; + +template +concept front_poppable = requires(ContainerT& cont) { + cont.pop_front(); +}; + +template +concept back_poppable = requires(ContainerT& cont) { + cont.pop_back(); +}; + +struct erase_front_fn +{ + template + requires front_poppable + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.pop_front())) + { + cont.pop_front(); + } + + template + requires (!front_poppable) && begin_erasable + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.erase(std::ranges::begin(cont)))) + { + cont.erase(std::ranges::begin(cont)); + } +}; + +struct erase_back_fn +{ + template + requires back_poppable + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.pop_back())) + { + cont.pop_back(); + } + + template + requires (!back_poppable) && end_erasable + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.erase(std::ranges::prev(std::ranges::end(cont))))) + { + cont.erase(std::ranges::prev(std::ranges::end(cont))); + } +}; + +} // detail + +[[maybe_unused]] inline constexpr detail::erase_front_fn erase_front{}; +[[maybe_unused]] inline constexpr detail::erase_back_fn erase_back{}; + +template concept front_erasable = requires(ContainerT& cont) { erase_front(cont); }; +template concept back_erasable = requires(ContainerT& cont) { erase_back(cont); }; + + +// ------------------------------------------------------------ + +template +concept growable_array = + std::ranges::range && + ( + ( + std::default_initializable> && + appendable + ) || + ( + std::move_constructible> && + appendable> + ) + ); + +template +concept fixed_array = + !growable_array && + std::ranges::output_range> && + std::ranges::sized_range; + +// ------------------------------------------------------------ + +template +concept has_reserve = requires(std::remove_cvref_t& cont, SizeT const size) { + cont.reserve(size); +}; + +template +concept has_shrink_to_fit = requires(std::remove_cvref_t& cont) { + cont.shrink_to_fit(); +}; + + +template +concept compatible_iterator = + // The standard only requires "not participating unless `It` qualifies as an input + // iterator" and leaves the extent unspecified; implementations check as little as + // `iterator_traits::iterator_category`. The iterators that satisfy Cpp17InputIterator + // but not `std::input_iterator` are legacy defects (e.g. `difference_type = void`), + // which we do not support, so `std::input_iterator` is used here. + std::input_iterator && + // This is NOT `std::convertible_to`; see: https://stackoverflow.com/questions/79940611/stdfrom-range-does-not-work-with-explicit-conversions?noredirect=1 + std::constructible_from>; + +// Note: `compatible_range` does NOT subsume `compatible_iterator` by design. +// Historically in the C++ standard, `container(std::from_range_t, R)` and `container(it, se)` +// meant two distinct semantics. For details, see the comment on `compatible_iterator`. +// +// Also note that in the standard, the element requirement of `container(it, se)` is a +// precondition (Cpp17EmplaceConstructible; violation is a hard error inside the constructor), +// whereas `compatible_iterator` makes it a constraint. This keeps +// `std::constructible_from` honest, which generic code such as +// `std::ranges::to` relies on when choosing a construction strategy. +template +concept compatible_range = + std::ranges::input_range && + std::convertible_to, ElemT>; + +} // iris::container + +namespace iris::container::dummy { + +template +struct mapping_container +{ + using key_type = K; + using mapped_type = V; + std::pair const* begin() const; + std::pair const* end() const; + + mapping_container() = default; + mapping_container(mapping_container const&) = delete; + mapping_container(mapping_container&&) = delete; + mapping_container& operator=(mapping_container const&) = delete; + mapping_container& operator=(mapping_container&&) = delete; + + void emplace(K&&, V&&); +}; + +template +struct unique_mapping_container +{ + using key_type = K; + using mapped_type = V; + std::pair const* begin() const; + std::pair const* end() const; + + unique_mapping_container() = default; + unique_mapping_container(unique_mapping_container const&) = delete; + unique_mapping_container(unique_mapping_container&&) = delete; + unique_mapping_container& operator=(unique_mapping_container const&) = delete; + unique_mapping_container& operator=(unique_mapping_container&&) = delete; + + void emplace(K&&, V&&); + std::pair const*, bool> try_emplace(K&&, V&&); + std::pair const*, bool> insert_or_assign(K&&, V&&); +}; + +template +struct growable_array +{ + T const* begin() const; + T const* end() const; + void emplace_back(T&&); + void emplace_back() requires std::default_initializable; +}; + +template +using fixed_array = T[1]; + +} // iris::container::dummy + +#endif diff --git a/include/iris/indexed_value.hpp b/include/iris/indexed_value.hpp index 9a1a44c..a530993 100644 --- a/include/iris/indexed_value.hpp +++ b/include/iris/indexed_value.hpp @@ -237,9 +237,7 @@ template template requires (!is_ttp_specialization_of_v) && - requires { - requires std::tuple_size>::value == 2; - } && + (std::tuple_size>::value == 2) && req::half_equality_comparable>> && req::half_equality_comparable>> [[nodiscard]] constexpr bool operator==(indexed_value const& a, U const& b) diff --git a/include/iris/interval.hpp b/include/iris/interval.hpp index 7d55521..4c95191 100644 --- a/include/iris/interval.hpp +++ b/include/iris/interval.hpp @@ -21,7 +21,7 @@ namespace iris { -template +template struct interval { using value_type = T; @@ -61,7 +61,7 @@ struct interval // Returns `true` if `other` shares any point with `*this`. // Note 1: intersects(∅) always returns `false`. // Note 2: Adjacent-only contact is touches(); for intersects-or-touches use connected(). - template + template [[nodiscard]] constexpr bool intersects(interval const other) const noexcept { return (lower < other.upper && other.lower < upper) && !empty() && !other.empty(); @@ -69,7 +69,7 @@ struct interval // !intersects // Note: disjoint(∅) always returns `true`. - template + template [[nodiscard]] constexpr bool disjoint(interval const other) const noexcept { return (upper <= other.lower || other.upper <= lower) || empty() || other.empty(); @@ -77,7 +77,7 @@ struct interval // Closures meet but the sets share no point. // Note: touches(∅) always returns `false`. - template + template [[nodiscard]] constexpr bool touches(interval const other) const noexcept { return (upper == other.lower || other.upper == lower) && !empty() && !other.empty(); @@ -85,7 +85,7 @@ struct interval // intersects || touches // Note: connected(∅) always returns `false`. - template + template [[nodiscard]] constexpr bool connected(interval const other) const noexcept { return (lower <= other.upper && other.lower <= upper) && !empty() && !other.empty(); @@ -94,7 +94,7 @@ struct interval // Returns `true` if every point of `other` is a point of `*this`. // Note: covers(∅) always returns `true`. // See also: `encloses(other)`. - template + template [[nodiscard]] constexpr bool covers(interval const other) const noexcept { return (lower <= other.lower && other.upper <= upper) || other.empty(); @@ -103,7 +103,7 @@ struct interval // Returns `true` if `other`'s bounds lie within [lower, upper]. // For nonempty `other`: identical to `covers(other)`. // For empty `other`: position-respecting (treats it like a 0-length "text caret".) - template + template [[nodiscard]] constexpr bool encloses(interval const other) const noexcept { return lower <= other.lower && other.upper <= upper; @@ -134,7 +134,7 @@ struct interval // Returns `true` if both intervals have exactly same bounds. // Note: All empty intervals denote ∅ and are mutually equal regardless of // bounds. Differs from `operator==`, which compares data representations. - template + template [[nodiscard]] constexpr bool equals(interval const other) const noexcept { return (lower == other.lower && upper == other.upper) || (empty() && other.empty()); @@ -143,14 +143,14 @@ struct interval // ------------------------------------------- // A ∩ B. Result is canonical empty [0,0) when the sets share no point. - template + template [[nodiscard]] constexpr interval intersection(interval const other) const noexcept { auto const lo = std::max(lower, static_cast(other.lower)); auto const hi = std::min(upper, static_cast(other.upper)); return lo < hi ? interval{lo, hi} : interval{}; } - template + template [[nodiscard]] constexpr interval operator&(interval const other) const noexcept { return intersection(other); diff --git a/include/iris/marshal/serialize.hpp b/include/iris/marshal/serialize.hpp index ef026bc..62f2b4a 100644 --- a/include/iris/marshal/serialize.hpp +++ b/include/iris/marshal/serialize.hpp @@ -5,6 +5,9 @@ #include // IWYU pragma: keep +#include +#include + #include #include @@ -207,7 +210,7 @@ struct basic_load_fn } template R> - requires ranges::growable_array_writable + requires container::growable_array static constexpr void operator()(ReaderT& rd, R& arr) { R tmp{}; @@ -216,8 +219,8 @@ struct basic_load_fn std::ranges::range_value_t elem{}; basic_load_fn{}(elem_rd, elem); - if constexpr (ranges::back_emplaceable) { - tmp.emplace_back(std::move(elem)); + if constexpr (container::back_pushable) { + container::append(tmp, std::move(elem)); } else { tmp.emplace(std::move(elem)); } @@ -231,7 +234,7 @@ struct basic_load_fn } template R> - requires ranges::fixed_array_writable + requires container::fixed_array static constexpr void operator()(ReaderT& rd, R& arr) { auto it = std::ranges::begin(arr); @@ -274,7 +277,7 @@ struct basic_load_fn mapped_type v{}; basic_load_fn{}(member_rd, v); - if constexpr (ranges::unique_mapping_container) { + if constexpr (container::unique_mapping_container) { if (!tmp.try_emplace(std::move(k), std::move(v)).second) { throw load_error{"object: duplicate key"}; } diff --git a/include/iris/marshal/serialize_traits.hpp b/include/iris/marshal/serialize_traits.hpp index ee098f5..3001eca 100644 --- a/include/iris/marshal/serialize_traits.hpp +++ b/include/iris/marshal/serialize_traits.hpp @@ -11,6 +11,7 @@ #include #include +#include #include @@ -313,16 +314,16 @@ consteval bool is_deserializable_impl() } else if constexpr (ranges::mapping_range) { return - ranges::mapping_container && + container::mapping_container && Format::template loadable_key> && loadable, Format> && loadable, Format>; } else if constexpr (std::ranges::input_range) { - if constexpr (ranges::growable_array_writable) { + if constexpr (container::growable_array) { return loadable, Format>; - } else if constexpr (ranges::fixed_array_writable) { + } else if constexpr (container::fixed_array) { return is_deserializable_impl, Format>(); } else { diff --git a/include/iris/ranges.hpp b/include/iris/ranges.hpp index fa057a5..897a465 100644 --- a/include/iris/ranges.hpp +++ b/include/iris/ranges.hpp @@ -5,8 +5,10 @@ #include // IWYU pragma: keep +#include // IWYU pragma: keep #include +#include // IWYU pragma: keep #include // IWYU pragma: export #include #include @@ -36,14 +38,6 @@ concept key_value_range = gettable<0, std::ranges::range_reference_t> && gettable<1, std::ranges::range_reference_t>; -template -struct dummy_key_value_range -{ - std::pair const* begin() const; - std::pair const* end() const; - ~dummy_key_value_range() = delete; -}; - template using range_key_t = std::remove_cvref_t>>; @@ -64,16 +58,6 @@ concept mapping_range = std::same_as, typename std::remove_cvref_t::key_type> && std::same_as, typename std::remove_cvref_t::mapped_type>; -template -struct dummy_mapping_range -{ - using key_type = K; - using mapped_type = V; - std::pair const* begin() const; - std::pair const* end() const; - ~dummy_mapping_range() = delete; -}; - // Thin view that adds the map-specific trait to the underlying range. // Can be used for making `key_value_range` model `mapping_range`. @@ -130,280 +114,26 @@ inline constexpr bool std::ranges::enable_borrowed_range; -namespace iris::ranges { - -template -concept mapping_container = - mapping_range && - std::default_initializable> && - requires( - std::remove_cvref_t& c, - typename std::remove_cvref_t::key_type k, - typename std::remove_cvref_t::mapped_type v - ) { - c.emplace(std::move(k), std::move(v)); - }; +namespace iris::ranges::dummy { template -struct dummy_mapping_container +struct key_value_range { - using key_type = K; - using mapped_type = V; std::pair const* begin() const; std::pair const* end() const; - - dummy_mapping_container() = default; - dummy_mapping_container(dummy_mapping_container const&) = delete; - dummy_mapping_container(dummy_mapping_container&&) = delete; - dummy_mapping_container& operator=(dummy_mapping_container const&) = delete; - dummy_mapping_container& operator=(dummy_mapping_container&&) = delete; - - void emplace(K&&, V&&); + ~key_value_range() = delete; }; -template -concept unique_mapping_container = - mapping_container && - requires( - std::remove_cvref_t& c, - typename std::remove_cvref_t::key_type k, - typename std::remove_cvref_t::mapped_type v - ) { - { c.try_emplace(std::move(k), std::move(v)).second } -> std::convertible_to; - { c.insert_or_assign(std::move(k), std::move(v)).second } -> std::convertible_to; - }; - template -struct dummy_unique_mapping_container : dummy_mapping_container +struct mapping_range { using key_type = K; using mapped_type = V; std::pair const* begin() const; std::pair const* end() const; - - using dummy_mapping_container::dummy_mapping_container; - - std::pair const*, bool> try_emplace(K&&, V&&); - std::pair const*, bool> insert_or_assign(K&&, V&&); -}; - - -template -concept default_back_emplaceable = requires(T c) { - std::forward(c).emplace_back(); -}; - -template -concept back_emplaceable = - requires(T c, std::ranges::range_value_t> v) { - std::forward(c).emplace_back(std::move(v)); - } && - ( - !std::default_initializable>> || - default_back_emplaceable - ); - -template -concept default_front_emplaceable = requires(T c) { - std::forward(c).emplace_front(); -}; - -template -concept front_emplaceable = - requires(T c, std::ranges::range_value_t> v) { - std::forward(c).emplace_front(std::move(v)); - } && - ( - !std::default_initializable>> || - default_front_emplaceable - ); - -namespace detail { - -struct front_fn -{ - template - static constexpr bool has_front = requires(R&& r) { std::forward(r).front(); }; - - template - requires has_front - [[nodiscard]] static constexpr auto&& operator()(R&& r) noexcept - { - return std::forward(r).front(); - } - - template - requires (!has_front) - [[nodiscard]] static constexpr auto&& operator()(R&& r) noexcept - { - return *std::ranges::begin(std::forward(r)); - } -}; - -struct back_fn -{ - template - static constexpr bool has_back = requires(R&& r) { std::forward(r).back(); }; - - template - requires has_back - [[nodiscard]] static constexpr auto&& operator()(R&& r) noexcept - { - return std::forward(r).back(); - } - - template - requires (!has_back) - [[nodiscard]] static constexpr auto&& operator()(R&& r) noexcept - { - return *std::ranges::prev(std::ranges::end(std::forward(r))); - } -}; - -} // detail - -[[maybe_unused]] inline constexpr detail::front_fn front{}; -[[maybe_unused]] inline constexpr detail::back_fn back{}; - -namespace detail { - -struct emplace_front_ref_fn -{ - template - [[nodiscard]] static constexpr auto& operator()(ContainerT& cont, Args&&... args) - noexcept(noexcept(cont.emplace_front(std::forward(args)...))) - { - if constexpr (std::is_void_v(args)...))>) { - cont.emplace_front(std::forward(args)...); - return front(cont); - } else { - return cont.emplace_front(std::forward(args)...); - } - } -}; - -struct emplace_back_ref_fn -{ - template - [[nodiscard]] static constexpr auto& operator()(ContainerT& cont, Args&&... args) - noexcept(noexcept(cont.emplace_back(std::forward(args)...))) - { - if constexpr (std::is_void_v(args)...))>) { - cont.emplace_back(std::forward(args)...); - return back(cont); - } else { - return cont.emplace_back(std::forward(args)...); - } - } -}; - -} // detail - -[[maybe_unused]] inline constexpr detail::emplace_front_ref_fn emplace_front_ref{}; -[[maybe_unused]] inline constexpr detail::emplace_back_ref_fn emplace_back_ref{}; - - -namespace detail { - -struct weak_pop_front_fn -{ - template - static constexpr bool has_pop_front = requires (ContainerT& cont) { - cont.pop_front(); - }; - - template - requires has_pop_front - static constexpr void operator()(ContainerT& cont) - noexcept(noexcept(cont.pop_front())) - { - cont.pop_front(); - } - - template - requires (!has_pop_front) - static constexpr void operator()(ContainerT& cont) - noexcept(noexcept(cont.erase(std::ranges::begin(cont)))) - { - cont.erase(std::ranges::begin(cont)); - } + ~mapping_range() = delete; }; -struct weak_pop_back_fn -{ - template - static constexpr bool has_pop_back = requires (ContainerT& cont) { - cont.pop_back(); - }; - - template - requires has_pop_back - static constexpr void operator()(ContainerT& cont) - noexcept(noexcept(cont.pop_back())) - { - cont.pop_back(); - } - - template - requires (!has_pop_back) - static constexpr void operator()(ContainerT& cont) - noexcept(noexcept(cont.erase(std::ranges::prev(std::ranges::end(cont))))) - { - cont.erase(std::ranges::prev(std::ranges::end(cont))); - } -}; - -} // detail - -[[maybe_unused]] inline constexpr detail::weak_pop_front_fn weak_pop_front{}; -[[maybe_unused]] inline constexpr detail::weak_pop_back_fn weak_pop_back{}; - - -template -concept default_emplaceable = requires(T c) { - std::forward(c).emplace(); -}; - -template -concept emplaceable = - requires(T c, std::ranges::range_value_t> v) { - std::forward(c).emplace(std::move(v)); - } && - ( - !std::default_initializable>> || - default_emplaceable - ); - -template -concept growable_array_writable = back_emplaceable || emplaceable; - -template -struct dummy_growable_array -{ - T const* begin() const; - T const* end() const; - void emplace_back(T&&); - void emplace_back() requires std::default_initializable; -}; - -template -concept fixed_array_writable = - !growable_array_writable && - std::ranges::forward_range && - std::ranges::sized_range && - std::is_lvalue_reference_v> && - !std::is_const_v>> && - std::is_move_assignable_v>>; - -template -using dummy_fixed_array = T[1]; - - -template -concept container_compatible_range = - std::ranges::input_range && - std::convertible_to, ElemT>; - -} // iris::ranges +} // iris::ranges::dummy #endif diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp index cd45b28..46f2965 100644 --- a/include/iris/run_length_sequence.hpp +++ b/include/iris/run_length_sequence.hpp @@ -5,14 +5,17 @@ #include // IWYU pragma: keep #include +#include #include #include #include +#include #include #include +#include #include #include #include @@ -22,7 +25,7 @@ #include #include -#include +#include // IWYU pragma: keep #include namespace iris::detail { @@ -31,6 +34,13 @@ struct run_length_sequence_comp; namespace iris { +template +struct run_length_run_ref +{ + T const& value; + interval span; +}; + // A compressed bidirectional container that holds only one instance of `T` // per each adjacent equivalent elements. // @@ -73,7 +83,8 @@ namespace iris { template< class T, class IndexT = unsigned, - class RunContainerT = std::vector> + template class IndexedValuePairTT = indexed_value, + class RunContainerT = std::vector > class run_length_sequence { @@ -106,18 +117,14 @@ class run_length_sequence using iterator_concept = std::bidirectional_iterator_tag; using iterator_category = std::input_iterator_tag; // Our reference type is proxy, so this can only be input - using value_type = indexed_value< + using value_type = IndexedValuePairTT< IndexT, std::iter_value_t >; - using reference = indexed_value< + using reference = IndexedValuePairTT< IndexT, std::iter_reference_t >; - using rvalue_reference = indexed_value< - IndexT, - std::iter_rvalue_reference_t - >; using difference_type = run_length_sequence::difference_type; constexpr iterator_impl() noexcept = default; @@ -184,15 +191,6 @@ class run_length_sequence return static_cast(a.index()) - static_cast(b.index()); } - [[nodiscard]] friend constexpr auto iter_move(iterator_impl const& it) - noexcept(noexcept(std::ranges::iter_move(it.value_it_))) - { - return rvalue_reference{ - it.index(), - std::ranges::iter_move(it.value_it_) - }; - } - private: [[nodiscard]] constexpr IndexT index() const noexcept { @@ -214,9 +212,11 @@ class run_length_sequence using item_type = T; using index_type = IndexT; using run_container_type = RunContainerT; + using allocator_type = run_container_type::allocator_type; + using run_ref = run_length_run_ref; - using value_type = indexed_value; - using const_reference = indexed_value; + using value_type = IndexedValuePairTT; + using const_reference = IndexedValuePairTT; using reference = const_reference; using const_iterator = iterator_impl; @@ -224,17 +224,33 @@ class run_length_sequence using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - constexpr run_length_sequence() = default; - template R> - constexpr run_length_sequence(std::from_range_t, R&& r) + constexpr explicit run_length_sequence(allocator_type const& alloc) noexcept + : runs_(alloc) + {} + + template R> + constexpr run_length_sequence(std::from_range_t, R&& r, allocator_type const& alloc = allocator_type()) + : runs_(alloc) { for (auto&& elem : r) { this->emplace_back(std::forward(elem)); } } + template It, std::sentinel_for Se> + constexpr run_length_sequence(It it, Se se, allocator_type const& alloc = allocator_type()) + : runs_(alloc) + { + for (; it != se; ++it) { + this->emplace_back(*it); + } + } + + constexpr run_length_sequence(std::initializer_list il, allocator_type const& alloc = allocator_type()) + : run_length_sequence(il.begin(), il.end(), alloc) + {} [[nodiscard]] constexpr const_iterator begin() const noexcept { check_range_concepts(); return const_iterator{std::ranges::begin(runs_), offsets_.begin()}; } [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } @@ -273,12 +289,6 @@ class run_length_sequence return static_cast(offsets_.back()); } - [[nodiscard]] constexpr size_type run_count() const noexcept - { - static_assert(std::ranges::sized_range); - return static_cast(std::ranges::size(runs_)); - } - constexpr void clear() noexcept requires requires(RunContainerT& runs) { runs.clear(); } { @@ -291,12 +301,12 @@ class run_length_sequence [[nodiscard]] constexpr const_reference front() const noexcept IRIS_LIFETIMEBOUND { assert(!this->empty()); - return {static_cast(0u), ranges::front(runs_)}; + return {static_cast(0u), container::front(runs_)}; } [[nodiscard]] constexpr const_reference back() const noexcept IRIS_LIFETIMEBOUND { assert(!this->empty()); - return {static_cast(offsets_.back() - static_cast(1u)), ranges::back(runs_)}; + return {static_cast(offsets_.back() - static_cast(1u)), container::back(runs_)}; } template @@ -317,15 +327,15 @@ class run_length_sequence throwf("run_length_sequence capacity exceeded"); } - if (ranges::back(std::as_const(runs_)) == std::as_const(value)) { + if (container::back(std::as_const(runs_)) == std::as_const(value)) { // Equivalent element already exists; no need to insert. - return {offsets_.back()++, ranges::back(runs_)}; + return {offsets_.back()++, container::back(runs_)}; } // Need to insert new element auto const new_pos = offsets_.back(); offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; - auto& elem = ranges::emplace_back_ref(runs_, std::forward(value)); + auto& elem = container::append_return(runs_, std::forward(value)); ofs_insertion_guard.clear(); return {new_pos, elem}; } @@ -348,8 +358,8 @@ class run_length_sequence if (offsets_.back() == max_size()) { throwf("run_length_sequence capacity exceeded"); } - if constexpr (requires { ranges::weak_pop_back(runs_); }) { - auto& elem = ranges::emplace_back_ref(runs_, std::forward(args)...); + if constexpr (requires { container::erase_back(runs_); }) { + auto& elem = container::append_return(runs_, std::forward(args)...); [[maybe_unused]] elem_insertion_guard elem_insertion_guard{this}; auto const prev_it = std::ranges::prev(std::ranges::end(runs_), 2); @@ -357,8 +367,8 @@ class run_length_sequence if (std::as_const(*prev_it) == std::as_const(elem)) { // Equivalent element already exists; no need to insert. elem_insertion_guard.clear(); - ranges::weak_pop_back(runs_); - return {offsets_.back()++, ranges::back(runs_)}; + container::erase_back(runs_); + return {offsets_.back()++, container::back(runs_)}; } // Need to insert new element @@ -369,21 +379,127 @@ class run_length_sequence } else { T temp(std::forward(args)...); - if (ranges::back(std::as_const(runs_)) == std::as_const(temp)) { + if (container::back(std::as_const(runs_)) == std::as_const(temp)) { // Equivalent element already exists; no need to insert. - return {offsets_.back()++, ranges::back(runs_)}; + return {offsets_.back()++, container::back(runs_)}; } // Need to insert new element auto const new_pos = offsets_.back(); offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; - auto& elem = ranges::emplace_back_ref(runs_, std::move(temp)); + auto& elem = container::append_return(runs_, std::move(temp)); ofs_insertion_guard.clear(); return {new_pos, elem}; } } } + // Offers heterogeneous comparison benefit + template + requires + (!std::same_as, T>) && + std::constructible_from + constexpr const_reference push_back(U&& value_like) IRIS_LIFETIMEBOUND + { + return this->emplace_back(std::forward(value_like)); + } + constexpr const_reference push_back(T const& value) IRIS_LIFETIMEBOUND + { + return this->emplace_back(value); + } + constexpr const_reference push_back(T&& value) IRIS_LIFETIMEBOUND + { + return this->emplace_back(std::move(value)); + } + + constexpr void pop_back() + noexcept(noexcept(container::erase_back(runs_))) + requires requires(RunContainerT& runs) { container::erase_back(runs); } + { + static_assert(req::Cpp17Destructible); + IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD + assert(!this->empty()); + assert(offsets_.size() >= 2); + + auto const n = offsets_.size(); + if (offsets_[n - 1] - offsets_[n - 2] == static_cast(1u)) { + container::erase_back(runs_); + offsets_.pop_back(); + if (offsets_.size() == 1) { + offsets_.clear(); // remove sentinel + } + + } else { + --offsets_.back(); + } + } + + constexpr void shrink_to_fit() + { + if constexpr (container::has_shrink_to_fit) { + runs_.shrink_to_fit(); + } + offsets_.shrink_to_fit(); + } + + template R> + constexpr void append_range(R&& r) + { + for (auto it = std::ranges::begin(r); it != std::ranges::end(r); ++it) { + this->emplace_back(*it); + } + } + + // --------------------------------------------------------------------- + + [[nodiscard]] constexpr RunContainerT const& runs() const noexcept + { + return runs_; + } + + [[nodiscard]] constexpr size_type run_count() const noexcept + { + static_assert(std::ranges::sized_range); + return static_cast(std::ranges::size(runs_)); + } + + [[nodiscard]] constexpr auto run_view() const noexcept + { + check_range_concepts(); + return std::views::zip_transform( + [](T const& value, auto const& bounds) noexcept -> run_ref { + return {value, interval{std::get<0>(bounds), std::get<1>(bounds)}}; + }, + runs_, offsets_ | std::views::pairwise + ); + } + + // --------------------------------------------------------------------- + + // Returns an iterator to the element at logical position `pos`, or `end()` + // when `pos >= size()`. + // + // Complexity: O(log run_count()) for locating the run, plus the cost of + // advancing an iterator of `RunContainerT` by the run index (O(1) when + // `RunContainerT` is random access). + [[nodiscard]] constexpr const_iterator nth(IndexT pos) const noexcept + { + check_range_concepts(); + if (pos >= this->size()) return this->end(); + + auto const ofs_next = std::ranges::upper_bound(offsets_, pos); + assert(ofs_next != offsets_.begin()); + assert(ofs_next != offsets_.end()); + auto const ofs_it = std::ranges::prev(ofs_next); + + auto const run_index = ofs_it - offsets_.begin(); + auto const value_it = std::ranges::next(std::ranges::begin(runs_), run_index); + + return const_iterator{value_it, ofs_it, static_cast(pos - *ofs_it)}; + } + + // --------------------------------------------------------------------- + constexpr void swap(run_length_sequence& other) noexcept(std::is_nothrow_swappable_v && std::is_nothrow_swappable_v) { @@ -400,9 +516,13 @@ class run_length_sequence static_assert(std::same_as, T&>); static_assert(std::same_as, T const&>); + static_assert(container::back_pushable); + static_assert(container::back_accessible); + static_assert(container::front_accessible); + static_assert(requires (RunContainerT& runs) { - { runs.back() } -> std::same_as; - { runs.front() } -> std::same_as; + { container::back(runs) } -> std::same_as; + { container::front(runs) } -> std::same_as; }); } @@ -413,7 +533,7 @@ class run_length_sequence [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; offsets_.emplace_back(static_cast(0u)); offsets_.emplace_back(static_cast(1u)); // sentinel - auto& elem = ranges::emplace_back_ref(runs_, std::forward(args)...); + auto& elem = container::append_return(runs_, std::forward(args)...); ofs_insertion_guard.clear(); return {static_cast(0u), elem}; } @@ -460,7 +580,7 @@ class run_length_sequence constexpr ~elem_insertion_guard() noexcept { if (!self_) return; - ranges::weak_pop_back(self_->runs_); + container::erase_back(self_->runs_); } private: @@ -511,8 +631,11 @@ class run_length_sequence #undef IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD }; -template -constexpr void swap(run_length_sequence& a, run_length_sequence& b) +template class IndexedValuePairTT, class RunContainerT> +constexpr void swap( + run_length_sequence& a, + run_length_sequence& b +) noexcept(noexcept(a.swap(b))) { a.swap(b); @@ -523,8 +646,12 @@ namespace detail { struct run_length_sequence_comp { - template - [[nodiscard]] static constexpr bool equals(run_length_sequence const& a, run_length_sequence const& b) + template class IndexedValuePairTT, class RunContainerT> + [[nodiscard]] static constexpr bool + equals( + run_length_sequence const& a, + run_length_sequence const& b + ) noexcept(noexcept(std::declval() == std::declval())) { static_assert(std::equality_comparable); @@ -537,8 +664,12 @@ struct run_length_sequence_comp } // detail -template -[[nodiscard]] constexpr bool operator==(run_length_sequence const& a, run_length_sequence const& b) +template class IndexedValuePairTT, class RunContainerT> +[[nodiscard]] constexpr bool +operator==( + run_length_sequence const& a, + run_length_sequence const& b +) noexcept(noexcept(detail::run_length_sequence_comp::equals(a, b))) { return detail::run_length_sequence_comp::equals(a, b); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bce894f..ead5c33 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -183,6 +183,7 @@ if(PROJECT_IS_TOP_LEVEL) core type_traits ranges + container_traits stdint enum indirect diff --git a/test/container_traits.cpp b/test/container_traits.cpp new file mode 100644 index 0000000..4fdfb88 --- /dev/null +++ b/test/container_traits.cpp @@ -0,0 +1,800 @@ +// SPDX-License-Identifier: MIT + +#include "iris_test.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include // TODO +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; +using namespace std::string_literals; + +TEST_CASE("container: map") +{ + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(!iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(!iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(!iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>>); + STATIC_CHECK(!iris::ranges::mapping_range>>); + STATIC_CHECK(!iris::container::mapping_container>>); + + STATIC_CHECK(iris::ranges::key_value_range>>); + STATIC_CHECK(!iris::ranges::mapping_range>>); + STATIC_CHECK(!iris::container::mapping_container>>); +} + +TEST_CASE("container: compatible range/iterator") +{ + static_assert(!std::is_constructible_v, std::from_range_t, std::vector>); + STATIC_CHECK(iris::container::compatible_iterator::const_iterator, std::string>); + + static_assert(std::is_constructible_v, std::vector::const_iterator, std::vector::const_iterator>); + STATIC_CHECK(!iris::container::compatible_range, std::string>); +} + +TEST_CASE("container: member traits") +{ + STATIC_CHECK(iris::container::back_pushable>); + STATIC_CHECK(iris::container::back_pushable&>); + STATIC_CHECK(!iris::container::back_pushable const&>); + STATIC_CHECK(iris::container::growable_array>); + STATIC_CHECK(iris::container::growable_array&>); + STATIC_CHECK(!iris::container::growable_array const&>); + STATIC_CHECK(!iris::container::fixed_array>); + + STATIC_CHECK(iris::container::appendable>); + STATIC_CHECK(iris::container::appendable&>); + STATIC_CHECK(!iris::container::appendable const&>); + STATIC_CHECK(iris::container::growable_array>); + STATIC_CHECK(iris::container::growable_array&>); + STATIC_CHECK(!iris::container::growable_array const&>); + STATIC_CHECK(!iris::container::fixed_array>); + + STATIC_CHECK(iris::container::fixed_array); + STATIC_CHECK(iris::container::fixed_array); + STATIC_CHECK(!iris::container::fixed_array); + STATIC_CHECK(!iris::container::growable_array); + + STATIC_CHECK(iris::container::fixed_array>); + STATIC_CHECK(iris::container::fixed_array&>); + STATIC_CHECK(!iris::container::fixed_array const&>); + STATIC_CHECK(!iris::container::growable_array>); + + STATIC_CHECK(iris::container::fixed_array>); + STATIC_CHECK(iris::container::fixed_array&>); + STATIC_CHECK(iris::container::fixed_array const&>); + STATIC_CHECK(!iris::container::growable_array>); + + STATIC_CHECK(!iris::container::fixed_array>); + STATIC_CHECK(!iris::container::fixed_array&>); + STATIC_CHECK(!iris::container::fixed_array const&>); + STATIC_CHECK(!iris::container::growable_array>); + + STATIC_CHECK(iris::container::fixed_array>); + STATIC_CHECK(iris::container::fixed_array&>); + STATIC_CHECK(iris::container::fixed_array const&>); + STATIC_CHECK(!iris::container::growable_array>); +} + +TEST_CASE("container: dummy types") +{ + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(!iris::ranges::mapping_range>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(!iris::container::mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(!iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::container::growable_array>); + STATIC_CHECK(iris::container::fixed_array>); +} + + +struct recording_item +{ + [[nodiscard]] static recording_item make_moved(recording_item const& other) + { + return recording_item{"move[" + other.log + "]"}; + } + + recording_item() + { + log += "default"; + } + + recording_item(int a) + { + log += std::format("{}", a); + } + + recording_item(int a, int b) + { + log += std::format("({},{})", a, b); + } + + recording_item(recording_item const& other) + { + log = "copy[" + other.log + "]"; + } + + recording_item(recording_item&& other) // NOLINT(cppcoreguidelines-noexcept-move-operations, performance-noexcept-move-constructor) + { + log = "move[" + other.log + "]"; + } + + recording_item& operator=(recording_item const&) = delete; + recording_item& operator=(recording_item&&) = default; + + [[nodiscard]] bool operator==(recording_item const&) const = default; + + std::string log; + +private: + explicit recording_item(std::string override_log) + : log(std::move(override_log)) + {} +}; + +template<> +struct std::formatter : std::formatter +{ + auto format(recording_item const& rec, auto& ctx) const + { + return std::formatter::format(rec.log, ctx); + } +}; + +template +struct recording_container +{ + recording_container() + { + elems.reserve(100); + } + + auto begin() const + { + if constexpr (IsFrontTest) { + return std::reverse_iterator{elems.end()}; + } else { + return elems.begin(); + } + } + auto begin() + { + if constexpr (IsFrontTest) { + return std::reverse_iterator{elems.end()}; + } else { + return elems.begin(); + } + } + auto end() const + { + if constexpr (IsFrontTest) { + return std::reverse_iterator{elems.begin()}; + } else { + return elems.end(); + } + } + auto end() + { + if constexpr (IsFrontTest) { + return std::reverse_iterator{elems.begin()}; + } else { + return elems.end(); + } + } + + template + decltype(auto) emplace_front(Args&&... args) requires IsFrontTest && HasEmplaceMeow + { + log += " emplace_front"; + return elems.emplace_back(std::forward(args)...); + } + + void push_front(T const& value) requires IsFrontTest && HasPushMeow + { + log += " push_front"; + elems.push_back(value); + } + + void push_front(T&& value) requires IsFrontTest && HasPushMeow + { + log += " push_front"; + elems.push_back(std::move(value)); + } + + template + decltype(auto) emplace_back(Args&&... args) requires (!IsFrontTest) && HasEmplaceMeow + { + log += " emplace_back"; + return elems.emplace_back(std::forward(args)...); + } + + void push_back(T const& value) requires (!IsFrontTest) && HasPushMeow + { + log += " push_back"; + elems.push_back(value); + } + + void push_back(T&& value) requires (!IsFrontTest) && HasPushMeow + { + log += " push_back"; + elems.push_back(std::move(value)); + } + + template + auto emplace(auto it, Args&&... args) requires HasEmplace + { + log += " emplace"; + if constexpr (IsFrontTest) { + return elems.emplace(it.base(), std::forward(args)...); + } else { + return elems.emplace(it, std::forward(args)...); + } + } + + auto insert(auto it, T const& value) requires HasInsert + { + log += " insert"; + if constexpr (IsFrontTest) { + return elems.insert(it.base(), value); + } else { + return elems.insert(it, value); + } + } + + auto insert(auto it, T&& value) requires HasInsert + { + log += " insert"; + if constexpr (IsFrontTest) { + return elems.insert(it.base(), std::move(value)); + } else { + return elems.insert(it, std::move(value)); + } + } + + [[nodiscard]] std::string elems_str() const + { + return elems | std::views::transform([](T const& value) { + return std::format("{}", value); + }) | std::views::join_with("|"sv) | std::ranges::to(); + } + + std::vector elems; + std::string log; +}; + +static_assert(std::ranges::range>); +static_assert(std::same_as>, int&>); +static_assert(std::same_as const>, int const&>); + +TEST_CASE("container: prepend") +{ + { + // emplace_front + push_front + emplace + insert + using Cont = recording_container; + + STATIC_CHECK(iris::container::front_pushable); + STATIC_CHECK(iris::container::front_pushable); + STATIC_CHECK(iris::container::front_pushable); + + STATIC_CHECK(iris::container::default_prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::prepend(cont); cont_log_expected += " emplace_front"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 1); cont_log_expected += " emplace_front"; elems_str_expected += "|1"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 2, 3); cont_log_expected += " emplace_front"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::prepend_return(cont); cont_log_expected += " emplace_front"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::prepend_return(cont, 4); cont_log_expected += " emplace_front"; elems_str_expected += "|4"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{4}); + } + { + auto&& elem = iris::container::prepend_return(cont, 5, 6); cont_log_expected += " emplace_front"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // push_front + emplace + insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + + STATIC_CHECK(iris::container::default_prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::prepend(cont); cont_log_expected += " emplace"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 1); cont_log_expected += " push_front"; elems_str_expected += "|move[1]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 2, 3); cont_log_expected += " emplace"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::prepend_return(cont); cont_log_expected += " emplace"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::prepend_return(cont, 4); cont_log_expected += " push_front"; elems_str_expected += "|move[4]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{4})); + } + { + auto&& elem = iris::container::prepend_return(cont, 5, 6); cont_log_expected += " emplace"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // emplace + insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + + STATIC_CHECK(iris::container::default_prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::prepend(cont); cont_log_expected += " emplace"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 1); cont_log_expected += " emplace"; elems_str_expected += "|1"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 2, 3); cont_log_expected += " emplace"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::prepend_return(cont); cont_log_expected += " emplace"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::prepend_return(cont, 4); cont_log_expected += " emplace"; elems_str_expected += "|4"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{4}); + } + { + auto&& elem = iris::container::prepend_return(cont, 5, 6); cont_log_expected += " emplace"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + + STATIC_CHECK(iris::container::default_prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(!iris::container::prependable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(!std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::prepend(cont); cont_log_expected += " insert"; elems_str_expected += "move[default]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 1); cont_log_expected += " insert"; elems_str_expected += "|move[1]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::prepend_return(cont); cont_log_expected += " insert"; elems_str_expected += "|move[default]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{})); + } + { + auto&& elem = iris::container::prepend_return(cont, 4); cont_log_expected += " insert"; elems_str_expected += "|move[4]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{4})); + } + } + { + // no accessor + using Cont = recording_container; + + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + + STATIC_CHECK(!iris::container::default_prependable); + STATIC_CHECK(!iris::container::prependable); + STATIC_CHECK(!iris::container::prependable); + STATIC_CHECK(!iris::container::prependable); + + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + } +} + +TEST_CASE("container: append") +{ + { + // emplace_back + push_back + emplace + insert + using Cont = recording_container; + + STATIC_CHECK(iris::container::back_pushable); + STATIC_CHECK(iris::container::back_pushable); + STATIC_CHECK(iris::container::back_pushable); + + STATIC_CHECK(iris::container::default_appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::append(cont); cont_log_expected += " emplace_back"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 1); cont_log_expected += " emplace_back"; elems_str_expected += "|1"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 2, 3); cont_log_expected += " emplace_back"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::append_return(cont); cont_log_expected += " emplace_back"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::append_return(cont, 4); cont_log_expected += " emplace_back"; elems_str_expected += "|4"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{4}); + } + { + auto&& elem = iris::container::append_return(cont, 5, 6); cont_log_expected += " emplace_back"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // push_back + emplace + insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + + STATIC_CHECK(iris::container::default_appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::append(cont); cont_log_expected += " emplace"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 1); cont_log_expected += " push_back"; elems_str_expected += "|move[1]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 2, 3); cont_log_expected += " emplace"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::append_return(cont); cont_log_expected += " emplace"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::append_return(cont, 4); cont_log_expected += " push_back"; elems_str_expected += "|move[4]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{4})); + } + { + auto&& elem = iris::container::append_return(cont, 5, 6); cont_log_expected += " emplace"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // emplace + insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + + STATIC_CHECK(iris::container::default_appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::append(cont); cont_log_expected += " emplace"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 1); cont_log_expected += " emplace"; elems_str_expected += "|1"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 2, 3); cont_log_expected += " emplace"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::append_return(cont); cont_log_expected += " emplace"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::append_return(cont, 4); cont_log_expected += " emplace"; elems_str_expected += "|4"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{4}); + } + { + auto&& elem = iris::container::append_return(cont, 5, 6); cont_log_expected += " emplace"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + + STATIC_CHECK(iris::container::default_appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(!iris::container::appendable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(!std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::append(cont); cont_log_expected += " insert"; elems_str_expected += "move[default]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 1); cont_log_expected += " insert"; elems_str_expected += "|move[1]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::append_return(cont); cont_log_expected += " insert"; elems_str_expected += "|move[default]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{})); + } + { + auto&& elem = iris::container::append_return(cont, 4); cont_log_expected += " insert"; elems_str_expected += "|move[4]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{4})); + } + } + { + // no accessor + using Cont = recording_container; + + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + + STATIC_CHECK(!iris::container::default_appendable); + STATIC_CHECK(!iris::container::appendable); + STATIC_CHECK(!iris::container::appendable); + STATIC_CHECK(!iris::container::appendable); + + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + } +} diff --git a/test/ranges.cpp b/test/ranges.cpp index 08d69ba..fda8194 100644 --- a/test/ranges.cpp +++ b/test/ranges.cpp @@ -2,16 +2,15 @@ #include "iris_test.hpp" +#include #include +#include +#include #include #include -#include -#include #include #include -#include -#include template struct FakeNonRangeMap @@ -64,48 +63,10 @@ struct FakeRangeMapWithNonTupleKey value_type const* end() const; }; -TEST_CASE("ranges: map related traits") +TEST_CASE("ranges: as_map") { using iris::ranges::as_map; - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(!iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(!iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(!iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>>); - STATIC_CHECK(!iris::ranges::mapping_range>>); - STATIC_CHECK(!iris::ranges::mapping_container>>); - - STATIC_CHECK(iris::ranges::key_value_range>>); - STATIC_CHECK(!iris::ranges::mapping_range>>); - STATIC_CHECK(!iris::ranges::mapping_container>>); - // --------------------------------------------------- // Viewed maps { @@ -117,19 +78,19 @@ TEST_CASE("ranges: map related traits") STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); // Map& STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); // Map const& STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); } { using PairVec = std::vector>; @@ -141,7 +102,7 @@ TEST_CASE("ranges: map related traits") STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); // Map& STATIC_CHECK(iris::ranges::key_value_range())>); @@ -150,7 +111,7 @@ TEST_CASE("ranges: map related traits") STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); // Map const& STATIC_CHECK(iris::ranges::key_value_range())>); @@ -159,7 +120,7 @@ TEST_CASE("ranges: map related traits") STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); } } @@ -184,69 +145,5 @@ TEST_CASE("ranges: map related traits") STATIC_CHECK(!iris::ranges::key_value_range>); } -TEST_CASE("ranges: container related traits") -{ - STATIC_CHECK(iris::ranges::back_emplaceable>); - STATIC_CHECK(iris::ranges::back_emplaceable&>); - STATIC_CHECK(!iris::ranges::back_emplaceable const&>); - STATIC_CHECK(iris::ranges::growable_array_writable>); - STATIC_CHECK(iris::ranges::growable_array_writable&>); - STATIC_CHECK(!iris::ranges::growable_array_writable const&>); - STATIC_CHECK(!iris::ranges::fixed_array_writable>); - - STATIC_CHECK(iris::ranges::emplaceable>); - STATIC_CHECK(iris::ranges::emplaceable&>); - STATIC_CHECK(!iris::ranges::emplaceable const&>); - STATIC_CHECK(iris::ranges::growable_array_writable>); - STATIC_CHECK(iris::ranges::growable_array_writable&>); - STATIC_CHECK(!iris::ranges::growable_array_writable const&>); - STATIC_CHECK(!iris::ranges::fixed_array_writable>); - - STATIC_CHECK(iris::ranges::fixed_array_writable); - STATIC_CHECK(iris::ranges::fixed_array_writable); - STATIC_CHECK(!iris::ranges::fixed_array_writable); - STATIC_CHECK(!iris::ranges::growable_array_writable); - - STATIC_CHECK(iris::ranges::fixed_array_writable>); - STATIC_CHECK(iris::ranges::fixed_array_writable&>); - STATIC_CHECK(!iris::ranges::fixed_array_writable const&>); - STATIC_CHECK(!iris::ranges::growable_array_writable>); - - STATIC_CHECK(iris::ranges::fixed_array_writable>); - STATIC_CHECK(iris::ranges::fixed_array_writable&>); - STATIC_CHECK(iris::ranges::fixed_array_writable const&>); - STATIC_CHECK(!iris::ranges::growable_array_writable>); - - STATIC_CHECK(!iris::ranges::fixed_array_writable>); - STATIC_CHECK(!iris::ranges::fixed_array_writable&>); - STATIC_CHECK(!iris::ranges::fixed_array_writable const&>); - STATIC_CHECK(!iris::ranges::growable_array_writable>); - - STATIC_CHECK(iris::ranges::fixed_array_writable>); - STATIC_CHECK(iris::ranges::fixed_array_writable&>); - STATIC_CHECK(iris::ranges::fixed_array_writable const&>); - STATIC_CHECK(!iris::ranges::growable_array_writable>); -} - -TEST_CASE("ranges: dummy types") -{ - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(!iris::ranges::mapping_range>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(!iris::ranges::mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(!iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::growable_array_writable>); - STATIC_CHECK(iris::ranges::fixed_array_writable>); -} +// Test cases involving containers must go to `container_traits.cpp` +// Test cases for dummy ranges / dummy containers must go to `container_traits.cpp` diff --git a/test/run_length_sequence.cpp b/test/run_length_sequence.cpp index de52533..830cc75 100644 --- a/test/run_length_sequence.cpp +++ b/test/run_length_sequence.cpp @@ -12,10 +12,11 @@ #include #include -TEST_CASE("run_length_sequence") -{ - using namespace std::string_view_literals; +using namespace std::string_view_literals; +using namespace std::string_literals; +TEST_CASE("run_length_sequence: traits") +{ using RLS = iris::run_length_sequence; STATIC_CHECK(std::same_as); STATIC_CHECK(std::same_as); @@ -62,16 +63,183 @@ TEST_CASE("run_length_sequence") } } -TEST_CASE("run_length_sequence: misc") +TEST_CASE("run_length_sequence: construction") +{ + constexpr auto expected_input = std::array{2, 3, 3, 4, 2, 2, 3, 4, 4}; + auto const expected_runs = std::vector{2, 3, 4, 2, 3, 4}; + + // std::from_range + { + iris::run_length_sequence seq{std::from_range, expected_input}; + auto const expected_elems = std::views::zip(std::views::iota(0u), expected_input) | std::ranges::to(); + auto const actual_elems = seq | std::ranges::to(); + CHECK(expected_elems == actual_elems); + CHECK(seq.run_count() == expected_runs.size()); + CHECK(seq.runs() == expected_runs); + + auto const run_view = seq.run_view(); + auto it = run_view.begin(); + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 2); + CHECK(run_ref.span.equals({0, 1})); + } + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 3); + CHECK(run_ref.span.equals({1, 3})); + } + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 4); + CHECK(run_ref.span.equals({3, 4})); + } + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 2); + CHECK(run_ref.span.equals({4, 6})); + } + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 3); + CHECK(run_ref.span.equals({6, 7})); + } + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 4); + CHECK(run_ref.span.equals({7, 9})); + } + REQUIRE(it == run_view.end()); + } + + // it, se + { + iris::run_length_sequence seq{expected_input.begin(), expected_input.end()}; + auto const expected_elems = std::views::zip(std::views::iota(0u), expected_input) | std::ranges::to(); + auto const actual_elems = seq | std::ranges::to(); + CHECK(expected_elems == actual_elems); + CHECK(seq.run_count() == expected_runs.size()); + CHECK(seq.runs() == expected_runs); + } + + // initializer list + { + iris::run_length_sequence seq{2, 3, 3, 4, 2, 2, 3, 4, 4}; + auto const expected_elems = std::views::zip(std::views::iota(0u), expected_input) | std::ranges::to(); + auto const actual_elems = seq | std::ranges::to(); + CHECK(expected_elems == actual_elems); + CHECK(seq.run_count() == expected_runs.size()); + CHECK(seq.runs() == expected_runs); + } + + // initializer list + append_range + { + iris::run_length_sequence seq{2, 3, 3, 4, 2}; + seq.append_range(std::array{2, 3, 4, 4}); + auto const expected_elems = std::views::zip(std::views::iota(0u), expected_input) | std::ranges::to(); + auto const actual_elems = seq | std::ranges::to(); + CHECK(expected_elems == actual_elems); + CHECK(seq.run_count() == expected_runs.size()); + CHECK(seq.runs() == expected_runs); + } +} + +TEST_CASE("run_length_sequence: emplace_back / push_back / pop_back") { + using RLS = iris::run_length_sequence; + + // emplace_back(char array) + { + RLS seq; + auto&& elem = seq.emplace_back("foo"); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + // emplace_back(string_view) + { + RLS seq; + auto&& elem = seq.emplace_back("foo"sv); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + // emplace_back(string) + { + RLS seq; + auto&& elem = seq.emplace_back("foo"s); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + + // push_back(char array) + { + RLS seq; + auto&& elem = seq.push_back("foo"); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + // push_back(string_view) + { + RLS seq; + auto&& elem = seq.push_back("foo"sv); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + // push_back(string) + { + RLS seq; + auto&& elem = seq.push_back("foo"s); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + // push_back(initializer_list) { - // TODO - //iris::run_length_sequence seq{std::from_range, std::initializer_list{2, 3, 3, 4, 2, 2, 3, 4, 4}}; - //constexpr auto expected_arr = std::array{2, 3, 4}; - //auto expected = std::views::zip(std::views::iota(0u), expected_arr); - //auto aaa = seq | std::ranges::to(); - //auto bbb = expected | std::ranges::to(); - //CHECK(aaa == bbb); + constexpr auto str_arr = std::array{'a', 'b', 'c'}; + + RLS seq; + auto&& elem = seq.push_back({str_arr.begin(), str_arr.end()}); // this cannot be resolved by `emplace_back` + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "abc"); + + seq.pop_back(); + CHECK(seq.empty()); } } @@ -79,10 +247,11 @@ TEST_CASE("run_length_sequence: sequential insertion") { using RLS = iris::run_length_sequence; RLS seq; + CHECK(seq == seq); CHECK(seq.empty()); CHECK(seq.size() == 0); // NOLINT(readability-container-size-empty) CHECK(seq.begin() == seq.end()); - CHECK(seq == seq); + CHECK(seq.nth(0) == seq.end()); { using std::swap; @@ -99,6 +268,12 @@ TEST_CASE("run_length_sequence: sequential insertion") CHECK(elem.value == 1.0); CHECK(seq.front() == elem); CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + CHECK(seq.nth(1) == seq.end()); auto it = seq.begin(); @@ -118,6 +293,17 @@ TEST_CASE("run_length_sequence: sequential insertion") CHECK(elem.index == 1); CHECK(elem.value == 1.0); CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + { + auto nth1 = *seq.nth(1); + CHECK(nth1.index == 1); + CHECK(nth1.value == 1.0); + } + CHECK(seq.nth(2) == seq.end()); auto it = seq.begin(); @@ -140,6 +326,22 @@ TEST_CASE("run_length_sequence: sequential insertion") CHECK(elem.index == 2); CHECK(elem.value == 1.1); CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + { + auto nth1 = *seq.nth(1); + CHECK(nth1.index == 1); + CHECK(nth1.value == 1.0); + } + { + auto nth2 = *seq.nth(2); + CHECK(nth2.index == 2); + CHECK(nth2.value == 1.1); + } + CHECK(seq.nth(3) == seq.end()); auto it = seq.begin(); @@ -167,6 +369,27 @@ TEST_CASE("run_length_sequence: sequential insertion") CHECK(elem.index == 3); CHECK(elem.value == 1.1); CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + { + auto nth1 = *seq.nth(1); + CHECK(nth1.index == 1); + CHECK(nth1.value == 1.0); + } + { + auto nth2 = *seq.nth(2); + CHECK(nth2.index == 2); + CHECK(nth2.value == 1.1); + } + { + auto nth3 = *seq.nth(3); + CHECK(nth3.index == 3); + CHECK(nth3.value == 1.1); + } + CHECK(seq.nth(4) == seq.end()); auto it = seq.begin(); @@ -199,6 +422,32 @@ TEST_CASE("run_length_sequence: sequential insertion") CHECK(elem.index == 4); CHECK(elem.value == 1.1); CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + { + auto nth1 = *seq.nth(1); + CHECK(nth1.index == 1); + CHECK(nth1.value == 1.0); + } + { + auto nth2 = *seq.nth(2); + CHECK(nth2.index == 2); + CHECK(nth2.value == 1.1); + } + { + auto nth3 = *seq.nth(3); + CHECK(nth3.index == 3); + CHECK(nth3.value == 1.1); + } + { + auto nth4 = *seq.nth(4); + CHECK(nth4.index == 4); + CHECK(nth4.value == 1.1); + } + CHECK(seq.nth(5) == seq.end()); auto it = seq.begin(); @@ -236,6 +485,37 @@ TEST_CASE("run_length_sequence: sequential insertion") CHECK(elem.index == 5); CHECK(elem.value == 1.2); CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + { + auto nth1 = *seq.nth(1); + CHECK(nth1.index == 1); + CHECK(nth1.value == 1.0); + } + { + auto nth2 = *seq.nth(2); + CHECK(nth2.index == 2); + CHECK(nth2.value == 1.1); + } + { + auto nth3 = *seq.nth(3); + CHECK(nth3.index == 3); + CHECK(nth3.value == 1.1); + } + { + auto nth4 = *seq.nth(4); + CHECK(nth4.index == 4); + CHECK(nth4.value == 1.1); + } + { + auto nth5 = *seq.nth(5); + CHECK(nth5.index == 5); + CHECK(nth5.value == 1.2); + } + CHECK(seq.nth(6) == seq.end()); auto it = seq.begin();