From 940a115a7479a639a8197106d5c765e398a39047 Mon Sep 17 00:00:00 2001 From: Grey Golla Date: Wed, 17 Jan 2024 18:24:21 -0800 Subject: [PATCH] First pass of unified basic map interface + complex adapter layer --- .../bidirectional_iterator.hpp | 5 + include/fixed_containers/fixed_map.hpp | 12 +- .../fixed_containers/fixed_map_adapter.hpp | 469 +++++ .../fixed_containers/fixed_red_black_tree.hpp | 37 +- .../fixed_red_black_tree_types.hpp | 2 +- include/fixed_containers/new_fixed_map.hpp | 107 + .../unified_interface_red_black_tree.hpp | 201 ++ test/general_map_interface.hpp | 36 + test/new_fixed_map_test.cpp | 1761 +++++++++++++++++ 9 files changed, 2607 insertions(+), 23 deletions(-) create mode 100644 include/fixed_containers/fixed_map_adapter.hpp create mode 100644 include/fixed_containers/new_fixed_map.hpp create mode 100644 include/fixed_containers/unified_interface_red_black_tree.hpp create mode 100644 test/general_map_interface.hpp create mode 100644 test/new_fixed_map_test.cpp diff --git a/include/fixed_containers/bidirectional_iterator.hpp b/include/fixed_containers/bidirectional_iterator.hpp index 8b6ff5e1..a335beb4 100644 --- a/include/fixed_containers/bidirectional_iterator.hpp +++ b/include/fixed_containers/bidirectional_iterator.hpp @@ -150,6 +150,11 @@ class BidirectionalIterator return out; } + constexpr const EntryProvider& reference_provider() const + { + return reference_provider_; + } + private: constexpr void advance() noexcept { diff --git a/include/fixed_containers/fixed_map.hpp b/include/fixed_containers/fixed_map.hpp index fb6a117e..3b68a214 100644 --- a/include/fixed_containers/fixed_map.hpp +++ b/include/fixed_containers/fixed_map.hpp @@ -92,7 +92,7 @@ class FixedMap { if (current_index_ == NULL_INDEX) { - current_index_ = tree_->index_of_min_at(); + current_index_ = tree_->index_of_min_at().i; } else { @@ -104,7 +104,7 @@ class FixedMap { if (current_index_ == MAXIMUM_SIZE) { - current_index_ = tree_->index_of_max_at(); + current_index_ = tree_->index_of_max_at().i; } else { @@ -240,11 +240,11 @@ class FixedMap constexpr const_iterator cbegin() const noexcept { - return create_const_iterator(tree().index_of_min_at()); + return create_const_iterator(tree().index_of_min_at().i); } constexpr const_iterator cend() const noexcept { return create_const_iterator(MAXIMUM_SIZE); } constexpr const_iterator begin() const noexcept { return cbegin(); } - constexpr iterator begin() noexcept { return create_iterator(tree().index_of_min_at()); } + constexpr iterator begin() noexcept { return create_iterator(tree().index_of_min_at().i); } constexpr const_iterator end() const noexcept { return cend(); } constexpr iterator end() noexcept { return create_iterator(MAXIMUM_SIZE); } @@ -256,12 +256,12 @@ class FixedMap } constexpr reverse_iterator rend() noexcept { - return create_reverse_iterator(tree().index_of_min_at()); + return create_reverse_iterator(tree().index_of_min_at().i); } constexpr const_reverse_iterator rend() const noexcept { return crend(); } constexpr const_reverse_iterator crend() const noexcept { - return create_const_reverse_iterator(tree().index_of_min_at()); + return create_const_reverse_iterator(tree().index_of_min_at().i); } [[nodiscard]] constexpr std::size_t size() const noexcept { return tree().size(); } diff --git a/include/fixed_containers/fixed_map_adapter.hpp b/include/fixed_containers/fixed_map_adapter.hpp new file mode 100644 index 00000000..c3f798d3 --- /dev/null +++ b/include/fixed_containers/fixed_map_adapter.hpp @@ -0,0 +1,469 @@ +#pragma once + +#include "fixed_containers/bidirectional_iterator.hpp" +#include "fixed_containers/erase_if.hpp" + +namespace fixed_containers +{ + +template +class FixedMapAdapter +{ +public: + using key_type = K; + using mapped_type = V; + using value_type = std::pair; + using reference = std::pair; + using const_reference = std::pair; + using pointer = std::add_pointer_t; + using const_pointer = std::add_pointer_t; + +private: + using TableIndex = typename TableImpl::OpaqueIndexType; + + template + using PairProvider = typename TableImpl::template PairProvider; + + template + using Iterator = + BidirectionalIterator, PairProvider, CONSTNESS, DIRECTION>; +public: + using const_iterator = + Iterator; + using iterator = Iterator; + using const_reverse_iterator = + Iterator; + using reverse_iterator = + Iterator; + + using size_type = std::size_t; + using difference_type = ptrdiff_t; + +public: + static constexpr size_type max_size() noexcept { return TableImpl::CAPACITY; } + +public: + TableImpl IMPLEMENTATION_DETAIL_DO_NOT_USE_table_; + +private: + constexpr TableImpl& table() { + return IMPLEMENTATION_DETAIL_DO_NOT_USE_table_; + } + + constexpr const TableImpl& table() const { + return IMPLEMENTATION_DETAIL_DO_NOT_USE_table_; + } + +public: + template + explicit constexpr FixedMapAdapter(Args&&... args): IMPLEMENTATION_DETAIL_DO_NOT_USE_table_(std::forward(args)...) + {} + + constexpr FixedMapAdapter() = default; + +public: + [[nodiscard]] constexpr V& at(const K& key, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + { + TableIndex idx = table().opaque_index_at(key); + if (!table().exists(idx)) + { + CheckingType::out_of_range(key, size(), loc); + } + return table().value(idx); + } + + [[nodiscard]] constexpr const V& at(const K& key, + const std_transition::source_location& loc = + std_transition::source_location::current()) const noexcept + { + TableIndex idx = table().opaque_index_at(key); + if (!table().exists(idx)) + { + CheckingType::out_of_range(key, size(), loc); + } + return table().value(idx); + } + + constexpr V& operator[](const K& key) noexcept + { + TableIndex idx = table().opaque_index_at(key); + if (!table().exists(idx)) + { + check_not_full(std_transition::source_location::current()); + idx = table().emplace(idx, key); + } + return table().value(idx); + } + + constexpr V& operator[](K&& key) noexcept + { + TableIndex idx = table().opaque_index_at(key); + if (!table().exists(idx)) + { + check_not_full(std_transition::source_location::current()); + idx = table().emplace(idx, std::move(key)); + } + return table().value(idx); + } + + constexpr const_iterator cbegin() const noexcept + { + return create_const_iterator(table().begin()); + } + constexpr const_iterator cend() const noexcept { return create_const_iterator(table().end()); } + constexpr const_iterator begin() const noexcept { return cbegin(); } + constexpr iterator begin() noexcept { return create_iterator(table().begin()); } + constexpr const_iterator end() const noexcept { return cend(); } + constexpr iterator end() noexcept { return create_iterator(table().end()); } + + constexpr reverse_iterator rbegin() noexcept { return create_reverse_iterator(table().end()); } + constexpr const_reverse_iterator rbegin() const noexcept { return crbegin(); } + constexpr const_reverse_iterator crbegin() const noexcept + { + return create_const_reverse_iterator(table().end()); + } + constexpr reverse_iterator rend() noexcept + { + return create_reverse_iterator(table().begin()); + } + constexpr const_reverse_iterator rend() const noexcept { return crend(); } + constexpr const_reverse_iterator crend() const noexcept + { + return create_const_reverse_iterator(table().begin()); + } + + [[nodiscard]] constexpr std::size_t size() const noexcept { return table().size(); } + [[nodiscard]] constexpr bool empty() const noexcept { return table().size() == 0; } + + constexpr void clear() noexcept { table().clear(); } + + constexpr std::pair insert( + const value_type& pair, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + { + TableIndex idx = table().opaque_index_at(pair.first); + if (table().exists(idx)) + { + return {create_iterator(idx), false}; + } + + check_not_full(loc); + idx = table().emplace(idx, pair.first, pair.second); + return {create_iterator(idx), true}; + } + + constexpr std::pair insert( + value_type&& pair, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + { + TableIndex idx = table().opaque_index_at(pair.first); + if (table().exists(idx)) + { + return {create_iterator(idx), false}; + } + + check_not_full(std::source_location::current()); + idx = table().emplace(idx, std::move(pair.first), std::move(pair.second)); + return {create_iterator(idx), true}; + } + + template + constexpr void insert(InputIt first, + InputIt last, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + { + for (; first != last; std::advance(first, 1)) + { + this->insert(*first, loc); + } + } + + constexpr void insert(std::initializer_list list, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + { + this->insert(list.begin(), list.end(), loc); + } + + template + requires std::is_assignable_v + constexpr std::pair insert_or_assign( + const key_type& key, + M&& obj, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + { + TableIndex idx = table().opaque_index_at(key); + if (table().exists(idx)) + { + table().value(idx) = std::forward(obj); + return {create_iterator(idx), false}; + } + + check_not_full(loc); + idx = table().emplace(idx, key, std::forward(obj)); + return {create_iterator(idx), true}; + } + + template + requires std::is_assignable_v + constexpr std::pair insert_or_assign( + key_type&& key, + M&& obj, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + { + TableIndex idx = table().opaque_index_at(key); + if (table().exists(idx)) + { + table().value(idx) = std::forward(obj); + return {create_iterator(idx), false}; + } + + check_not_full(loc); + idx = table().emplace(idx, std::move(key), std::forward(obj)); + return {create_iterator(idx), true}; + } + + template + requires std::is_assignable_v + constexpr iterator insert_or_assign(const_iterator /*hint*/, + const key_type& key, + M&& obj, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + { + return insert_or_assign(key, std::forward(obj), loc).first; + } + + template + requires std::is_assignable_v + constexpr iterator insert_or_assign(const_iterator /*hint*/, + key_type&& key, + M&& obj, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept + { + return insert_or_assign(std::move(key), std::forward(obj), loc).first; + } + + template + constexpr std::pair try_emplace(const K& key, Args&&... args) noexcept + { + TableIndex idx = table().opaque_index_at(key); + if (table().exists(idx)) + { + return {create_iterator(idx), false}; + } + + check_not_full(std::source_location::current()); + idx = table().emplace(idx, key, std::forward(args)...); + return {create_iterator(idx), true}; + } + + template + constexpr std::pair try_emplace(K&& key, Args&&... args) noexcept + { + TableIndex idx = table().opaque_index_at(key); + if (table().exists(idx)) + { + return {create_iterator(idx), false}; + } + + check_not_full(std::source_location::current()); + idx = table().emplace(idx, std::move(key), std::forward(args)...); + return {create_iterator(idx), true}; + } + + template + constexpr std::pair try_emplace(const_iterator /*hint*/, + const K& key, + Args&&... args) noexcept + { + return try_emplace(key, std::forward(args)...); + } + + template + constexpr std::pair try_emplace(const_iterator /*hint*/, + K&& key, + Args&&... args) noexcept + { + return try_emplace(std::move(key), std::forward(args)...); + } + + template + constexpr std::pair emplace(Args&&... args) noexcept + { + // TODO: try to avoid constructing the pair twice + std::pair as_pair{std::forward(args)...}; + return try_emplace(std::move(as_pair.first), std::move(as_pair.second)); + } + + template + constexpr std::pair emplace_hint(const_iterator /*hint*/, + Args&&... args) noexcept + { + return emplace(std::forward(args)...); + } + + constexpr iterator erase(const_iterator pos) noexcept + { + // TODO: shouldn't these be CheckingType:: checks? + assert(pos != cend()); + TableIndex idx = table().opaque_index_at(pos->first); + assert(table().exists(idx)); + TableIndex next_idx = table().erase(idx); + return create_checked_iterator(next_idx); + } + + constexpr iterator erase(iterator pos) noexcept + { + assert(pos != cend()); + TableIndex idx = table().opaque_index_at(pos->first); + assert(table().exists(idx)); + TableIndex next_idx = table().erase(idx); + return create_checked_iterator(next_idx); + } + + constexpr iterator erase(const_iterator first, const_iterator last) noexcept + { + const PairProvider& start = first.reference_provider(); + const PairProvider& end = last.reference_provider(); + TableIndex next_idx = table().erase_range(start, end); + return create_checked_iterator(next_idx); + } + + constexpr size_type erase(const key_type& key) noexcept + { + TableIndex idx = table().opaque_index_at(key); + if(!table().exists(idx)) + { + return false; + } + table().erase(idx); + return true; + } + + [[nodiscard]] constexpr iterator find(const K& key) noexcept + { + TableIndex idx = table().opaque_index_at(key); + return create_checked_iterator(idx); + } + + [[nodiscard]] constexpr const_iterator find(const K& key) const noexcept + { + TableIndex idx = table().opaque_index_at(key); + if (!table().exists(idx)) + { + return cend(); + } + return create_const_iterator(idx); + } + + // TODO: handle transparent versions of functions (need to check tranparency carefully) + + [[nodiscard]] constexpr bool contains(const K& key) const noexcept + { + TableIndex idx = table().opaque_index_at(key); + return table().exists(idx); + } + + [[nodiscard]] constexpr std::size_t count(const K& key) const noexcept + { + return static_cast(contains(key)); + } + + // TODO: make a subclass of this for ordered maps with all the fun functions there + + template + [[nodiscard]] constexpr bool operator==(const FixedMapAdapter& other) const + { + if(size() != other.size()) + { + return false; + } + return std::equal(cbegin(), cend(), other.cbegin()); + } + +private: + constexpr iterator create_checked_iterator(const TableIndex& index) noexcept + { + // check for nonexistent indices and replace them with end() so the iterator compares correctly + if (!table().exists(index)) + { + return end(); + } + return create_iterator(index); + } + + + constexpr iterator create_iterator(const TableIndex& start_index) noexcept + { + return iterator{PairProvider{&table(), start_index}}; + } + + constexpr const_iterator create_const_iterator(const TableIndex& start_index) const noexcept + { + return const_iterator{PairProvider{&table(), start_index}}; + } + + constexpr reverse_iterator create_reverse_iterator(const TableIndex& start_index) noexcept + { + return reverse_iterator{PairProvider{&table(), start_index}}; + } + + constexpr const_reverse_iterator create_const_reverse_iterator( + const TableIndex& start_index) const noexcept + { + return const_reverse_iterator{PairProvider{&table(), start_index}}; + } + + constexpr void check_not_full(const std_transition::source_location& loc) const + { + if (preconditions::test(!table().full())) + { + CheckingType::length_error(TableImpl::CAPACITY + 1, loc); + } + } + +}; + + +template +[[nodiscard]] constexpr + typename FixedMapAdapter:: + size_type + is_full( + const FixedMapAdapter& + c) +{ + return c.size() >= c.max_size(); +} + +template +constexpr + typename FixedMapAdapter:: + size_type + erase_if( + FixedMapAdapter& c, + Predicate predicate) +{ + return erase_if_detail::erase_if_impl(c, predicate); +} + +} diff --git a/include/fixed_containers/fixed_red_black_tree.hpp b/include/fixed_containers/fixed_red_black_tree.hpp index c3fb3bd4..abbccd33 100644 --- a/include/fixed_containers/fixed_red_black_tree.hpp +++ b/include/fixed_containers/fixed_red_black_tree.hpp @@ -85,7 +85,7 @@ class FixedRedBlackTreeBase constexpr void clear() noexcept { - delete_range_and_return_successor(index_of_min_at(), NULL_INDEX); + delete_range_and_return_successor(index_of_min_at().i, NULL_INDEX); } constexpr void insert_node(const K& key) noexcept @@ -169,7 +169,7 @@ class FixedRedBlackTreeBase constexpr NodeIndex delete_at_and_return_successor(const NodeIndex& i) noexcept { - return delete_at_and_return_successor_and_repositioned(i).successor; + return delete_at_and_return_successor_and_repositioned(i).successor.i; } constexpr NodeIndex delete_range_and_return_successor(const NodeIndex& from_index, @@ -194,7 +194,7 @@ class FixedRedBlackTreeBase fixup_repositioned_index(to, d.repositioned, i); - i = d.successor; + i = d.successor.i; } return to; @@ -335,43 +335,46 @@ class FixedRedBlackTreeBase return i != NULL_INDEX; } - [[nodiscard]] constexpr NodeIndex index_of_min_at(const NodeIndex& root_index) const noexcept + [[nodiscard]] constexpr NodeIndexAndParentIndex index_of_min_at(const NodeIndex& root_index) const noexcept { if (root_index == NULL_INDEX) { - return NULL_INDEX; + return {NULL_INDEX, NULL_INDEX, true}; } + NodeIndex parent_index = parent_index_of(root_index); for (NodeIndex i = root_index;;) { const NodeIndex left_index = tree_storage().left_index(i); if (left_index == NULL_INDEX) { - return i; + return {.parent = parent_index, .i = i, .is_left_child = true}; } + parent_index = i; i = left_index; } } - [[nodiscard]] constexpr NodeIndex index_of_min_at() const noexcept + [[nodiscard]] constexpr NodeIndexAndParentIndex index_of_min_at() const noexcept { return index_of_min_at(this->root_index()); } - [[nodiscard]] constexpr NodeIndex index_of_max_at(const NodeIndex& root_index) const noexcept + [[nodiscard]] constexpr NodeIndexAndParentIndex index_of_max_at(const NodeIndex& root_index) const noexcept { if (root_index == NULL_INDEX) { - return NULL_INDEX; + return {NULL_INDEX, NULL_INDEX, true}; } + NodeIndex parent_index = parent_index_of(root_index); for (NodeIndex i = root_index;;) { const NodeIndex right_index = tree_storage().right_index(i); if (right_index == NULL_INDEX) { - return i; + return {.parent = parent_index, .i = i, .is_left_child = false}; } i = right_index; } } - [[nodiscard]] constexpr NodeIndex index_of_max_at() const noexcept + [[nodiscard]] constexpr NodeIndexAndParentIndex index_of_max_at() const noexcept { return index_of_max_at(this->root_index()); } @@ -459,6 +462,7 @@ class FixedRedBlackTreeBase return node.left_index() != NULL_INDEX && node.right_index() != NULL_INDEX; } +public: // Accessors that automatically handle NULL_INDEX [[nodiscard]] constexpr NodeIndex parent_index_of(const NodeIndex& i) const { @@ -472,6 +476,7 @@ class FixedRedBlackTreeBase { return i == NULL_INDEX ? NULL_INDEX : tree_storage().right_index(i); } +private: [[nodiscard]] constexpr NodeColor color_of(const NodeIndex& i) const { // null nodes are treated as COLOR_BLACK @@ -722,7 +727,7 @@ class FixedRedBlackTreeBase *this, tree_storage_at(index_to_delete), ret.repositioned, index_to_delete); fixup_repositioned_index( IMPLEMENTATION_DETAIL_DO_NOT_USE_root_index_, ret.repositioned, index_to_delete); - fixup_repositioned_index(ret.successor, ret.repositioned, index_to_delete); + fixup_repositioned_index(ret.successor.i, ret.repositioned, index_to_delete); } return ret; @@ -884,7 +889,7 @@ class FixedRedBlackTree constexpr FixedRedBlackTree(const FixedRedBlackTree& other) : FixedRedBlackTree() { - for (NodeIndex i = other.index_of_min_at(); i != NULL_INDEX; + for (NodeIndex i = other.index_of_min_at().i; i != NULL_INDEX; i = other.index_of_successor_at(i)) { const auto node = other.tree_storage_at(i); @@ -902,7 +907,7 @@ class FixedRedBlackTree constexpr FixedRedBlackTree(FixedRedBlackTree&& other) noexcept : FixedRedBlackTree() { - for (NodeIndex i = other.index_of_min_at(); i != NULL_INDEX; + for (NodeIndex i = other.index_of_min_at().i; i != NULL_INDEX; i = other.index_of_successor_at(i)) { auto node = other.tree_storage_at(i); @@ -928,7 +933,7 @@ class FixedRedBlackTree } this->clear(); - for (NodeIndex i = other.index_of_min_at(); i != NULL_INDEX; + for (NodeIndex i = other.index_of_min_at().i; i != NULL_INDEX; i = other.index_of_successor_at(i)) { const auto node = other.tree_storage_at(i); @@ -952,7 +957,7 @@ class FixedRedBlackTree } this->clear(); - for (NodeIndex i = other.index_of_min_at(); i != NULL_INDEX; + for (NodeIndex i = other.index_of_min_at().i; i != NULL_INDEX; i = other.index_of_successor_at(i)) { auto node = other.tree_storage_at(i); diff --git a/include/fixed_containers/fixed_red_black_tree_types.hpp b/include/fixed_containers/fixed_red_black_tree_types.hpp index 46c7625b..fd349c66 100644 --- a/include/fixed_containers/fixed_red_black_tree_types.hpp +++ b/include/fixed_containers/fixed_red_black_tree_types.hpp @@ -102,7 +102,7 @@ struct NodeIndexAndParentIndex struct SuccessorIndexAndRepositionedIndex { - NodeIndex successor; + NodeIndexAndParentIndex successor; NodeIndex repositioned; }; diff --git a/include/fixed_containers/new_fixed_map.hpp b/include/fixed_containers/new_fixed_map.hpp new file mode 100644 index 00000000..39fc9347 --- /dev/null +++ b/include/fixed_containers/new_fixed_map.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include "fixed_containers/unified_interface_red_black_tree.hpp" +#include "fixed_containers/fixed_map_adapter.hpp" +#include "fixed_containers/map_checking.hpp" + +namespace fixed_containers +{ + +template , + fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness COMPACTNESS = + fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::EMBEDDED_COLOR, + template + typename StorageTemplate = FixedIndexBasedPoolStorage, + customize::MapChecking CheckingType = customize::MapAbortChecking> +class NewFixedMap : public FixedMapAdapter, + CheckingType> +{ + using FMA = FixedMapAdapter, + CheckingType>; +public: + constexpr NewFixedMap() noexcept + : NewFixedMap{Compare{}} {} + + explicit constexpr NewFixedMap(const Compare& comparator) noexcept + : FMA(comparator) {} + + template + constexpr NewFixedMap( + InputIt first, + InputIt last, + const Compare& comparator = Compare{}, + const std_transition::source_location& loc = std_transition::source_location::current()) + : NewFixedMap{comparator} + { + this->insert(first, last, loc); + } + + constexpr NewFixedMap(std::initializer_list list, + const Compare& comparator = {}, + const std_transition::source_location& loc = std_transition::source_location::current()) + : NewFixedMap{comparator} + { + this->insert(list, loc); + } + +}; + +/** + * Construct a NewFixedMap with its capacity being deduced from the number of key-value pairs being + * passed. + */ +template , + fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness COMPACTNESS = + fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::EMBEDDED_COLOR, + template typename StorageTemplate = FixedIndexBasedPoolStorage, + customize::MapChecking CheckingType, + std::size_t MAXIMUM_SIZE, + // Exposing this as a template parameter is useful for customization (for example with + // child classes that set the CheckingType) + typename FixedMapType = + NewFixedMap> +[[nodiscard]] constexpr FixedMapType make_fixed_map( + const std::pair (&list)[MAXIMUM_SIZE], + const Compare& comparator = Compare{}, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept +{ + return {std::begin(list), std::end(list), comparator, loc}; +} + +template , + fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness COMPACTNESS = + fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::EMBEDDED_COLOR, + template typename StorageTemplate = FixedIndexBasedPoolStorage, + std::size_t MAXIMUM_SIZE> +[[nodiscard]] constexpr auto make_fixed_map(const std::pair (&list)[MAXIMUM_SIZE], + const Compare& comparator = Compare{}, + const std_transition::source_location& loc = + std_transition::source_location::current()) noexcept +{ + using CheckingType = customize::MapAbortChecking; + using FixedMapType = + NewFixedMap; + return make_fixed_map(list, comparator, loc); +} + +} diff --git a/include/fixed_containers/unified_interface_red_black_tree.hpp b/include/fixed_containers/unified_interface_red_black_tree.hpp new file mode 100644 index 00000000..98ce76ff --- /dev/null +++ b/include/fixed_containers/unified_interface_red_black_tree.hpp @@ -0,0 +1,201 @@ +#pragma once + +#include "fixed_containers/fixed_red_black_tree.hpp" + +namespace fixed_containers::fixed_red_black_tree_detail +{ + +template + typename StorageTemplate> +class InterfaceRedBlackTree : public FixedRedBlackTree +{ +public: + using reference = std::pair; + using const_reference = std::pair; + + static constexpr std::size_t CAPACITY = MAXIMUM_SIZE; + + using OpaqueIndexType = NodeIndexAndParentIndex; + + template + class PairProvider + { + friend class PairProvider; + friend class InterfaceRedBlackTree; + using ConstOrMutableTree = std::conditional_t; + + private: + ConstOrMutableTree* tree_; + NodeIndex current_index_; + + public: + constexpr PairProvider(ConstOrMutableTree* const tree, + const OpaqueIndexType& current_index) noexcept + : tree_{tree} + , current_index_{replace_null_index_with_max_size_for_end_iterator(current_index.i)} + { + } + + constexpr PairProvider() noexcept + : tree_{nullptr} + , current_index_{CAPACITY} + { + } + + constexpr PairProvider(const PairProvider&) = default; + constexpr PairProvider(PairProvider&&) noexcept = default; + constexpr PairProvider& operator=(const PairProvider&) = default; + constexpr PairProvider& operator=(PairProvider&&) noexcept = default; + + // https://github.com/llvm/llvm-project/issues/62555 + template + constexpr PairProvider(const PairProvider& m) noexcept + requires(IS_CONST and !IS_CONST_2) + : PairProvider{m.tree_, {.parent = NULL_INDEX, .i = m.current_index_, .is_left_child = false}} + { + } + + constexpr void advance() noexcept + { + if (current_index_ == NULL_INDEX) + { + current_index_ = tree_->index_of_min_at().i; + } + else + { + current_index_ = tree_->index_of_successor_at(current_index_); + current_index_ = replace_null_index_with_max_size_for_end_iterator(current_index_); + } + } + constexpr void recede() noexcept + { + if (current_index_ == MAXIMUM_SIZE) + { + current_index_ = tree_->index_of_max_at().i; + } + else + { + current_index_ = tree_->index_of_predecessor_at(current_index_); + } + } + + constexpr std::conditional_t get() const noexcept + { + fixed_red_black_tree_detail::RedBlackTreeNodeView node = tree_->node_at(current_index_); + return {node.key(), node.value()}; + } + + template + constexpr bool operator==(const PairProvider& other) const noexcept + { + return tree_ == other.tree_ && current_index_ == other.current_index_; + } + }; + + constexpr OpaqueIndexType opaque_index_at(const K& k) const + { + return this->index_of_node_with_parent(k); + } + + constexpr OpaqueIndexType begin() const + { + return this->index_of_min_at(); + } + + constexpr OpaqueIndexType end() const + { + return {.parent = NULL_INDEX, .i = CAPACITY, .is_left_child = false}; + } + + constexpr bool exists(const OpaqueIndexType& i) const + { + // search will terminate with i being NULL if the key is not found + return i.i != NULL_INDEX; + } + + constexpr const V& value(const OpaqueIndexType& i) const + { + // no safety checks + return this->node_at(i.i).value(); + } + + constexpr V& value(const OpaqueIndexType& i) + { + return this->node_at(i.i).value(); + } + + template + constexpr OpaqueIndexType emplace(const OpaqueIndexType& i, const K& key, Args&&... args) + { + OpaqueIndexType mut_i = i; + this->insert_new_at(mut_i, key, std::forward(args)...); + return mut_i; + } + + template + constexpr OpaqueIndexType emplace(const OpaqueIndexType& i, K&& key, Args&&... args) + { + OpaqueIndexType mut_i = i; + this->insert_new_at(mut_i, std::move(key), std::forward(args)...); + return mut_i; + } + + constexpr OpaqueIndexType erase(const OpaqueIndexType& i) + { + NodeIndex successor_index = this->delete_at_and_return_successor(i.i); + + // construct the full index type. The `delete_at_and_return_successor` logic doesn't have enough info internally to create it "for free" + return this->full_index_from_node_index(successor_index); + } + + constexpr OpaqueIndexType erase_range(PairProvider first, PairProvider last) + { + NodeIndex start = first.current_index_; + NodeIndex end = last.current_index_; + const NodeIndex from = + start == CAPACITY ? NULL_INDEX : start; + const NodeIndex to = + end == CAPACITY ? NULL_INDEX : end; + const NodeIndex successor_index = this->delete_range_and_return_successor(from, to); + + return this->full_index_from_node_index(successor_index); + } + +public: + using Base = FixedRedBlackTree; + constexpr InterfaceRedBlackTree() noexcept : Base() { } + explicit constexpr InterfaceRedBlackTree(const Compare& comparator) noexcept : Base(comparator) { } + +private: + // The tree returns NULL_INDEX when an index is not available. + // For the purposes of iterators, use NULL_INDEX for rend() and + // MAXIMUM_SIZE for end() + static constexpr NodeIndex replace_null_index_with_max_size_for_end_iterator( + const NodeIndex& i) noexcept + { + return i == NULL_INDEX ? MAXIMUM_SIZE : i; + } + + constexpr OpaqueIndexType full_index_from_node_index(NodeIndex idx) const + { + NodeIndex parent_index = this->parent_index_of(idx); + if (idx == NULL_INDEX || parent_index == NULL_INDEX) + { + // follow the convention of marking root as a left child + return {.i = idx, .parent = parent_index, .is_left_child = true}; + } + + bool is_left_child = (this->tree_storage().left_index(parent_index) == idx); + return {.i = idx, .parent = parent_index, .is_left_child = is_left_child}; + } +}; + +} diff --git a/test/general_map_interface.hpp b/test/general_map_interface.hpp new file mode 100644 index 00000000..43412758 --- /dev/null +++ b/test/general_map_interface.hpp @@ -0,0 +1,36 @@ +#pragma once + +template < + typename Key, + typename Value> +class GeneralMap +{ +public: + using OpaqueIndexType = uint64_t; + using PairProvider = uint64_t; + + OpaqueIndexType opaque_index_at(const Key& k) const; + PairProvider begin() const; + PairProvider end() const; + + bool exists(const OpaqueIndexType& i) const; + + const Value& value(const OpaqueIndexType& i) const; + Value& value(const OpaqueIndexType& i); + + // requires `exists(i)` to be false + template + void emplace(const OpaqueIndexType& i, const Key& key, Args...&& args); + template + void emplace(const OpaqueIndexType& i, Key&& key, Args...&& args); + + // requires that `exists(i)` is true (e.g. erase will happen) + OpaqueIndexType erase(const OpaqueIndexType& i); + + // OpaqueIndexTypes are invalidated after erase, so directly take Key instead + OpaqueIndexType erase_range(const Key& start, const Key& end); + + void clear(); + + size_t size() const; +}; diff --git a/test/new_fixed_map_test.cpp b/test/new_fixed_map_test.cpp new file mode 100644 index 00000000..a1828a18 --- /dev/null +++ b/test/new_fixed_map_test.cpp @@ -0,0 +1,1761 @@ +#include "fixed_containers/new_fixed_map.hpp" + +#include "instance_counter.hpp" +#include "mock_testing_types.hpp" +#include "test_utilities_common.hpp" + +#include "fixed_containers/concepts.hpp" +#include "fixed_containers/consteval_compare.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace fixed_containers +{ +namespace +{ +using ES_1 = NewFixedMap; +static_assert(TriviallyCopyable); +static_assert(NotTrivial); +static_assert(StandardLayout); +static_assert(TriviallyCopyAssignable); +static_assert(TriviallyMoveAssignable); +static_assert(IsStructuralType); + +static_assert(std::bidirectional_iterator); +static_assert(std::bidirectional_iterator); +static_assert(!std::random_access_iterator); +static_assert(!std::random_access_iterator); + +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_copyable_v); + +static_assert(std::is_same_v, std::pair>); +static_assert(std::is_same_v, std::pair>); +static_assert(std::is_same_v, std::ptrdiff_t>); +static_assert(std::is_same_v::pointer, + ArrowProxy>>); +static_assert(std::is_same_v::iterator_category, + std::bidirectional_iterator_tag>); + +static_assert( + std::is_same_v, std::pair>); +static_assert( + std::is_same_v, std::pair>); +static_assert(std::is_same_v, std::ptrdiff_t>); +static_assert(std::is_same_v::pointer, + ArrowProxy>>); +static_assert(std::is_same_v::iterator_category, + std::bidirectional_iterator_tag>); + +static_assert(std::is_same_v); + +using STD_MAP_INT_INT = std::map; +static_assert(ranges::bidirectional_iterator); +static_assert(ranges::bidirectional_iterator); + +} // namespace + +TEST(NewFixedMap, DefaultConstructor) +{ + constexpr NewFixedMap s1{}; + static_assert(s1.empty()); +} + +TEST(NewFixedMap, IteratorConstructor) +{ + constexpr std::array INPUT{std::pair{2, 20}, std::pair{4, 40}}; + constexpr NewFixedMap s2{INPUT.begin(), INPUT.end()}; + static_assert(s2.size() == 2); + + static_assert(s2.at(2) == 20); + static_assert(s2.at(4) == 40); +} + +TEST(NewFixedMap, Initializer) +{ + constexpr NewFixedMap s1{{2, 20}, {4, 40}}; + static_assert(s1.size() == 2); + + constexpr NewFixedMap s2{{3, 30}}; + static_assert(s2.size() == 1); +} + +TEST(NewFixedMap, MaxSize) +{ + constexpr NewFixedMap s1{{2, 20}, {4, 40}}; + static_assert(s1.max_size() == 10); +} + +TEST(NewFixedMap, EmptySizeFull) +{ + constexpr NewFixedMap s1{{2, 20}, {4, 40}}; + static_assert(s1.size() == 2); + static_assert(!s1.empty()); + + constexpr NewFixedMap s2{}; + static_assert(s2.size() == 0); + static_assert(s2.empty()); + + constexpr NewFixedMap s3{{2, 20}, {4, 40}}; + static_assert(is_full(s3)); + + constexpr NewFixedMap s4{{2, 20}, {4, 40}}; + static_assert(!is_full(s4)); +} + +TEST(NewFixedMap, OperatorBracket_Constexpr) +{ + constexpr auto s1 = []() + { + NewFixedMap s{}; + s[2] = 20; + s[4] = 40; + return s; + }(); + + static_assert(s1.size() == 2); + static_assert(!s1.contains(1)); + static_assert(s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); +} + +TEST(NewFixedMap, MaxSizeDeduction) +{ + constexpr auto s1 = make_fixed_map({std::pair{30, 30}, std::pair{31, 54}}); + static_assert(s1.size() == 2); + static_assert(s1.max_size() == 2); + static_assert(s1.contains(30)); + static_assert(s1.contains(31)); + static_assert(!s1.contains(32)); +} + +TEST(NewFixedMap, OperatorBracket_NonConstexpr) +{ + NewFixedMap s1{}; + s1[2] = 25; + s1[4] = 45; + ASSERT_EQ(2, s1.size()); + ASSERT_TRUE(!s1.contains(1)); + ASSERT_TRUE(s1.contains(2)); + ASSERT_TRUE(!s1.contains(3)); + ASSERT_TRUE(s1.contains(4)); +} + +TEST(NewFixedMap, OperatorBracket_ExceedsCapacity) +{ + { + NewFixedMap s1{}; + s1[2]; + s1[4]; + s1[4]; + s1[4]; + EXPECT_DEATH(s1[6], ""); + } + { + NewFixedMap s1{}; + s1[2]; + s1[4]; + s1[4]; + s1[4]; + int key = 6; + EXPECT_DEATH(s1[key], ""); + } +} + +namespace +{ +struct ConstructionCounter +{ + static int counter; + using Self = ConstructionCounter; + + int value; + + explicit ConstructionCounter(int value_in_ctor = 0) + : value{value_in_ctor} + { + counter++; + } + ConstructionCounter(const Self& other) + : value{other.value} + { + counter++; + } + ConstructionCounter& operator=(const Self& other) = default; +}; +int ConstructionCounter::counter = 0; +} // namespace + +TEST(NewFixedMap, OperatorBracket_EnsureNoUnnecessaryTemporaries) +{ + NewFixedMap s1{}; + ASSERT_EQ(0, ConstructionCounter::counter); + ConstructionCounter instance1{25}; + ConstructionCounter instance2{35}; + ASSERT_EQ(2, ConstructionCounter::counter); + s1[2] = instance1; + ASSERT_EQ(3, ConstructionCounter::counter); + s1[4] = s1.at(2); + ASSERT_EQ(4, ConstructionCounter::counter); + s1[4] = instance2; + ASSERT_EQ(4, ConstructionCounter::counter); +} + +TEST(NewFixedMap, Insert) +{ + constexpr auto s1 = []() + { + NewFixedMap s{}; + s.insert({2, 20}); + s.insert({4, 40}); + return s; + }(); + + static_assert(s1.size() == 2); + static_assert(!s1.contains(1)); + static_assert(s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); +} + +TEST(NewFixedMap, Insert_ExceedsCapacity) +{ + { + NewFixedMap s1{}; + s1.insert({2, 20}); + s1.insert({4, 40}); + s1.insert({4, 41}); + s1.insert({4, 42}); + EXPECT_DEATH(s1.insert({6, 60}), ""); + } + { + NewFixedMap s1{}; + s1.insert({2, 20}); + s1.insert({4, 40}); + s1.insert({4, 41}); + s1.insert({4, 42}); + std::pair key_value{6, 60}; + EXPECT_DEATH(s1.insert(key_value), ""); + } +} + +TEST(NewFixedMap, InsertMultipleTimes) +{ + constexpr auto s1 = []() + { + NewFixedMap s{}; + { + auto [it, was_inserted] = s.insert({2, 20}); + assert_or_abort(was_inserted); + assert_or_abort(2 == it->first); + assert_or_abort(20 == it->second); + } + { + auto [it, was_inserted] = s.insert({4, 40}); + assert_or_abort(was_inserted); + assert_or_abort(4 == it->first); + assert_or_abort(40 == it->second); + } + { + auto [it, was_inserted] = s.insert({2, 99999}); + assert_or_abort(!was_inserted); + assert_or_abort(2 == it->first); + assert_or_abort(20 == it->second); + } + { + auto [it, was_inserted] = s.insert({4, 88888}); + assert_or_abort(!was_inserted); + assert_or_abort(4 == it->first); + assert_or_abort(40 == it->second); + } + return s; + }(); + + static_assert(s1.size() == 2); + static_assert(!s1.contains(1)); + static_assert(s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); +} + +TEST(NewFixedMap, InsertIterators) +{ + constexpr NewFixedMap a{{2, 20}, {4, 40}}; + + constexpr auto s1 = [&]() + { + NewFixedMap s{}; + s.insert(a.begin(), a.end()); + return s; + }(); + + static_assert(s1.size() == 2); + static_assert(!s1.contains(1)); + static_assert(s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); +} + +TEST(NewFixedMap, InsertInitializer) +{ + constexpr auto s1 = []() + { + NewFixedMap s{}; + s.insert({{2, 20}, {4, 40}}); + return s; + }(); + + static_assert(s1.size() == 2); + static_assert(!s1.contains(1)); + static_assert(s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); +} + +TEST(NewFixedMap, InsertOrAssign) +{ + constexpr auto s1 = []() + { + NewFixedMap s{}; + { + auto [it, was_inserted] = s.insert_or_assign(2, 20); + assert_or_abort(was_inserted); + assert_or_abort(2 == it->first); + assert_or_abort(20 == it->second); + } + { + const int key = 4; + auto [it, was_inserted] = s.insert_or_assign(key, 40); + assert_or_abort(was_inserted); + assert_or_abort(4 == it->first); + assert_or_abort(40 == it->second); + } + { + auto [it, was_inserted] = s.insert_or_assign(2, 99999); + assert_or_abort(!was_inserted); + assert_or_abort(2 == it->first); + assert_or_abort(99999 == it->second); + } + { + const int key = 4; + auto [it, was_inserted] = s.insert_or_assign(key, 88888); + assert_or_abort(!was_inserted); + assert_or_abort(4 == it->first); + assert_or_abort(88888 == it->second); + } + return s; + }(); + + static_assert(s1.size() == 2); + static_assert(!s1.contains(1)); + static_assert(s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); +} + +TEST(NewFixedMap, InsertOrAssign_ExceedsCapacity) +{ + { + NewFixedMap s1{}; + s1.insert_or_assign(2, 20); + s1.insert_or_assign(4, 40); + s1.insert_or_assign(4, 41); + s1.insert_or_assign(4, 42); + EXPECT_DEATH(s1.insert_or_assign(6, 60), ""); + } + { + NewFixedMap s1{}; + s1.insert_or_assign(2, 20); + s1.insert_or_assign(4, 40); + s1.insert_or_assign(4, 41); + s1.insert_or_assign(4, 42); + int key = 6; + EXPECT_DEATH(s1.insert_or_assign(key, 60), ""); + } +} + +TEST(NewFixedMap, TryEmplace) +{ + { + constexpr NewFixedMap s = []() + { + NewFixedMap s1{}; + s1.try_emplace(2, 20); + const int key = 2; + s1.try_emplace(key, 209999999); + return s1; + }(); + + static_assert(consteval_compare::equal<1, s.size()>); + static_assert(s.contains(2)); + } + + { + NewFixedMap s1{}; + + { + auto [it, was_inserted] = s1.try_emplace(2, 20); + + ASSERT_EQ(1, s1.size()); + ASSERT_TRUE(!s1.contains(1)); + ASSERT_TRUE(s1.contains(2)); + ASSERT_TRUE(!s1.contains(3)); + ASSERT_TRUE(!s1.contains(4)); + ASSERT_EQ(20, s1.at(2)); + ASSERT_TRUE(was_inserted); + ASSERT_EQ(2, it->first); + ASSERT_EQ(20, it->second); + } + + { + const int key = 2; + auto [it, was_inserted] = s1.try_emplace(key, 209999999); + ASSERT_EQ(1, s1.size()); + ASSERT_TRUE(!s1.contains(1)); + ASSERT_TRUE(s1.contains(2)); + ASSERT_TRUE(!s1.contains(3)); + ASSERT_TRUE(!s1.contains(4)); + ASSERT_EQ(20, s1.at(2)); + ASSERT_FALSE(was_inserted); + ASSERT_EQ(2, it->first); + ASSERT_EQ(20, it->second); + } + } + + { + NewFixedMap s1{}; + s1.try_emplace(1ULL, /*ImplicitlyConvertibleFromInt*/ 2, ExplicitlyConvertibleFromInt{3}); + + std::map s2{}; + s2.try_emplace(1ULL, /*ImplicitlyConvertibleFromInt*/ 2, ExplicitlyConvertibleFromInt{3}); + } +} + +TEST(NewFixedMap, TryEmplace_ExceedsCapacity) +{ + { + NewFixedMap s1{}; + s1.try_emplace(2, 20); + s1.try_emplace(4, 40); + s1.try_emplace(4, 41); + s1.try_emplace(4, 42); + EXPECT_DEATH(s1.try_emplace(6, 60), ""); + } + { + NewFixedMap s1{}; + s1.try_emplace(2, 20); + s1.try_emplace(4, 40); + s1.try_emplace(4, 41); + s1.try_emplace(4, 42); + int key = 6; + EXPECT_DEATH(s1.try_emplace(key, 60), ""); + } +} + +TEST(NewFixedMap, TryEmplace_TypeConversion) +{ + { + int* raw_ptr = new int; + NewFixedMap, 10> s{}; + s.try_emplace(3, raw_ptr); + } + { + int* raw_ptr = new int; + std::map> s{}; + s.try_emplace(3, raw_ptr); + } +} + +TEST(NewFixedMap, Emplace) +{ + { + constexpr NewFixedMap s = []() + { + NewFixedMap s1{}; + s1.emplace(2, 20); + const int key = 2; + s1.emplace(key, 209999999); + return s1; + }(); + + static_assert(consteval_compare::equal<1, s.size()>); + static_assert(s.contains(2)); + } + + { + NewFixedMap s1{}; + + { + auto [it, was_inserted] = s1.emplace(2, 20); + + ASSERT_EQ(1, s1.size()); + ASSERT_TRUE(!s1.contains(1)); + ASSERT_TRUE(s1.contains(2)); + ASSERT_TRUE(!s1.contains(3)); + ASSERT_TRUE(!s1.contains(4)); + ASSERT_EQ(20, s1.at(2)); + ASSERT_TRUE(was_inserted); + ASSERT_EQ(2, it->first); + ASSERT_EQ(20, it->second); + } + + { + auto [it, was_inserted] = s1.emplace(2, 209999999); + ASSERT_EQ(1, s1.size()); + ASSERT_TRUE(!s1.contains(1)); + ASSERT_TRUE(s1.contains(2)); + ASSERT_TRUE(!s1.contains(3)); + ASSERT_TRUE(!s1.contains(4)); + ASSERT_EQ(20, s1.at(2)); + ASSERT_FALSE(was_inserted); + ASSERT_EQ(2, it->first); + ASSERT_EQ(20, it->second); + } + + { + NewFixedMap s2{}; + s2.emplace(1, MockMoveableButNotCopyable{}); + } + } +} + +TEST(NewFixedMap, Emplace_ExceedsCapacity) +{ + { + NewFixedMap s1{}; + s1.emplace(2, 20); + s1.emplace(4, 40); + s1.emplace(4, 41); + s1.emplace(4, 42); + EXPECT_DEATH(s1.emplace(6, 60), ""); + } + { + NewFixedMap s1{}; + s1.emplace(2, 20); + s1.emplace(4, 40); + s1.emplace(4, 41); + s1.emplace(4, 42); + int key = 6; + EXPECT_DEATH(s1.emplace(key, 60), ""); + } +} + +TEST(NewFixedMap, Clear) +{ + constexpr auto s1 = []() + { + NewFixedMap s{{2, 20}, {4, 40}}; + s.clear(); + return s; + }(); + + static_assert(s1.empty()); +} + +TEST(NewFixedMap, Erase) +{ + constexpr auto s1 = []() + { + NewFixedMap s{{2, 20}, {4, 40}}; + auto removed_count = s.erase(2); + assert_or_abort(removed_count == 1); + removed_count = s.erase(3); + assert_or_abort(removed_count == 0); + return s; + }(); + + static_assert(s1.size() == 1); + static_assert(!s1.contains(1)); + static_assert(!s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); +} + +TEST(NewFixedMap, EraseIterator) +{ + constexpr auto s1 = []() + { + NewFixedMap s{{2, 20}, {3, 30}, {4, 40}}; + { + auto it = s.begin(); + auto next = s.erase(it); + assert_or_abort(next->first == 3); + assert_or_abort(next->second == 30); + } + + { + auto it = s.cbegin(); + auto next = s.erase(it); + assert_or_abort(next->first == 4); + assert_or_abort(next->second == 40); + } + return s; + }(); + + static_assert(s1.size() == 1); + static_assert(!s1.contains(1)); + static_assert(!s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); +} + +TEST(NewFixedMap, EraseIterator_Ambiguity) +{ + // If the iterator has extraneous auto-conversions, it might cause ambiguity between the various + // overloads + NewFixedMap s1{}; + s1.erase(""); +} + +TEST(NewFixedMap, EraseIterator_InvalidIterator) +{ + NewFixedMap s{{2, 20}, {4, 40}}; + { + auto it = s.begin(); + std::advance(it, 2); + EXPECT_DEATH(s.erase(it), ""); + } +} + +TEST(NewFixedMap, EraseRange) +{ + { + constexpr auto s1 = []() + { + NewFixedMap s{{2, 20}, {3, 30}, {4, 40}}; + auto from = s.begin(); + std::advance(from, 1); + auto to = s.begin(); + std::advance(to, 2); + auto next = s.erase(from, to); + assert_or_abort(next->first == 4); + assert_or_abort(next->second == 40); + return s; + }(); + + static_assert(consteval_compare::equal<2, s1.size()>); + static_assert(!s1.contains(1)); + static_assert(s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); + } + { + constexpr auto s1 = []() + { + NewFixedMap s{{2, 20}, {4, 40}}; + auto from = s.begin(); + auto to = s.begin(); + auto next = s.erase(from, to); + assert_or_abort(next->first == 2); + assert_or_abort(next->second == 20); + return s; + }(); + + static_assert(consteval_compare::equal<2, s1.size()>); + static_assert(!s1.contains(1)); + static_assert(s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); + } + { + constexpr auto s1 = []() + { + NewFixedMap s{{1, 10}, {4, 40}}; + auto from = s.begin(); + auto to = s.end(); + auto next = s.erase(from, to); + assert_or_abort(next == s.end()); + return s; + }(); + + static_assert(consteval_compare::equal<0, s1.size()>); + static_assert(!s1.contains(1)); + static_assert(!s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(!s1.contains(4)); + } +} + +TEST(NewFixedMap, EraseIf) +{ + constexpr auto s1 = []() + { + NewFixedMap s{{2, 20}, {3, 30}, {4, 40}}; + std::size_t removed_count = fixed_containers::erase_if(s, + [](const auto& entry) + { + const auto& [key, _] = entry; + return key == 2 or key == 4; + }); + assert_or_abort(2 == removed_count); + return s; + }(); + + static_assert(consteval_compare::equal<1, s1.size()>); + static_assert(!s1.contains(1)); + static_assert(!s1.contains(2)); + static_assert(s1.contains(3)); + static_assert(!s1.contains(4)); + + static_assert(s1.at(3) == 30); +} + +TEST(NewFixedMap, Iterator_StructuredBinding) +{ + constexpr auto s1 = []() + { + NewFixedMap s{}; + s.insert({3, 30}); + s.insert({4, 40}); + s.insert({1, 10}); + return s; + }(); + + for (auto&& [key, value] : s1) + { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } +} + +TEST(NewFixedMap, IteratorBasic) +{ + constexpr NewFixedMap s1{{1, 10}, {2, 20}, {3, 30}, {4, 40}}; + + static_assert(std::distance(s1.cbegin(), s1.cend()) == 4); + + static_assert(s1.begin()->first == 1); + static_assert(s1.begin()->second == 10); + static_assert(std::next(s1.begin(), 1)->first == 2); + static_assert(std::next(s1.begin(), 1)->second == 20); + static_assert(std::next(s1.begin(), 2)->first == 3); + static_assert(std::next(s1.begin(), 2)->second == 30); + static_assert(std::next(s1.begin(), 3)->first == 4); + static_assert(std::next(s1.begin(), 3)->second == 40); + + static_assert(std::prev(s1.end(), 1)->first == 4); + static_assert(std::prev(s1.end(), 1)->second == 40); + static_assert(std::prev(s1.end(), 2)->first == 3); + static_assert(std::prev(s1.end(), 2)->second == 30); + static_assert(std::prev(s1.end(), 3)->first == 2); + static_assert(std::prev(s1.end(), 3)->second == 20); + static_assert(std::prev(s1.end(), 4)->first == 1); + static_assert(std::prev(s1.end(), 4)->second == 10); +} + +TEST(NewFixedMap, IteratorTypes) +{ + constexpr auto s1 = []() + { + NewFixedMap s{{2, 20}, {4, 40}}; + + for (const auto& key_and_value : s) // "-Wrange-loop-bind-reference" + { + static_assert( + std::is_same_v&>); + // key_and_value.second = 5; // Allowed, but ideally should not. + (void)key_and_value; + } + // cannot do this + // error: non-const lvalue reference to type 'std::pair<...>' cannot bind to a temporary of + // type 'std::pair<...>' + /* + for (auto& key_and_value : s) + { + static_assert(std::is_same_v&>); + key_and_value.second = 5; // Allowed + } + */ + + for (auto&& key_and_value : s) + { + static_assert(std::is_same_v&&>); + key_and_value.second = 5; // Allowed + } + + for (const auto& [key, value] : s) // "-Wrange-loop-bind-reference" + { + static_assert(std::is_same_v); + static_assert(std::is_same_v); // Non-ideal, should be const + } + + // cannot do this + // error: non-const lvalue reference to type 'std::pair<...>' cannot bind to a temporary of + // type 'std::pair<...>' + /* + for (auto& [key, value] : s) + { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + */ + + for (auto&& [key, value] : s) + { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + return s; + }(); + + const auto lvalue_it = s1.begin(); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + NewFixedMap s_non_const{}; + auto lvalue_it_of_non_const = s_non_const.begin(); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + for (const auto& key_and_value : s1) + { + static_assert( + std::is_same_v&>); + } + + for (auto&& [key, value] : s1) + { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + { + std::map s{}; + + for (const auto& key_and_value : s) + { + static_assert( + std::is_same_v&>); + // key_and_value.second = 5; // Not allowed + } + + for (auto& key_and_value : s) + { + static_assert(std::is_same_v&>); + key_and_value.second = 5; // Allowed + } + + for (auto&& key_and_value : s) + { + static_assert(std::is_same_v&>); + key_and_value.second = 5; // Allowed + } + + for (const auto& [key, value] : s) + { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + for (auto& [key, value] : s) + { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + for (auto&& [key, value] : s) + { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + } +} + +TEST(NewFixedMap, IteratorMutableValue) +{ + constexpr auto s1 = []() + { + NewFixedMap s{{2, 20}, {4, 40}}; + + for (auto&& [key, value] : s) + { + value *= 2; + } + + return s; + }(); + + static_assert(std::distance(s1.cbegin(), s1.cend()) == 2); + + static_assert(s1.begin()->first == 2); + static_assert(s1.begin()->second == 40); + static_assert(std::next(s1.begin(), 1)->first == 4); + static_assert(std::next(s1.begin(), 1)->second == 80); + + static_assert(std::prev(s1.end(), 1)->first == 4); + static_assert(std::prev(s1.end(), 1)->second == 80); + static_assert(std::prev(s1.end(), 2)->first == 2); + static_assert(std::prev(s1.end(), 2)->second == 40); +} + +TEST(NewFixedMap, IteratorComparisonOperator) +{ + constexpr NewFixedMap s1{{{1, 10}, {4, 40}}}; + + // All combinations of [==, !=]x[const, non-const] + static_assert(s1.cbegin() == s1.cbegin()); + static_assert(s1.cbegin() == s1.begin()); + static_assert(s1.begin() == s1.begin()); + static_assert(s1.cbegin() != s1.cend()); + static_assert(s1.cbegin() != s1.end()); + static_assert(s1.begin() != s1.cend()); + + static_assert(std::next(s1.begin(), 2) == s1.end()); + static_assert(std::prev(s1.end(), 2) == s1.begin()); +} + +TEST(NewFixedMap, IteratorAssignment) +{ + constexpr auto s1 = []() + { + NewFixedMap s{{2, 20}, {4, 40}}; + + { + NewFixedMap::const_iterator it; // Default construction + it = s.cbegin(); + assert_or_abort(it == s.begin()); + assert_or_abort(it->first == 2); + assert_or_abort(it->second == 20); + + it = s.cend(); + assert_or_abort(it == s.cend()); + + { + NewFixedMap::iterator non_const_it; // Default construction + non_const_it = s.end(); + it = non_const_it; // Non-const needs to be assignable to const + assert_or_abort(it == s.end()); + } + + for (it = s.cbegin(); it != s.cend(); it++) + { + static_assert(std::is_same_v::const_iterator>); + } + + for (it = s.begin(); it != s.end(); it++) + { + static_assert(std::is_same_v::const_iterator>); + } + } + { + NewFixedMap::iterator it = s.begin(); + assert_or_abort(it == s.begin()); // Asserts are just to make the value used. + + // Const should not be assignable to non-const + // it = s.cend(); + + it = s.end(); + assert_or_abort(it == s.end()); + + for (it = s.begin(); it != s.end(); it++) + { + static_assert(std::is_same_v::iterator>); + } + } + return s; + }(); + + static_assert(s1.size() == 2); +} + +TEST(NewFixedMap, Iterator_OffByOneIssues) +{ + constexpr NewFixedMap s1{{{1, 10}, {4, 40}}}; + + static_assert(std::distance(s1.cbegin(), s1.cend()) == 2); + + static_assert(s1.begin()->first == 1); + static_assert(s1.begin()->second == 10); + static_assert(std::next(s1.begin(), 1)->first == 4); + static_assert(std::next(s1.begin(), 1)->second == 40); + + static_assert(std::prev(s1.end(), 1)->first == 4); + static_assert(std::prev(s1.end(), 1)->second == 40); + static_assert(std::prev(s1.end(), 2)->first == 1); + static_assert(std::prev(s1.end(), 2)->second == 10); +} + +TEST(NewFixedMap, Iterator_EnsureOrder) +{ + constexpr auto s1 = []() + { + NewFixedMap s{}; + s.insert({3, 30}); + s.insert({4, 40}); + s.insert({1, 10}); + return s; + }(); + + static_assert(std::distance(s1.cbegin(), s1.cend()) == 3); + + static_assert(s1.begin()->first == 1); + static_assert(s1.begin()->second == 10); + static_assert(std::next(s1.begin(), 1)->first == 3); + static_assert(std::next(s1.begin(), 1)->second == 30); + static_assert(std::next(s1.begin(), 2)->first == 4); + static_assert(std::next(s1.begin(), 2)->second == 40); + + static_assert(std::prev(s1.end(), 1)->first == 4); + static_assert(std::prev(s1.end(), 1)->second == 40); + static_assert(std::prev(s1.end(), 2)->first == 3); + static_assert(std::prev(s1.end(), 2)->second == 30); + static_assert(std::prev(s1.end(), 3)->first == 1); + static_assert(std::prev(s1.end(), 3)->second == 10); +} + +TEST(NewFixedMap, DereferencedIteratorAssignability) +{ + { + using DereferencedIt = std::map::iterator::value_type; + static_assert(NotMoveAssignable); + static_assert(NotCopyAssignable); + } + + { + using DereferencedIt = NewFixedMap::iterator::value_type; + static_assert(NotMoveAssignable); + static_assert(NotCopyAssignable); + } +} + +TEST(NewFixedMap, Iterator_AccessingDefaultConstructedIteratorFails) +{ + auto it = NewFixedMap::iterator{}; + + EXPECT_DEATH(it->second++, ""); +} + +static constexpr NewFixedMap LIVENESS_TEST_INSTANCE{{1, 100}}; + +TEST(NewFixedMap, IteratorDereferenceLiveness) +{ + { + constexpr auto ref = []() { return *LIVENESS_TEST_INSTANCE.begin(); }(); + static_assert(ref.first == 1); + static_assert(ref.second == 100); + } + + { + // this test needs ubsan/asan + NewFixedMap m = {{1, 100}}; + decltype(m)::reference ref = *m.begin(); // Fine + EXPECT_EQ(1, ref.first); + EXPECT_EQ(100, ref.second); + } + { + // this test needs ubsan/asan + NewFixedMap m = {{1, 100}}; + auto ref = *m.begin(); // Fine + EXPECT_EQ(1, ref.first); + EXPECT_EQ(100, ref.second); + } + { + /* + // this test needs ubsan/asan + NewFixedMap m = {{1, 100}}; + auto& ref = *m.begin(); // Fails to compile, instead of allowing dangling pointers + EXPECT_EQ(1, ref.first); + EXPECT_EQ(100, ref.second); + */ + } +} + +TEST(NewFixedMap, ReverseIteratorBasic) +{ + constexpr NewFixedMap s1{{1, 10}, {2, 20}, {3, 30}, {4, 40}}; + + static_assert(consteval_compare::equal<4, std::distance(s1.crbegin(), s1.crend())>); + + static_assert(consteval_compare::equal<4, s1.rbegin()->first>); + static_assert(consteval_compare::equal<40, s1.rbegin()->second>); + static_assert(consteval_compare::equal<3, std::next(s1.rbegin(), 1)->first>); + static_assert(consteval_compare::equal<30, std::next(s1.rbegin(), 1)->second>); + static_assert(consteval_compare::equal<2, std::next(s1.rbegin(), 2)->first>); + static_assert(consteval_compare::equal<20, std::next(s1.rbegin(), 2)->second>); + static_assert(consteval_compare::equal<1, std::next(s1.rbegin(), 3)->first>); + static_assert(consteval_compare::equal<10, std::next(s1.rbegin(), 3)->second>); + + static_assert(consteval_compare::equal<1, std::prev(s1.rend(), 1)->first>); + static_assert(consteval_compare::equal<10, std::prev(s1.rend(), 1)->second>); + static_assert(consteval_compare::equal<2, std::prev(s1.rend(), 2)->first>); + static_assert(consteval_compare::equal<20, std::prev(s1.rend(), 2)->second>); + static_assert(consteval_compare::equal<3, std::prev(s1.rend(), 3)->first>); + static_assert(consteval_compare::equal<30, std::prev(s1.rend(), 3)->second>); + static_assert(consteval_compare::equal<4, std::prev(s1.rend(), 4)->first>); + static_assert(consteval_compare::equal<40, std::prev(s1.rend(), 4)->second>); +} + +TEST(NewFixedMap, ReverseIteratorBase) +{ + constexpr auto s1 = []() + { + NewFixedMap s{{1, 10}, {2, 20}, {3, 30}}; + auto it = s.rbegin(); // points to 3 + std::advance(it, 1); // points to 2 + // https://stackoverflow.com/questions/1830158/how-to-call-erase-with-a-reverse-iterator + s.erase(std::next(it).base()); + return s; + }(); + + static_assert(s1.size() == 2); + static_assert(s1.at(1) == 10); + static_assert(s1.at(3) == 30); +} + +TEST(NewFixedMap, Find) +{ + constexpr NewFixedMap s1{{2, 20}, {4, 40}}; + static_assert(s1.size() == 2); + + static_assert(s1.find(1) == s1.cend()); + static_assert(s1.find(2) != s1.cend()); + static_assert(s1.find(3) == s1.cend()); + static_assert(s1.find(4) != s1.cend()); + + static_assert(s1.at(2) == 20); + static_assert(s1.at(4) == 40); +} + +// TEST(NewFixedMap, Find_TransparentComparator) +// { +// constexpr NewFixedMap> s{}; +// constexpr MockBComparableToA b{5}; +// static_assert(s.find(b) == s.end()); +// } + +TEST(NewFixedMap, MutableFind) +{ + constexpr auto s1 = []() + { + NewFixedMap s{{2, 20}, {4, 40}}; + auto it = s.find(2); + it->second = 25; + it++; + it->second = 45; + return s; + }(); + + static_assert(s1.at(2) == 25); + static_assert(s1.at(4) == 45); +} + +TEST(NewFixedMap, Contains) +{ + constexpr NewFixedMap s1{{2, 20}, {4, 40}}; + static_assert(s1.size() == 2); + + static_assert(!s1.contains(1)); + static_assert(s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(s1.contains(4)); + + static_assert(s1.at(2) == 20); + static_assert(s1.at(4) == 40); +} + +// TEST(NewFixedMap, Contains_TransparentComparator) +// { +// constexpr NewFixedMap> s{ +// {MockAComparableToB{1}, 10}, {MockAComparableToB{3}, 30}, {MockAComparableToB{5}, 50}}; +// constexpr MockBComparableToA b{5}; +// static_assert(s.contains(b)); +// } + +TEST(NewFixedMap, Count) +{ + constexpr NewFixedMap s1{{2, 20}, {4, 40}}; + static_assert(s1.size() == 2); + + static_assert(s1.count(1) == 0); + static_assert(s1.count(2) == 1); + static_assert(s1.count(3) == 0); + static_assert(s1.count(4) == 1); + + static_assert(s1.at(2) == 20); + static_assert(s1.at(4) == 40); +} + +// TEST(NewFixedMap, Count_TransparentComparator) +// { +// constexpr NewFixedMap> s{ +// {MockAComparableToB{1}, 10}, {MockAComparableToB{3}, 30}, {MockAComparableToB{5}, 50}}; +// constexpr MockBComparableToA b{5}; +// static_assert(s.count(b) == 1); +// } + +// TEST(NewFixedMap, LowerBound) +// { +// constexpr NewFixedMap s1{{2, 20}, {4, 40}}; +// static_assert(s1.size() == 2); + +// static_assert(s1.lower_bound(1)->first == 2); +// static_assert(s1.lower_bound(2)->first == 2); +// static_assert(s1.lower_bound(3)->first == 4); +// static_assert(s1.lower_bound(4)->first == 4); +// static_assert(s1.lower_bound(5) == s1.cend()); +// } + +// TEST(NewFixedMap, LowerBound_TransparentComparator) +// { +// constexpr NewFixedMap> s{ +// {MockAComparableToB{1}, 10}, {MockAComparableToB{3}, 30}, {MockAComparableToB{5}, 50}}; +// constexpr MockBComparableToA b{3}; +// static_assert(s.lower_bound(b)->first == MockAComparableToB{3}); +// } + +// TEST(NewFixedMap, UpperBound) +// { +// constexpr NewFixedMap s1{{2, 20}, {4, 40}}; +// static_assert(s1.size() == 2); + +// static_assert(s1.upper_bound(1)->first == 2); +// static_assert(s1.upper_bound(2)->first == 4); +// static_assert(s1.upper_bound(3)->first == 4); +// static_assert(s1.upper_bound(4) == s1.cend()); +// static_assert(s1.upper_bound(5) == s1.cend()); +// } + +// TEST(NewFixedMap, UpperBound_TransparentComparator) +// { +// constexpr NewFixedMap> s{ +// {MockAComparableToB{1}, 10}, {MockAComparableToB{3}, 30}, {MockAComparableToB{5}, 50}}; +// constexpr MockBComparableToA b{3}; +// static_assert(s.upper_bound(b)->first == MockAComparableToB{5}); +// } + +// TEST(NewFixedMap, EqualRange) +// { +// constexpr NewFixedMap s1{{2, 20}, {4, 40}}; +// static_assert(s1.size() == 2); + +// static_assert(s1.equal_range(1).first == s1.lower_bound(1)); +// static_assert(s1.equal_range(1).second == s1.upper_bound(1)); + +// static_assert(s1.equal_range(2).first == s1.lower_bound(2)); +// static_assert(s1.equal_range(2).second == s1.upper_bound(2)); + +// static_assert(s1.equal_range(3).first == s1.lower_bound(3)); +// static_assert(s1.equal_range(3).second == s1.upper_bound(3)); + +// static_assert(s1.equal_range(4).first == s1.lower_bound(4)); +// static_assert(s1.equal_range(4).second == s1.upper_bound(4)); + +// static_assert(s1.equal_range(5).first == s1.lower_bound(5)); +// static_assert(s1.equal_range(5).second == s1.upper_bound(5)); +// } + +// TEST(NewFixedMap, EqualRange_TransparentComparator) +// { +// constexpr NewFixedMap> s{ +// {MockAComparableToB{1}, 10}, {MockAComparableToB{3}, 30}, {MockAComparableToB{5}, 50}}; +// constexpr MockBComparableToA b{3}; +// static_assert(s.equal_range(b).first == s.lower_bound(b)); +// static_assert(s.equal_range(b).second == s.upper_bound(b)); +// } + +TEST(NewFixedMap, Equality) +{ + { + constexpr NewFixedMap s1{{1, 10}, {4, 40}}; + constexpr NewFixedMap s2{{4, 40}, {1, 10}}; + constexpr NewFixedMap s3{{1, 10}, {3, 30}}; + constexpr NewFixedMap s4{{1, 10}}; + + static_assert(s1 == s2); + static_assert(s2 == s1); + + static_assert(s1 != s3); + static_assert(s3 != s1); + + static_assert(s1 != s4); + static_assert(s4 != s1); + } + + // Values + { + constexpr NewFixedMap s1{{1, 10}, {4, 40}}; + constexpr NewFixedMap s2{{1, 10}, {4, 44}}; + constexpr NewFixedMap s3{{1, 40}, {4, 10}}; + + static_assert(s1 != s2); + static_assert(s1 != s3); + } +} + +TEST(NewFixedMap, Ranges) +{ + NewFixedMap s1{{1, 10}, {4, 40}}; + auto f = s1 | ranges::views::filter([](const auto& v) -> bool { return v.second == 10; }); + + EXPECT_EQ(1, ranges::distance(f)); + int first_entry = (*f.begin()).second; // Can't use arrow with range-v3 because it requires + // l-value. Note that std::ranges works + EXPECT_EQ(10, first_entry); +} + +TEST(NewFixedMap, ClassTemplateArgumentDeduction) +{ + // Compile-only test + NewFixedMap a = NewFixedMap{}; + (void)a; +} + +TEST(NewFixedMap, NonDefaultConstructible) +{ + { + constexpr NewFixedMap s1{}; + static_assert(s1.empty()); + } + { + NewFixedMap s2{}; + s2.emplace(1, 3); + } +} + +TEST(NewFixedMap, MoveableButNotCopyable) +{ + { + NewFixedMap s{}; + s.emplace("", MockMoveableButNotCopyable{}); + } +} + +TEST(NewFixedMap, NonAssignable) +{ + { + NewFixedMap s{}; + s[1]; + s[2]; + s[3]; + + s.erase(2); + } +} + +static constexpr int INT_VALUE_10 = 10; +static constexpr int INT_VALUE_20 = 20; +static constexpr int INT_VALUE_30 = 30; + +TEST(NewFixedMap, ConstRef) +{ + { +#ifndef _LIBCPP_VERSION + std::map s{{1, INT_VALUE_10}}; + s.insert({2, INT_VALUE_20}); + s.emplace(3, INT_VALUE_30); + s.erase(3); + + auto s_copy = s; + s = s_copy; + s = std::move(s_copy); + + ASSERT_TRUE(s.contains(1)); + ASSERT_TRUE(s.contains(2)); + ASSERT_TRUE(!s.contains(3)); + ASSERT_TRUE(!s.contains(4)); + + ASSERT_EQ(INT_VALUE_10, s.at(1)); +#endif + } + + { + NewFixedMap s{{1, INT_VALUE_10}}; + s.insert({2, INT_VALUE_20}); + s.emplace(3, INT_VALUE_30); + s.erase(3); + + auto s_copy = s; + s = s_copy; + s = std::move(s_copy); + + ASSERT_TRUE(s.contains(1)); + ASSERT_TRUE(s.contains(2)); + ASSERT_TRUE(!s.contains(3)); + ASSERT_TRUE(!s.contains(4)); + + ASSERT_EQ(INT_VALUE_10, s.at(1)); + } + + { + constexpr NewFixedMap s1 = []() + { + NewFixedMap s{{1.0, INT_VALUE_10}}; + s.insert({2, INT_VALUE_20}); + s.emplace(3, INT_VALUE_30); + s.erase(3); + + auto s_copy = s; + s = s_copy; + s = std::move(s_copy); + + return s; + }(); + + static_assert(s1.contains(1)); + static_assert(s1.contains(2)); + static_assert(!s1.contains(3)); + static_assert(!s1.contains(4)); + + static_assert(s1.at(1) == INT_VALUE_10); + } + + static_assert(NotTriviallyCopyable); + static_assert(NotTriviallyCopyable>); +} + +namespace +{ +template /*INSTANCE*/> +struct NewFixedMapInstanceCanBeUsedAsATemplateParameter +{ +}; + +template /*INSTANCE*/> +constexpr void fixed_map_instance_can_be_used_as_a_template_parameter() +{ +} +} // namespace + +TEST(NewFixedMap, UsageAsTemplateParameter) +{ + static constexpr NewFixedMap INSTANCE1{}; + fixed_map_instance_can_be_used_as_a_template_parameter(); + NewFixedMapInstanceCanBeUsedAsATemplateParameter my_struct{}; + static_cast(my_struct); +} + +namespace +{ +struct NewFixedMapInstanceCounterUniquenessToken +{ +}; + +using InstanceCounterNonTrivialAssignment = + instance_counter::InstanceCounterNonTrivialAssignment; + +using NewFixedMapOfInstanceCounterNonTrivial = + NewFixedMap; +static_assert(!TriviallyCopyAssignable); +static_assert(!TriviallyMoveAssignable); +static_assert(!TriviallyDestructible); + +using InstanceCounterTrivialAssignment = + instance_counter::InstanceCounterTrivialAssignment; + +using NewFixedMapOfInstanceCounterTrivial = + NewFixedMap; +static_assert(TriviallyCopyAssignable); +static_assert(TriviallyMoveAssignable); +static_assert(!TriviallyDestructible); + +static_assert(NewFixedMapOfInstanceCounterNonTrivial::const_iterator{} == + NewFixedMapOfInstanceCounterNonTrivial::const_iterator{}); + +template +struct NewFixedMapInstanceCheckFixture : public ::testing::Test +{ +}; +TYPED_TEST_SUITE_P(NewFixedMapInstanceCheckFixture); +} // namespace + +TYPED_TEST_P(NewFixedMapInstanceCheckFixture, NewFixedMap_InstanceCheck) +{ + using MapOfInstanceCounterType = TypeParam; + using InstanceCounterType = typename MapOfInstanceCounterType::key_type; + static_assert(std::is_same_v); + MapOfInstanceCounterType v1{}; + + // [] l-value + ASSERT_EQ(0, InstanceCounterType::counter); + { // IMPORTANT SCOPE, don't remove. + // This will be destroyed when we go out of scope + InstanceCounterType aa{1}; + ASSERT_EQ(1, InstanceCounterType::counter); + v1[aa] = aa; + ASSERT_EQ(3, InstanceCounterType::counter); + v1[aa] = aa; + v1[aa] = aa; + v1[aa] = aa; + v1[aa] = aa; + v1[aa] = aa; + ASSERT_EQ(3, InstanceCounterType::counter); + v1.clear(); + ASSERT_EQ(1, InstanceCounterType::counter); + } + ASSERT_EQ(0, InstanceCounterType::counter); + + // Insert l-value + ASSERT_EQ(0, InstanceCounterType::counter); + { // IMPORTANT SCOPE, don't remove. + // This will be destroyed when we go out of scope + InstanceCounterType aa{1}; + ASSERT_EQ(1, InstanceCounterType::counter); + v1.insert({aa, aa}); + ASSERT_EQ(1, v1.size()); + ASSERT_EQ(3, InstanceCounterType::counter); + v1.insert({aa, aa}); + v1.insert({aa, aa}); + v1.insert({aa, aa}); + ASSERT_EQ(1, v1.size()); + ASSERT_EQ(3, InstanceCounterType::counter); + v1.clear(); + ASSERT_EQ(0, v1.size()); + ASSERT_EQ(1, InstanceCounterType::counter); + } + ASSERT_EQ(0, InstanceCounterType::counter); + + // Double clear + { + v1.clear(); + v1.clear(); + } + + // [] r-value + ASSERT_EQ(0, InstanceCounterType::counter); + { // IMPORTANT SCOPE, don't remove. + // This will be destroyed when we go out of scope + InstanceCounterType aa{1}; + InstanceCounterType bb{1}; + ASSERT_EQ(2, InstanceCounterType::counter); + v1[std::move(bb)] = std::move(aa); + ASSERT_EQ(1, v1.size()); + ASSERT_EQ(4, InstanceCounterType::counter); + v1.clear(); + ASSERT_EQ(0, v1.size()); + ASSERT_EQ(2, InstanceCounterType::counter); + v1[InstanceCounterType{}] = InstanceCounterType{}; // With temporary + v1[InstanceCounterType{}] = InstanceCounterType{}; // With temporary + v1[InstanceCounterType{}] = InstanceCounterType{}; // With temporary + ASSERT_EQ(1, v1.size()); + ASSERT_EQ(4, InstanceCounterType::counter); + } + ASSERT_EQ(2, InstanceCounterType::counter); + v1.clear(); + ASSERT_EQ(0, InstanceCounterType::counter); + + // insert r-value + ASSERT_EQ(0, InstanceCounterType::counter); + { // IMPORTANT SCOPE, don't remove. + // This will be destroyed when we go out of scope + InstanceCounterType aa{1}; + InstanceCounterType bb{1}; + ASSERT_EQ(2, InstanceCounterType::counter); + v1.insert({std::move(bb), std::move(aa)}); + ASSERT_EQ(1, v1.size()); + ASSERT_EQ(4, InstanceCounterType::counter); + v1.clear(); + ASSERT_EQ(0, v1.size()); + ASSERT_EQ(2, InstanceCounterType::counter); + v1.insert({InstanceCounterType{}, InstanceCounterType{}}); // With temporary + v1.insert({InstanceCounterType{}, InstanceCounterType{}}); // With temporary + v1.insert({InstanceCounterType{}, InstanceCounterType{}}); // With temporary + ASSERT_EQ(1, v1.size()); + ASSERT_EQ(4, InstanceCounterType::counter); + } + ASSERT_EQ(2, InstanceCounterType::counter); + v1.clear(); + ASSERT_EQ(0, InstanceCounterType::counter); + + // Emplace + ASSERT_EQ(0, InstanceCounterType::counter); + { // IMPORTANT SCOPE, don't remove. + // This will be destroyed when we go out of scope + InstanceCounterType aa{1}; + ASSERT_EQ(1, InstanceCounterType::counter); + v1.emplace(aa, aa); + ASSERT_EQ(1, v1.size()); + ASSERT_EQ(3, InstanceCounterType::counter); + v1.emplace(aa, aa); + v1.emplace(aa, aa); + v1.emplace(aa, aa); + ASSERT_EQ(1, v1.size()); + ASSERT_EQ(3, InstanceCounterType::counter); + v1.clear(); + ASSERT_EQ(0, v1.size()); + ASSERT_EQ(1, InstanceCounterType::counter); + } + ASSERT_EQ(0, InstanceCounterType::counter); + + // Try-Emplace + ASSERT_EQ(0, InstanceCounterType::counter); + { // IMPORTANT SCOPE, don't remove. + // This will be destroyed when we go out of scope + InstanceCounterType aa{1}; + ASSERT_EQ(1, InstanceCounterType::counter); + v1.try_emplace(aa, aa); + ASSERT_EQ(1, v1.size()); + ASSERT_EQ(3, InstanceCounterType::counter); + v1.try_emplace(aa, aa); + v1.try_emplace(aa, aa); + v1.try_emplace(std::move(aa), InstanceCounterType{1}); + ASSERT_EQ(1, v1.size()); + ASSERT_EQ(3, InstanceCounterType::counter); + v1.clear(); + ASSERT_EQ(0, v1.size()); + ASSERT_EQ(1, InstanceCounterType::counter); + } + ASSERT_EQ(0, InstanceCounterType::counter); + + // Erase with iterators + { + for (int i = 0; i < 10; i++) + { + v1[InstanceCounterType{i}] = InstanceCounterType{i}; + } + ASSERT_EQ(10, v1.size()); + ASSERT_EQ(20, InstanceCounterType::counter); + v1.erase(v1.begin()); + ASSERT_EQ(9, v1.size()); + ASSERT_EQ(18, InstanceCounterType::counter); + v1.erase(std::next(v1.begin(), 2), std::next(v1.begin(), 5)); + ASSERT_EQ(6, v1.size()); + ASSERT_EQ(12, InstanceCounterType::counter); + v1.erase(v1.cbegin()); + ASSERT_EQ(5, v1.size()); + ASSERT_EQ(10, InstanceCounterType::counter); + v1.erase(v1.begin(), v1.end()); + ASSERT_EQ(0, v1.size()); + ASSERT_EQ(0, InstanceCounterType::counter); + } + + // Erase with key + { + for (int i = 0; i < 10; i++) + { + v1[InstanceCounterType{i}] = InstanceCounterType{i}; + } + ASSERT_EQ(10, v1.size()); + ASSERT_EQ(20, InstanceCounterType::counter); + v1.erase(InstanceCounterType{5}); + ASSERT_EQ(9, v1.size()); + ASSERT_EQ(18, InstanceCounterType::counter); + v1.erase(InstanceCounterType{995}); // not in map + ASSERT_EQ(9, v1.size()); + ASSERT_EQ(18, InstanceCounterType::counter); + v1.erase(InstanceCounterType{7}); + ASSERT_EQ(8, v1.size()); + ASSERT_EQ(16, InstanceCounterType::counter); + v1.clear(); + ASSERT_EQ(0, v1.size()); + ASSERT_EQ(0, InstanceCounterType::counter); + } + + ASSERT_EQ(0, InstanceCounterType::counter); + v1[InstanceCounterType{1}] = InstanceCounterType{1}; + v1[InstanceCounterType{2}] = InstanceCounterType{2}; + ASSERT_EQ(4, InstanceCounterType::counter); + + { // IMPORTANT SCOPE, don't remove. + MapOfInstanceCounterType v2{v1}; + ASSERT_EQ(8, InstanceCounterType::counter); + } + ASSERT_EQ(4, InstanceCounterType::counter); + + { // IMPORTANT SCOPE, don't remove. + MapOfInstanceCounterType v2 = v1; + ASSERT_EQ(8, InstanceCounterType::counter); + v1 = v2; + ASSERT_EQ(8, InstanceCounterType::counter); + } + ASSERT_EQ(4, InstanceCounterType::counter); + + { // IMPORTANT SCOPE, don't remove. + MapOfInstanceCounterType v2{std::move(v1)}; + ASSERT_EQ(4, InstanceCounterType::counter); + } + ASSERT_EQ(0, InstanceCounterType::counter); + v1[InstanceCounterType{1}] = InstanceCounterType{1}; + v1[InstanceCounterType{2}] = InstanceCounterType{2}; + ASSERT_EQ(4, InstanceCounterType::counter); + + { // IMPORTANT SCOPE, don't remove. + MapOfInstanceCounterType v2 = std::move(v1); + ASSERT_EQ(4, InstanceCounterType::counter); + } + ASSERT_EQ(0, InstanceCounterType::counter); + + // Lookup + { + for (int i = 0; i < 10; i++) + { + v1[InstanceCounterType{i}] = InstanceCounterType{i}; + } + + const auto v2 = v1; + ASSERT_EQ(10, v1.size()); + ASSERT_EQ(10, v2.size()); + ASSERT_EQ(40, InstanceCounterType::counter); + + (void)v1.find(InstanceCounterType{5}); + (void)v1.find(InstanceCounterType{995}); + (void)v2.find(InstanceCounterType{5}); + (void)v2.find(InstanceCounterType{995}); + ASSERT_EQ(10, v1.size()); + ASSERT_EQ(10, v2.size()); + ASSERT_EQ(40, InstanceCounterType::counter); + + (void)v1.contains(InstanceCounterType{5}); + (void)v1.contains(InstanceCounterType{995}); + (void)v2.contains(InstanceCounterType{5}); + (void)v2.contains(InstanceCounterType{995}); + ASSERT_EQ(10, v1.size()); + ASSERT_EQ(10, v2.size()); + ASSERT_EQ(40, InstanceCounterType::counter); + + (void)v1.count(InstanceCounterType{5}); + (void)v1.count(InstanceCounterType{995}); + (void)v2.count(InstanceCounterType{5}); + (void)v2.count(InstanceCounterType{995}); + ASSERT_EQ(10, v1.size()); + ASSERT_EQ(10, v2.size()); + ASSERT_EQ(40, InstanceCounterType::counter); + + v1.clear(); + ASSERT_EQ(0, v1.size()); + ASSERT_EQ(20, InstanceCounterType::counter); + } + + ASSERT_EQ(0, InstanceCounterType::counter); + + v1.clear(); + ASSERT_EQ(0, v1.size()); + ASSERT_EQ(0, InstanceCounterType::counter); +} + +REGISTER_TYPED_TEST_SUITE_P(NewFixedMapInstanceCheckFixture, NewFixedMap_InstanceCheck); + +// We want same semantics as std::map, so run it with std::map as well +using NewFixedMapInstanceCheckTypes = testing::Types< + std::map, + std::map, + NewFixedMap, + NewFixedMap>; + +INSTANTIATE_TYPED_TEST_SUITE_P(NewFixedMap, + NewFixedMapInstanceCheckFixture, + NewFixedMapInstanceCheckTypes, + NameProviderForTypeParameterizedTest); + +} // namespace fixed_containers + +namespace another_namespace_unrelated_to_the_fixed_containers_namespace +{ +TEST(NewFixedMap, ArgumentDependentLookup) +{ + // Compile-only test + fixed_containers::NewFixedMap a{}; + erase_if(a, [](auto&&) { return true; }); + (void)is_full(a); +} +} // namespace another_namespace_unrelated_to_the_fixed_containers_namespace