diff --git a/BUILD.bazel b/BUILD.bazel index 1a7a1eef..9758957f 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -142,11 +142,13 @@ cc_library( hdrs = ["include/fixed_containers/fixed_deque.hpp"], includes = ["include"], deps = [ + ":algorithm", + ":circular_integer_range_iterator", ":consteval_compare", ":iterator_utils", ":optional_storage", ":preconditions", - ":random_access_iterator_transformer", + ":random_access_iterator", ":source_location", ":string_literal", ":type_name", diff --git a/include/fixed_containers/fixed_deque.hpp b/include/fixed_containers/fixed_deque.hpp index 9a519d69..c90d4fd0 100644 --- a/include/fixed_containers/fixed_deque.hpp +++ b/include/fixed_containers/fixed_deque.hpp @@ -1,17 +1,21 @@ #pragma once +#include "fixed_containers/algorithm.hpp" +#include "fixed_containers/circular_integer_range_iterator.hpp" #include "fixed_containers/consteval_compare.hpp" #include "fixed_containers/iterator_utils.hpp" #include "fixed_containers/optional_storage.hpp" #include "fixed_containers/preconditions.hpp" -#include "fixed_containers/random_access_iterator_transformer.hpp" +#include "fixed_containers/random_access_iterator.hpp" #include "fixed_containers/source_location.hpp" #include "fixed_containers/string_literal.hpp" #include "fixed_containers/type_name.hpp" +#include #include #include #include +#include namespace fixed_containers::fixed_deque_customize { @@ -71,23 +75,21 @@ class FixedDeque static_assert(IsNotReference, "References are not allowed"); static_assert(consteval_compare::equal); using Checking = CheckingType; + using Array = std::array; + using CircularIndexEntryProvider = + CircularIntegerRangeEntryProvider>; + static constexpr StartingIntegerAndDistance FULL_STARTING_INDEX_AND_SIZE{ + .start = 0, .distance = MAXIMUM_SIZE}; + static constexpr IntegerRange FULL_RANGE = FULL_STARTING_INDEX_AND_SIZE.to_range(); - struct Mapper + static constexpr std::size_t increment_index_with_wraparound(std::size_t i, std::size_t n = 1) { - constexpr T& operator()(OptionalT& opt_storage) const noexcept { return opt_storage.value; } - constexpr const T& operator()(const OptionalT& opt_storage) const noexcept - { - return opt_storage.value; - } - }; - - template - using IteratorImpl = RandomAccessIteratorTransformer< - typename std::array::const_iterator, - typename std::array::iterator, - Mapper, - Mapper, - CONSTNESS>; + return circular_integer_detail::increment_index_with_wraparound(FULL_RANGE, i, n).integer; + } + static constexpr std::size_t decrement_index_with_wraparound(std::size_t i, std::size_t n = 1) + { + return circular_integer_detail::decrement_index_with_wraparound(FULL_RANGE, i, n).integer; + } public: using value_type = T; @@ -97,10 +99,91 @@ class FixedDeque using const_pointer = const T*; using reference = T&; using const_reference = const T&; - using const_iterator = IteratorImpl; - using iterator = IteratorImpl; - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; + +private: + template + class ReferenceProvider + { + friend class ReferenceProvider; + using ConstOrMutableArray = std::conditional_t; + + private: + ConstOrMutableArray* array_; + CircularIndexEntryProvider current_index_; + + private: + constexpr ReferenceProvider(ConstOrMutableArray* const array, + CircularIndexEntryProvider current_index) noexcept + : array_{array} + , current_index_{current_index} + { + } + + public: + constexpr ReferenceProvider() noexcept + : ReferenceProvider{nullptr, FULL_STARTING_INDEX_AND_SIZE} + { + } + + constexpr ReferenceProvider( + ConstOrMutableArray* const array, + const StartingIntegerAndDistance& starting_index_and_distance) noexcept + : ReferenceProvider(array, CircularIndexEntryProvider{{}, starting_index_and_distance}) + { + } + + // https://github.com/llvm/llvm-project/issues/62555 + template + constexpr ReferenceProvider(const ReferenceProvider& m) noexcept + requires(IS_CONST and !IS_CONST_2) + : ReferenceProvider{m.array_, m.current_index_} + { + } + + constexpr void advance(const std::size_t n) noexcept { current_index_.advance(n); } + constexpr void recede(const std::size_t n) noexcept { current_index_.recede(n); } + + constexpr std::conditional_t get( + difference_type n) const noexcept + { + const std::size_t i = current_index_.get(n); + return optional_storage_detail::get(array_->at(i)); + } + + template + constexpr bool operator==(const ReferenceProvider& other) const noexcept + { + return array_ == other.array_ && current_index_ == other.current_index_; + } + template + constexpr auto operator<=>(const ReferenceProvider& other) const noexcept + { + assert(array_ == other.array_); + return current_index_ <=> other.current_index_; + } + + template + constexpr std::ptrdiff_t operator-(const ReferenceProvider& other) const + { + assert(array_ == other.array_); + return static_cast(current_index_ - other.current_index_); + } + }; + + template + using Iterator = RandomAccessIterator, + ReferenceProvider, + CONSTNESS, + DIRECTION>; + +public: + using const_iterator = + Iterator; + using iterator = Iterator; + using const_reverse_iterator = + Iterator; + using reverse_iterator = + Iterator; private: static constexpr void check_target_size(size_type target_size, @@ -113,14 +196,12 @@ class FixedDeque } private: - std::array array_; - std::size_t index_i_; - std::size_t index_j_; + Array array_; + StartingIntegerAndDistance starting_index_and_size_; public: constexpr FixedDeque() noexcept - : index_i_(0) - , index_j_(0) + : starting_index_and_size_{MAXIMUM_SIZE - 1, 0} // Don't initialize the array { } @@ -139,7 +220,7 @@ class FixedDeque std_transition::source_location::current()) noexcept : FixedDeque() { - insert(end(), first, last, loc); + insert(create_iterator(starting_index_and_size_), first, last, loc); } constexpr void resize( @@ -158,14 +239,14 @@ class FixedDeque // Reinitialize the new members if we are enlarging while (size() < count) { - place_at(size(), v); + place_at(end_index(), v); increment_size(); } // Destroy extras if we are making it smaller. while (size() > count) { decrement_size(); - destroy_at(size()); + destroy_at(end_index()); } } @@ -216,22 +297,21 @@ class FixedDeque const std::size_t read_start = this->index_of(last); const std::size_t write_start = this->index_of(first); - std::size_t entry_count_to_move = size() - read_start; - const std::size_t entry_count_to_remove = read_start - write_start; + const auto entry_count_to_move = static_cast(end() - last); + const auto entry_count_to_remove = static_cast(last - first); // Clean out the gap - destroy_index_range(write_start, write_start + entry_count_to_remove); + destroy_range({.start = write_start, .distance = entry_count_to_remove}); + + auto read_start_it = create_iterator({.start = read_start, .distance = 0}); + auto read_end_it = create_iterator({.start = read_start, .distance = entry_count_to_move}); + auto write_start_it = create_iterator({.start = write_start, .distance = 0}); // Do the move - for (std::size_t i = 0; i < entry_count_to_move; ++i) - { - place_at(write_start + i, - std::move(optional_storage_detail::get(array_unchecked_at(read_start + i)))); - destroy_at(read_start + i); - } + std::move(read_start_it, read_end_it, write_start_it); decrement_size(entry_count_to_remove); - return iterator{this->begin() + static_cast(write_start)}; + return create_iterator({.start = write_start, .distance = 0}); } constexpr iterator erase(const_iterator it, const std_transition::source_location& loc = @@ -240,28 +320,47 @@ class FixedDeque return erase(it, it + 1, loc); } - constexpr iterator begin() noexcept { return create_iterator(0); } + constexpr iterator begin() noexcept + { + return create_iterator({.start = starting_index_and_size_.start, .distance = 0}); + } constexpr const_iterator begin() const noexcept { return cbegin(); } - constexpr const_iterator cbegin() const noexcept { return create_const_iterator(0); } - constexpr iterator end() noexcept { return create_iterator(size()); } + constexpr const_iterator cbegin() const noexcept + { + return create_const_iterator({.start = starting_index_and_size_.start, .distance = 0}); + } + constexpr iterator end() noexcept { return create_iterator(starting_index_and_size_); } constexpr const_iterator end() const noexcept { return cend(); } - constexpr const_iterator cend() const noexcept { return create_const_iterator(size()); } + constexpr const_iterator cend() const noexcept + { + return create_const_iterator(starting_index_and_size_); + } - constexpr reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } + constexpr reverse_iterator rbegin() noexcept + { + return create_reverse_iterator(starting_index_and_size_); + } constexpr const_reverse_iterator rbegin() const noexcept { return crbegin(); } constexpr const_reverse_iterator crbegin() const noexcept { - return const_reverse_iterator(cend()); + return create_const_reverse_iterator(starting_index_and_size_); + } + constexpr reverse_iterator rend() noexcept + { + return create_reverse_iterator({.start = starting_index_and_size_.start, .distance = 0}); } - constexpr reverse_iterator rend() noexcept { return reverse_iterator(begin()); } constexpr const_reverse_iterator rend() const noexcept { return crend(); } constexpr const_reverse_iterator crend() const noexcept { - return const_reverse_iterator(cbegin()); + return create_const_reverse_iterator( + {.start = starting_index_and_size_.start, .distance = 0}); } [[nodiscard]] constexpr std::size_t max_size() const noexcept { return MAXIMUM_SIZE; } - [[nodiscard]] constexpr std::size_t size() const noexcept { return index_j_ - index_i_; } + [[nodiscard]] constexpr std::size_t size() const noexcept + { + return starting_index_and_size_.distance; + } [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } template @@ -282,7 +381,7 @@ class FixedDeque for (std::size_t i = 0; i < this->size(); i++) { - if (this->unchecked_at(i) != other.at(i)) + if (this->at(i) != other.at(i)) { return false; } @@ -298,11 +397,11 @@ class FixedDeque const std::size_t min_size = (std::min)(this->size(), other.size()); for (std::size_t i = 0; i < min_size; i++) { - if (unchecked_at(i) < other.at(i)) + if (at(i) < other.at(i)) { return OrderingType::less; } - if (unchecked_at(i) > other.at(i)) + if (at(i) > other.at(i)) { return OrderingType::greater; } @@ -332,7 +431,9 @@ class FixedDeque { Checking::out_of_range(i, size(), loc); } - return array_[i].value; + assert(starting_index_and_size_.to_range().contains(front_index() + i)); + const std::size_t adjusted_i = increment_index_with_wraparound(front_index(), i); + return unchecked_at(adjusted_i); } constexpr const_reference at(size_type i, const std_transition::source_location& loc = @@ -342,32 +443,34 @@ class FixedDeque { Checking::out_of_range(i, size(), loc); } - return array_[i].value; + assert(starting_index_and_size_.to_range().contains(front_index() + i)); + const std::size_t adjusted_i = increment_index_with_wraparound(front_index(), i); + return unchecked_at(adjusted_i); } constexpr reference front( const std_transition::source_location& loc = std_transition::source_location::current()) { check_not_empty(loc); - return array_[0].value; + return unchecked_at(front_index()); } constexpr const_reference front(const std_transition::source_location& loc = std_transition::source_location::current()) const { check_not_empty(loc); - return array_[0].value; + return unchecked_at(front_index()); } constexpr reference back( const std_transition::source_location& loc = std_transition::source_location::current()) { check_not_empty(loc); - return array_[size() - 1].value; + return array_[back_index()].value; } constexpr const_reference back(const std_transition::source_location& loc = std_transition::source_location::current()) const { check_not_empty(loc); - return array_[size() - 1].value; + return array_[back_index()].value; } private: @@ -380,17 +483,15 @@ class FixedDeque const std::size_t n) { const std::size_t read_start = index_of(it); + const std::size_t value_count_to_move = + starting_index_and_size_.to_range().end_exclusive() - read_start; const std::size_t write_start = read_start + n; - const std::size_t value_count_to_move = size() - read_start; - const std::size_t read_end = read_start + value_count_to_move - 1; - const std::size_t write_end = write_start + value_count_to_move - 1; - - for (std::size_t i = 0; i < value_count_to_move; i++) - { - place_at(write_end - i, std::move(array_[read_end - i].value)); - destroy_at(read_end - i); - } + auto read_start_it = create_iterator({.start = read_start, .distance = 0}); + auto read_end_it = create_iterator({.start = read_start, .distance = value_count_to_move}); + auto write_end_it = + create_iterator({.start = write_start, .distance = value_count_to_move}); + algorithm::emplace_move_backward(read_start_it, read_end_it, write_end_it); increment_size(n); @@ -399,12 +500,12 @@ class FixedDeque constexpr void push_back_internal(const value_type& v) { - place_at(size(), v); + place_at(end_index(), v); increment_size(); } constexpr void push_back_internal(value_type&& v) { - place_at(size(), std::move(v)); + place_at(end_index(), std::move(v)); increment_size(); } @@ -420,11 +521,12 @@ class FixedDeque const std::size_t write_index = this->advance_all_after_iterator_by_n(it, entry_count_to_add); - for (std::size_t i = write_index; first != last; std::advance(first, 1), i++) + auto write_it = create_iterator({.start = write_index, .distance = 0}); + for (auto w_it = write_it; first != last; std::advance(first, 1), std::advance(w_it, 1)) { - place_at(i, *first); + std::construct_at(&*w_it, *first); } - return begin() + static_cast(write_index); + return write_it; } template @@ -434,53 +536,64 @@ class FixedDeque InputIt last, const std_transition::source_location& loc) { + const std::size_t original_size = size(); + // Place everything at the end of the vector - std::size_t new_size = size(); - for (; first != last && new_size < max_size(); ++first, ++new_size) + for (; first != last && size() < max_size(); ++first) { - place_at(new_size, *first); + push_back_internal(*first); } if (first != last) // Reached capacity { - // Count excess elements + std::size_t excess_element_count = 0; for (; first != last; ++first) { - new_size++; + excess_element_count++; } - Checking::length_error(new_size, loc); + Checking::length_error(MAXIMUM_SIZE + excess_element_count, loc); } // Rotate into the correct places const std::size_t write_index = this->index_of(it); + auto write_it = create_iterator({.start = write_index, .distance = 0}); std::rotate( - create_iterator(write_index), create_iterator(size()), create_iterator(new_size)); - index_j_ = (index_i_ + new_size) % MAXIMUM_SIZE; + {write_it}, + create_iterator({.start = starting_index_and_size_.start, .distance = original_size}), + create_iterator(starting_index_and_size_)); - return begin() + static_cast(write_index); + return write_it; } - constexpr iterator create_iterator(const std::size_t start_index) noexcept + constexpr iterator create_iterator( + const StartingIntegerAndDistance& start_and_distance) noexcept { - auto array_it = std::next(std::begin(array_), static_cast(start_index)); - return iterator{array_it, Mapper{}}; + return iterator{ReferenceProvider{&array_, start_and_distance}}; + } + constexpr const_iterator create_const_iterator( + const StartingIntegerAndDistance& start_and_distance) const noexcept + { + return const_iterator{ReferenceProvider{&array_, start_and_distance}}; } - constexpr const_iterator create_const_iterator(const std::size_t start_index) const noexcept + constexpr reverse_iterator create_reverse_iterator( + const StartingIntegerAndDistance& start_and_distance) noexcept { - auto array_it = std::next(std::begin(array_), static_cast(start_index)); - return const_iterator{array_it, Mapper{}}; + return reverse_iterator{ReferenceProvider{&array_, start_and_distance}}; } -private: - constexpr std::size_t index_of(iterator it) const + constexpr const_reverse_iterator create_const_reverse_iterator( + const StartingIntegerAndDistance& start_and_distance) const noexcept { - return static_cast(it - begin()); + return const_reverse_iterator{ReferenceProvider{&array_, start_and_distance}}; } + +private: constexpr std::size_t index_of(const_iterator it) const { - return static_cast(it - cbegin()); + return static_cast( + std::distance(create_const_iterator({.start = 0, .distance = 0}), it)); } constexpr void check_not_full(const std_transition::source_location& loc) const @@ -500,24 +613,39 @@ class FixedDeque // [WORKAROUND-1] - Needed by the non-trivially-copyable flavor of FixedDeque protected: + [[nodiscard]] constexpr std::size_t front_index() const + { + return starting_index_and_size_.start; + } + [[nodiscard]] constexpr std::size_t back_index() const + { + return decrement_index_with_wraparound(end_index(), 1); + } + [[nodiscard]] constexpr std::size_t end_index() const + { + return increment_index_with_wraparound(starting_index_and_size_.start, + starting_index_and_size_.distance); + } + constexpr void increment_size(const std::size_t n = 1) { - index_j_ = (index_j_ + n) % (MAXIMUM_SIZE + 1); + starting_index_and_size_.distance += n; } constexpr void decrement_size(const std::size_t n = 1) { - index_j_ = (index_j_ - n) % (MAXIMUM_SIZE + 1); + starting_index_and_size_.distance -= n; } + constexpr void set_size(const std::size_t size) { starting_index_and_size_.distance = size; } constexpr const OptionalT& array_unchecked_at(const std::size_t i) const { return array_[i]; } constexpr OptionalT& array_unchecked_at(const std::size_t i) { return array_[i]; } constexpr const T& unchecked_at(const std::size_t i) const { - return optional_storage_detail::get(array_[i]); + return optional_storage_detail::get(array_unchecked_at(i)); } constexpr T& unchecked_at(const std::size_t i) { - return optional_storage_detail::get(array_[i]); + return optional_storage_detail::get(array_unchecked_at(i)); } constexpr void destroy_at(std::size_t) @@ -530,16 +658,19 @@ class FixedDeque array_[i].value.~T(); } - constexpr void destroy_index_range(std::size_t, std::size_t) + constexpr void destroy_range(const StartingIntegerAndDistance&) requires TriviallyDestructible { } - constexpr void destroy_index_range(std::size_t from_inclusive, std::size_t to_exclusive) + constexpr void destroy_range(const StartingIntegerAndDistance& start_and_distance) requires NotTriviallyDestructible { - for (std::size_t i = from_inclusive; i < to_exclusive; i++) + auto start = create_iterator({.start = start_and_distance.start, .distance = 0}); + auto end = create_iterator(start_and_distance); + + for (auto it = start; it != end; it++) { - destroy_at(i); + destroy_at(*it); } } diff --git a/test/fixed_deque_test.cpp b/test/fixed_deque_test.cpp index ad89cfa6..08f0a3eb 100644 --- a/test/fixed_deque_test.cpp +++ b/test/fixed_deque_test.cpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace fixed_containers @@ -12,8 +13,6 @@ namespace fixed_containers namespace { void const_ref(const int&) {} -void const_span_ref(const std::span&) {} -void const_span_of_const_ref(const std::span&) {} } // namespace TEST(FixedDeque, DefaultConstructor) @@ -142,7 +141,6 @@ TEST(FixedDeque, BracketOperator) static_assert(v1.size() == 3); const_ref(v1[0]); - const_span_of_const_ref(v1); auto v2 = FixedDeque{0, 1, 2}; v2[1] = 901; @@ -177,7 +175,6 @@ TEST(FixedDeque, At) static_assert(v1.size() == 3); const_ref(v1.at(0)); - const_span_of_const_ref(v1); auto v2 = FixedDeque{0, 1, 2}; v2.at(1) = 901; @@ -247,8 +244,6 @@ TEST(FixedDeque, Equality) const_ref(v1[0]); const_ref(v2[0]); - const_span_of_const_ref(v1); - const_span_of_const_ref(v2); } TEST(FixedDeque, Comparison) @@ -257,8 +252,8 @@ TEST(FixedDeque, Comparison) // Equal size, left < right { - std::vector left{1, 2, 3}; - std::vector right{1, 2, 4}; + std::deque left{1, 2, 3}; + std::deque right{1, 2, 4}; ASSERT_TRUE(left < right); ASSERT_TRUE(left <= right); @@ -283,8 +278,8 @@ TEST(FixedDeque, Comparison) // Left has fewer elements, left > right { - std::vector left{1, 5}; - std::vector right{1, 2, 4}; + std::deque left{1, 5}; + std::deque right{1, 2, 4}; ASSERT_TRUE(!(left < right)); ASSERT_TRUE(!(left <= right)); @@ -309,8 +304,8 @@ TEST(FixedDeque, Comparison) // Right has fewer elements, left < right { - std::vector left{1, 2, 3}; - std::vector right{1, 5}; + std::deque left{1, 2, 3}; + std::deque right{1, 5}; ASSERT_TRUE(left < right); ASSERT_TRUE(left <= right); @@ -335,8 +330,8 @@ TEST(FixedDeque, Comparison) // Left has one additional element { - std::vector left{1, 2, 3}; - std::vector right{1, 2}; + std::deque left{1, 2, 3}; + std::deque right{1, 2}; ASSERT_TRUE(!(left < right)); ASSERT_TRUE(!(left <= right)); @@ -361,8 +356,8 @@ TEST(FixedDeque, Comparison) // Right has one additional element { - std::vector left{1, 2}; - std::vector right{1, 2, 3}; + std::deque left{1, 2}; + std::deque right{1, 2, 3}; ASSERT_TRUE(left < right); ASSERT_TRUE(left <= right); @@ -391,7 +386,7 @@ TEST(FixedDeque, IteratorAssignment) FixedDeque::iterator it; // Default construction FixedDeque::const_iterator const_it; // Default construction - const_it = it; // Non-const needs to assignable to const + const_it = it; // Non-const needs to be assignable to const } TEST(FixedDeque, TrivialIterators) @@ -471,7 +466,7 @@ TEST(FixedDeque, NonTrivialIterators) { } int i_; - std::vector v_; // unused, but makes S non-trivial + std::deque v_; // unused, but makes S non-trivial }; static_assert(!std::is_trivially_copyable_v); { @@ -706,8 +701,6 @@ TEST(FixedDeque, IterationBasic) EXPECT_EQ(ctr, 6); const_ref(v[0]); - const_span_ref(v); - const_span_of_const_ref(v); } TEST(FixedDeque, InsertIterator) @@ -858,16 +851,18 @@ TEST(FixedDeque, Erase_Empty) // Don't Expect Death v1.erase(std::remove_if(v1.begin(), v1.end(), [&](const auto&) { return true; }), v1.end()); - EXPECT_DEATH(v1.erase(v1.begin()), ""); + // Don't Expect Death + v1.erase(v1.begin()); } { - std::vector v1{}; + std::deque v1{}; // Don't Expect Death v1.erase(std::remove_if(v1.begin(), v1.end(), [&](const auto&) { return true; }), v1.end()); - EXPECT_DEATH(v1.erase(v1.begin()), ""); + // Don't Expect Death + v1.erase(v1.begin()); } }