1 // Copyright 2020 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 #pragma once 15 16 #include <algorithm> 17 #include <array> 18 #include <cstddef> 19 #include <cstdint> 20 #include <iterator> 21 #include <type_traits> 22 23 #include "pw_assert/assert.h" 24 25 namespace pw::containers { 26 27 // Define and use a custom Pair object. This is because std::pair does not 28 // support constexpr assignment until C++20. The assignment is needed since 29 // the array of pairs will be sorted in the constructor (if not already). 30 template <typename First, typename Second> 31 struct Pair { 32 First first; 33 Second second; 34 35 bool operator==(const Pair& p) const { 36 return first == p.first && second == p.second; 37 } 38 39 bool operator!=(const Pair& p) const { return !(*this == p); } 40 }; 41 42 template <typename T1, typename T2> 43 Pair(T1, T2) -> Pair<T1, T2>; 44 45 /// A simple, fixed-size associative array with lookup by key or value. 46 /// 47 /// FlatMaps can be initialized by: 48 /// @rst 49 /// .. literalinclude:: examples/flat_map.cc 50 /// :language: cpp 51 /// :linenos: 52 /// :start-after: [pw_containers-flat_map] 53 /// :end-before: [pw_containers-flat_map] 54 /// @endrst 55 /// 56 /// The keys do not need to be sorted as the constructor will sort the items 57 /// if need be. 58 template <typename Key, typename Value, size_t kArraySize> 59 class FlatMap { 60 public: 61 using key_type = Key; 62 using mapped_type = Value; 63 using value_type = Pair<key_type, mapped_type>; 64 using pointer = value_type*; 65 using reference = value_type&; 66 using size_type = size_t; 67 using difference_type = ptrdiff_t; 68 using container_type = typename std::array<value_type, kArraySize>; 69 using iterator = typename container_type::iterator; 70 using const_iterator = typename container_type::const_iterator; 71 72 /// A bidirectional iterator for the mapped values. 73 /// 74 /// Also an output iterator. 75 class mapped_iterator { 76 public: 77 using value_type = FlatMap::mapped_type; 78 using difference_type = std::ptrdiff_t; 79 using pointer = value_type*; 80 using reference = value_type&; 81 using iterator_category = std::bidirectional_iterator_tag; 82 mapped_iterator()83 constexpr mapped_iterator() : current_{} {} 84 85 constexpr mapped_iterator(const mapped_iterator& other) = default; 86 constexpr mapped_iterator& operator=(const mapped_iterator& other) = 87 default; 88 89 constexpr reference operator*() const { 90 reference value = current_->second; 91 return value; 92 } 93 94 constexpr pointer operator->() const { return &operator*(); } 95 96 constexpr mapped_iterator& operator++() { 97 ++current_; 98 return *this; 99 } 100 101 constexpr mapped_iterator operator++(int) { 102 mapped_iterator original = *this; 103 operator++(); 104 return original; 105 } 106 107 constexpr mapped_iterator& operator--() { 108 --current_; 109 return *this; 110 } 111 112 constexpr mapped_iterator operator--(int) { 113 mapped_iterator original = *this; 114 operator--(); 115 return original; 116 } 117 118 constexpr bool operator==(const mapped_iterator& other) const { 119 return current_ == other.current_; 120 } 121 122 constexpr bool operator!=(const mapped_iterator& other) const { 123 return !(*this == other); 124 } 125 126 private: 127 friend class FlatMap; 128 mapped_iterator(FlatMap::iterator current)129 constexpr mapped_iterator(FlatMap::iterator current) : current_(current) {} 130 131 FlatMap::iterator current_; 132 }; 133 FlatMap(const std::array<value_type,kArraySize> & items)134 constexpr FlatMap(const std::array<value_type, kArraySize>& items) 135 : items_(items) { 136 ConstexprSort(items_.begin(), kArraySize); 137 } 138 139 // Omits explicit here to support assignment-like syntax, which is common to 140 // initialize a container. 141 template <typename... Items, 142 typename = std::enable_if_t< 143 std::conjunction_v<std::is_same<Items, value_type>...>>> FlatMap(const Items &...items)144 constexpr FlatMap(const Items&... items) : FlatMap(std::array{items...}) {} 145 146 FlatMap(FlatMap&) = delete; 147 FlatMap& operator=(FlatMap&) = delete; 148 149 // Capacity. size()150 constexpr size_type size() const { return kArraySize; } empty()151 constexpr size_type empty() const { return size() == 0; } max_size()152 constexpr size_type max_size() const { return kArraySize; } 153 154 // Lookup. 155 /// Accesses a mutable mapped value. 156 /// 157 /// @pre The key must exist. 158 /// 159 /// @param[in] key The key to the mapped value. 160 /// 161 /// @returns A reference to the mapped value. at(const key_type & key)162 constexpr mapped_type& at(const key_type& key) { 163 PW_ASSERT(end() != begin()); 164 iterator it = std::lower_bound( 165 items_.begin(), items_.end(), key, IsItemKeyLessThanGivenKey); 166 const key_type& found_key = it->first; 167 PW_ASSERT(it != items_.end() && found_key == key); 168 mapped_type& found_value = it->second; 169 return found_value; 170 } 171 172 /// Accesses a mapped value. 173 /// 174 /// @pre The key must exist. 175 /// 176 /// @param[in] key The key to the mapped value. 177 /// 178 /// @returns A const reference to the mapped value. at(const key_type & key)179 constexpr const mapped_type& at(const key_type& key) const { 180 PW_ASSERT(end() != begin()); 181 const_iterator it = std::lower_bound( 182 items_.cbegin(), items_.cend(), key, IsItemKeyLessThanGivenKey); 183 const key_type& found_key = it->first; 184 PW_ASSERT(it != items_.cend() && found_key == key); 185 const mapped_type& found_value = it->second; 186 return found_value; 187 } 188 contains(const key_type & key)189 constexpr bool contains(const key_type& key) const { 190 return find(key) != end(); 191 } 192 find(const key_type & key)193 constexpr const_iterator find(const key_type& key) const { 194 if (end() == begin()) { 195 return end(); 196 } 197 198 const_iterator it = lower_bound(key); 199 if (it == end() || it->first != key) { 200 return end(); 201 } 202 return it; 203 } 204 lower_bound(const key_type & key)205 constexpr const_iterator lower_bound(const key_type& key) const { 206 return std::lower_bound(begin(), end(), key, IsItemKeyLessThanGivenKey); 207 } 208 upper_bound(const key_type & key)209 constexpr const_iterator upper_bound(const key_type& key) const { 210 return std::upper_bound( 211 begin(), end(), key, [](key_type lkey, const value_type& item) { 212 return item.first > lkey; 213 }); 214 } 215 equal_range(const key_type & key)216 constexpr std::pair<const_iterator, const_iterator> equal_range( 217 const key_type& key) const { 218 if (end() == begin()) { 219 return std::make_pair(end(), end()); 220 } 221 222 return std::make_pair(lower_bound(key), upper_bound(key)); 223 } 224 225 // Iterators. begin()226 constexpr const_iterator begin() const { return cbegin(); } cbegin()227 constexpr const_iterator cbegin() const { return items_.cbegin(); } end()228 constexpr const_iterator end() const { return cend(); } cend()229 constexpr const_iterator cend() const { return items_.cend(); } 230 231 /// Gets an iterator to the first mapped value. 232 /// 233 /// Mapped iterators iterate through the mapped values, and allow mutation of 234 /// those values (for a non-const FlatMap object). 235 /// 236 /// @returns An iterator to the first mapped value. mapped_begin()237 constexpr mapped_iterator mapped_begin() { 238 return mapped_iterator(items_.begin()); 239 } 240 241 /// Gets an iterator to one past the last mapped value. 242 /// 243 /// Mapped iterators iterate through the mapped values, and allow mutation of 244 /// those values (for a non-const FlatMap object). 245 /// 246 /// @returns An iterator to one past the last mapped value. mapped_end()247 constexpr mapped_iterator mapped_end() { 248 return mapped_iterator(items_.end()); 249 } 250 251 private: 252 // Simple stable insertion sort function for constexpr support. 253 // std::stable_sort is not constexpr. Should not be a problem with performance 254 // in regards to the sizes that are typically dealt with. ConstexprSort(iterator data,size_type size)255 static constexpr void ConstexprSort(iterator data, size_type size) { 256 if (size < 2) { 257 return; 258 } 259 260 for (iterator it = data + 1, 261 end = data + static_cast<difference_type>(size); 262 it < end; 263 ++it) { 264 if (it->first < it[-1].first) { 265 // Rotate the value into place. 266 value_type temp = std::move(*it); 267 iterator it2 = it - 1; 268 while (true) { 269 *(it2 + 1) = std::move(*it2); 270 if (it2 == data || !(temp.first < it2[-1].first)) { 271 break; 272 } 273 --it2; 274 } 275 *it2 = std::move(temp); 276 } 277 } 278 } 279 IsItemKeyLessThanGivenKey(const value_type & item,key_type key)280 static constexpr bool IsItemKeyLessThanGivenKey(const value_type& item, 281 key_type key) { 282 const key_type& item_key = item.first; 283 return item_key < key; 284 } 285 286 std::array<value_type, kArraySize> items_; 287 }; 288 289 template <typename K, typename V, typename... Items> 290 FlatMap(const Pair<K, V>& item1, const Items&... items) 291 -> FlatMap<K, V, 1 + sizeof...(items)>; 292 293 } // namespace pw::containers 294