From 5df5c129059c6bbb6be557a010b911b21767502c Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sat, 15 Aug 2026 22:38:44 +0200 Subject: kstd: improve flat map iterator --- libs/kstd/kstd/flat_map.hpp | 84 ++++++++++- libs/kstd/kstd/flat_map.tests.cpp | 310 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 389 insertions(+), 5 deletions(-) diff --git a/libs/kstd/kstd/flat_map.hpp b/libs/kstd/kstd/flat_map.hpp index af269c6a..b9b05366 100644 --- a/libs/kstd/kstd/flat_map.hpp +++ b/libs/kstd/kstd/flat_map.hpp @@ -81,10 +81,13 @@ namespace kstd template struct flat_map_iterator { - using iterator_category = std::random_access_iterator_tag; + using iterator_category = std::input_iterator_tag; + using iterator_concept = std::random_access_iterator_tag; using value_type = std::pair; using difference_type = std::ptrdiff_t; using reference = std::pair &>; + using rvalue_reference = std::pair, + std::conditional_t>; struct pointer { @@ -104,7 +107,7 @@ namespace kstd , m_index{it.m_index} {} - [[nodiscard]] constexpr auto operator*() const noexcept -> reference + [[nodiscard]] constexpr auto operator*() const -> reference { if (m_index >= m_containers->keys.size()) { @@ -113,12 +116,12 @@ namespace kstd return {m_containers->keys[m_index], m_containers->values[m_index]}; } - [[nodiscard]] constexpr auto operator->() const noexcept -> pointer + [[nodiscard]] constexpr auto operator->() const -> pointer { return {operator*()}; } - [[nodiscard]] constexpr auto operator[](difference_type offset) const noexcept -> reference + [[nodiscard]] constexpr auto operator[](difference_type offset) const -> reference { return *(*this + offset); } @@ -191,6 +194,24 @@ namespace kstd [[nodiscard]] constexpr auto friend operator<=>(flat_map_iterator const &, flat_map_iterator const &) noexcept = default; + [[nodiscard]] constexpr auto friend iter_move(flat_map_iterator const & it) -> rvalue_reference + { + if (it.m_index >= it.m_containers->keys.size()) + { + os::panic("[kstd::flat_map] Iterator out of range"); + } + + return rvalue_reference{static_cast(it.m_containers->keys[it.m_index]), + static_cast(it.m_containers->values[it.m_index])}; + } + + constexpr auto friend iter_swap(flat_map_iterator const & lhs, flat_map_iterator const & rhs) -> void + requires(!IsConst) + { + std::ranges::swap(lhs.m_containers->keys[lhs.m_index], rhs.m_containers->keys[rhs.m_index]); + std::ranges::swap(lhs.m_containers->values[lhs.m_index], rhs.m_containers->values[rhs.m_index]); + } + private: friend struct flat_map; friend flat_map_iterator; @@ -557,7 +578,8 @@ namespace kstd //! Try to insert a new key-value pair into the map. //! //! @param args Arguments to use for constructing the key-value pair. - //! @return A pair of an iterator to the inserted element and a boolean indicating whether the insertion took place. + //! @return A pair of an iterator to the inserted element and a boolean indicating whether the insertion took + //! place. template auto emplace(Args &&... args) -> std::pair requires std::constructible_from @@ -745,6 +767,58 @@ namespace kstd return find(key) != cend(); } + //! Get an iterator to the first element not less than the given key. + //! + //! @param key The key to look for. + //! @return An iterator to the first element not less than the given key iff. such an element exists, the end + //! iterator otherwise. + [[nodiscard]] constexpr auto lower_bound(key_type const & key) noexcept -> iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + auto offset = static_cast(std::ranges::distance(m_containers.keys.begin(), found)); + return iterator{m_containers, offset}; + } + + //! Get an iterator to the first element not less than the given key. + //! + //! @param key The key to look for. + //! @return An iterator to the first element not less than the given key iff. such an element exists, the end + //! iterator otherwise. + [[nodiscard]] constexpr auto lower_bound(key_type const & key) const noexcept -> const_iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + auto offset = static_cast(std::ranges::distance(m_containers.keys.begin(), found)); + return const_iterator{m_containers, offset}; + } + + //! Get an iterator to the first element not less than the given key. + //! + //! @param key The key to look for. + //! @return An iterator to the first element not less than the given key iff. such an element exists, the end + //! iterator otherwise. + template + requires requires { typename key_compare::is_transparent; } + [[nodiscard]] constexpr auto lower_bound(K const & key) noexcept -> iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + auto offset = static_cast(std::ranges::distance(m_containers.keys.begin(), found)); + return iterator{m_containers, offset}; + } + + //! Get an iterator to the first element not less than the given key. + //! + //! @param key The key to look for. + //! @return An iterator to the first element not less than the given key iff. such an element exists, the end + //! iterator otherwise. + template + requires requires { typename key_compare::is_transparent; } + [[nodiscard]] constexpr auto lower_bound(K const & key) const noexcept -> const_iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + auto offset = static_cast(std::ranges::distance(m_containers.keys.begin(), found)); + return const_iterator{m_containers, offset}; + } + //! Get the key comparator of this flat map. //! //! @return The key comparator of this flat map. diff --git a/libs/kstd/kstd/flat_map.tests.cpp b/libs/kstd/kstd/flat_map.tests.cpp index 16b97678..f3384b90 100644 --- a/libs/kstd/kstd/flat_map.tests.cpp +++ b/libs/kstd/kstd/flat_map.tests.cpp @@ -7,6 +7,7 @@ #include #include +#include #include SCENARIO("Flat Map initialization and construction", "[flat_map]") @@ -235,6 +236,215 @@ SCENARIO("Flat Map iterators", "[flat_map]") map.emplace(2, 20); map.emplace(3, 30); + WHEN("getting the begin iterator on a non-const map") + { + auto it = map.begin(); + auto copy = it; + + THEN("it returns an iterator to the first element") + { + REQUIRE(it != map.end()); + REQUIRE((*it).first == 1); + REQUIRE((*it).second == 10); + } + + THEN("its type is flat_map::iterator") + { + STATIC_REQUIRE(std::is_same_v::iterator>); + } + + THEN("it can be prefix-incremented to point to the next element") + { + ++it; + REQUIRE(it != map.end()); + REQUIRE((*it).first == 2); + REQUIRE((*it).second == 20); + } + + THEN("it can be postfix-incremented to point to the next element") + { + auto old = it++; + REQUIRE(old == copy); + + REQUIRE(it != map.end()); + REQUIRE((*it).first == 2); + REQUIRE((*it).second == 20); + } + + THEN("it can be addition-incremented") + { + it += 2; + REQUIRE(it != map.end()); + REQUIRE((*it).first == 3); + REQUIRE((*it).second == 30); + } + + THEN("an offset can be added to it") + { + auto new_it = it + 2; + REQUIRE(new_it != map.end()); + REQUIRE((*new_it).first == 3); + REQUIRE((*new_it).second == 30); + } + + THEN("it can be added to an offset") + { + auto new_it = 2 + it; + REQUIRE(new_it != map.end()); + REQUIRE((*new_it).first == 3); + REQUIRE((*new_it).second == 30); + } + + THEN("it can be random access indexed") + { + auto value = it[1]; + REQUIRE(value.first == 2); + REQUIRE(value.second == 20); + } + + THEN("out-of-bounds random access panics") + { + REQUIRE_THROWS_AS(it[3], kstd::tests::os_panic); + } + + THEN("it can be prefix-decremented") + { + --it; + REQUIRE_THROWS_AS(*it, kstd::tests::os_panic); + } + + THEN("it can be postfix-decremented") + { + auto old = it--; + REQUIRE_THROWS_AS(*it, kstd::tests::os_panic); + REQUIRE(old == copy); + } + + THEN("it can be written through") + { + it->second = 100; + REQUIRE(map.at(1) == 100); + } + + THEN("its distance to another iterator can be calculated") + { + auto other = map.begin(); + ++other; + + REQUIRE(other - it == 1); + } + + THEN("it can be compared to another iterator") + { + REQUIRE(it < map.end()); + REQUIRE(it <= map.end()); + REQUIRE(!(it > map.end())); + REQUIRE(!(it >= map.end())); + } + } + + WHEN("getting the begin iterator on a const map") + { + auto const & cmap = map; + auto it = cmap.begin(); + auto copy = it; + + THEN("it returns an iterator to the first element") + { + REQUIRE(it != cmap.end()); + REQUIRE((*it).first == 1); + REQUIRE((*it).second == 10); + } + + THEN("its type is flat_map::const_iterator") + { + STATIC_REQUIRE(std::is_same_v::const_iterator>); + } + + THEN("it can be prefix-incremented to point to the next element") + { + ++it; + REQUIRE(it != cmap.end()); + REQUIRE((*it).first == 2); + REQUIRE((*it).second == 20); + } + + THEN("it can be postfix-incremented to point to the next element") + { + auto old = it++; + REQUIRE(old == copy); + + REQUIRE(it != cmap.end()); + REQUIRE((*it).first == 2); + REQUIRE((*it).second == 20); + } + + THEN("it can be addition-incremented") + { + it += 2; + REQUIRE(it != cmap.end()); + REQUIRE((*it).first == 3); + REQUIRE((*it).second == 30); + } + + THEN("an offset can be added to it") + { + auto new_it = it + 2; + REQUIRE(new_it != cmap.end()); + REQUIRE((*new_it).first == 3); + REQUIRE((*new_it).second == 30); + } + + THEN("it can be added to an offset") + { + auto new_it = 2 + it; + REQUIRE(new_it != map.end()); + REQUIRE((*new_it).first == 3); + REQUIRE((*new_it).second == 30); + } + + THEN("it can be random access indexed") + { + auto value = it[1]; + REQUIRE(value.first == 2); + REQUIRE(value.second == 20); + } + + THEN("out-of-bounds random access panics") + { + REQUIRE_THROWS_AS(it[3], kstd::tests::os_panic); + } + + THEN("it can be prefix-decremented") + { + --it; + REQUIRE_THROWS_AS(*it, kstd::tests::os_panic); + } + + THEN("it can be postfix-decremented") + { + auto old = it--; + REQUIRE_THROWS_AS(*it, kstd::tests::os_panic); + REQUIRE(old == copy); + } + + THEN("its distance to another iterator can be calculated") + { + auto other = cmap.begin(); + ++other; + + REQUIRE(other - it == 1); + } + + THEN("it can be compared to another iterator") + { + REQUIRE(it < cmap.end()); + REQUIRE(it <= cmap.end()); + REQUIRE(!(it > cmap.end())); + REQUIRE(!(it >= cmap.end())); + } + } + WHEN("using forward iterators") { THEN("they navigate the elements in the correct forward order") @@ -253,6 +463,7 @@ SCENARIO("Flat Map iterators", "[flat_map]") ++it; REQUIRE(it == map.end()); + REQUIRE_THROWS_AS(*it, kstd::tests::os_panic); } THEN("const forward iterators provide correct access") @@ -271,6 +482,7 @@ SCENARIO("Flat Map iterators", "[flat_map]") ++it; REQUIRE(it == map.cend()); + REQUIRE_THROWS_AS(*it, kstd::tests::os_panic); } THEN("structured bindings evaluate correctly") @@ -305,6 +517,7 @@ SCENARIO("Flat Map iterators", "[flat_map]") ++it; REQUIRE(it == map.rend()); + REQUIRE_THROWS_AS(*it, kstd::tests::os_panic); } THEN("const reverse iterators provide correct access") @@ -323,6 +536,20 @@ SCENARIO("Flat Map iterators", "[flat_map]") ++it; REQUIRE(it == map.crend()); + REQUIRE_THROWS_AS(*it, kstd::tests::os_panic); + } + + THEN("structured bindings evaluate correctly") + { + auto it = map.crbegin(); + + auto [key, value] = *it; + + REQUIRE(key == 3); + REQUIRE(value == 30); + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); } } } @@ -412,3 +639,86 @@ SCENARIO("Flat Map heterogeneous element access", "[flat_map]") } } } + +SCENARIO("Flat Map queries", "[flat_map]") +{ + GIVEN("A populated Flat Map") + { + auto map = kstd::flat_map{}; + map.emplace('b', 10); + map.emplace('c', 20); + map.emplace('d', 30); + + WHEN("finding the lower bound of a key present in the map") + { + auto it = map.lower_bound('c'); + + THEN("it returns an iterator to the element") + { + REQUIRE(it != map.end()); + REQUIRE((*it).first == 'c'); + REQUIRE((*it).second == 20); + } + } + + WHEN("finding the lower bound of a key larger than any in the map") + { + auto it = map.lower_bound('e'); + + THEN("it returns an iterator to the end of the map") + { + REQUIRE(it == map.end()); + } + } + + WHEN("finding the lower bound of a key smaller than any in the map") + { + auto it = map.lower_bound('a'); + + THEN("it returns an iterator to the beginning of the map") + { + REQUIRE(it == map.begin()); + } + } + } + + GIVEN("A populated Flat Map with a transparent comparator") + { + auto map = kstd::flat_map>{}; + map.emplace("b", 10); + map.emplace("c", 20); + map.emplace("d", 30); + + WHEN("finding the lower bound of a key present in the map") + { + auto it = map.lower_bound("c"); + + THEN("it returns an iterator to the element") + { + REQUIRE(it != map.end()); + REQUIRE((*it).first == "c"); + REQUIRE((*it).second == 20); + } + } + + WHEN("finding the lower bound of a key larger than any in the map") + { + auto it = map.lower_bound("e"); + + THEN("it returns an iterator to the end of the map") + { + REQUIRE(it == map.end()); + } + } + + WHEN("finding the lower bound of a key smaller than any in the map") + { + auto it = map.lower_bound("a"); + + THEN("it returns an iterator to the beginning of the map") + { + REQUIRE(it == map.begin()); + } + } + } +} -- cgit v1.2.3