1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://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,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #ifndef API_BASE_CONTAINERS_UNORDERED_MAP_H
17 #define API_BASE_CONTAINERS_UNORDERED_MAP_H
18 
19 #include <cstddef>
20 #include <cstdint>
21 
22 #include <base/containers/iterator.h>
23 #include <base/containers/string.h>
24 #include <base/containers/string_view.h>
25 #include <base/containers/vector.h>
26 #include <base/namespace.h>
27 #include <base/util/log.h>
28 
29 BASE_BEGIN_NAMESPACE()
30 template<class T>
31 class unordered_map_iterator;
32 template<class T>
33 class const_unordered_map_iterator;
34 
35 template<class T1, class T2>
36 struct pair {
37     using first_type = T1;
38     using second_type = T2;
39     first_type first;
40     second_type second;
41 };
42 
43 #ifdef BASE_STD_COMPATIBILITY
44 using forward_iterator_tag = std::forward_iterator_tag; // yeah. we need this for std compatibility.
45 #else
46 struct forward_iterator_tag {};
47 #endif
48 
49 template<class T>
50 class const_unordered_map_iterator {
51 public:
52     using base_container = T;
53 
54     using iterator_category = forward_iterator_tag;
55     using value_type = typename base_container::value_type;
56     using difference_type = ptrdiff_t;
57     using pointer = typename base_container::const_pointer;
58     using reference = typename base_container::const_reference;
59 
60     using node_type = typename base_container::list_node;
61     using iterator = typename base_container::const_iterator;
62 
63     constexpr const_unordered_map_iterator() noexcept = default;
const_unordered_map_iterator(const typename base_container::iterator & other)64     constexpr const_unordered_map_iterator(const typename base_container::iterator& other) noexcept
65         : owner_ { other.owner_ }, it_ { other.it_ }
66     {}
67 
68     constexpr reference operator*() const noexcept
69     {
70         BASE_ASSERT(owner_ && it_);
71         return it_->data;
72     }
73     constexpr pointer operator->() const noexcept
74     {
75         BASE_ASSERT(owner_ && it_);
76         return &it_->data;
77     }
78 
79     constexpr bool operator==(const iterator& other) const noexcept
80     {
81         return ((owner_ == other.owner_) && (it_ == other.it_));
82     }
83     constexpr bool operator!=(const iterator& other) const noexcept
84     {
85         return ((owner_ != other.owner_) || (it_ != other.it_));
86     }
87 
88     constexpr bool operator==(const typename base_container::iterator& other) const noexcept
89     {
90         return ((owner_ == other.owner_) && (it_ == other.it_));
91     }
92     constexpr bool operator!=(const typename base_container::iterator& other) const noexcept
93     {
94         return ((owner_ != other.owner_) || (it_ != other.it_));
95     }
96     constexpr iterator& operator++() noexcept
97     {
98         it_ = owner_->advance(*this);
99         return *this;
100     }
101 
102 protected:
const_unordered_map_iterator(const T & owner,const node_type * ptr)103     constexpr explicit const_unordered_map_iterator(const T& owner, const node_type* ptr) noexcept
104         : owner_(&owner), it_ { ptr }
105     {}
106     friend T;
107     friend class unordered_map_iterator<T>;
108     const T* owner_ { nullptr };
109     const node_type* it_ { nullptr };
110 };
111 
112 template<class T>
113 class unordered_map_iterator {
114 public:
115     using base_container = T;
116 
117     using iterator_category = forward_iterator_tag;
118     using value_type = typename base_container::value_type;
119     using difference_type = ptrdiff_t;
120     using pointer = typename base_container::pointer;
121     using reference = typename base_container::reference;
122 
123     using node_type = typename base_container::list_node;
124     using iterator = typename base_container::iterator;
125     using const_iterator = typename base_container::const_iterator;
126 
127     constexpr unordered_map_iterator() noexcept = default;
128 
129     constexpr reference operator*() const noexcept
130     {
131         BASE_ASSERT(owner_ && it_);
132         return it_->data;
133     }
134     constexpr pointer operator->() const noexcept
135     {
136         BASE_ASSERT(owner_ && it_);
137         return &it_->data;
138     }
139     constexpr reference operator*() noexcept
140     {
141         BASE_ASSERT(owner_ && it_);
142         return it_->data;
143     }
144     constexpr pointer operator->() noexcept
145     {
146         BASE_ASSERT(owner_ && it_);
147         return &it_->data;
148     }
149 
150     constexpr bool operator==(const iterator& other) const noexcept
151     {
152         return ((owner_ == other.owner_) && (it_ == other.it_));
153     }
154     constexpr bool operator!=(const iterator& other) const noexcept
155     {
156         return ((owner_ != other.owner_) || (it_ != other.it_));
157     }
158 
159     constexpr bool operator==(const typename base_container::const_iterator& other) const noexcept
160     {
161         return ((owner_ == other.owner_) && (it_ == other.it_));
162     }
163     constexpr bool operator!=(const typename base_container::const_iterator& other) const noexcept
164     {
165         return ((owner_ != other.owner_) || (it_ != other.it_));
166     }
167 
168     constexpr iterator& operator++() noexcept
169     {
170         it_ = owner_->advance(*this);
171         return *this;
172     }
173 
174 protected:
unordered_map_iterator(T & owner,node_type * ptr)175     constexpr explicit unordered_map_iterator(T& owner, node_type* ptr) noexcept : owner_ { &owner }, it_ { ptr } {}
176     friend T;
177     friend const_unordered_map_iterator<T>;
178     T* owner_ { nullptr };
179     node_type* it_ { nullptr };
180 };
181 
182 template<class data_type>
183 struct node_handle {
184     using key_type = typename data_type::value_type::first_type;
185     using mapped_type = typename data_type::value_type::second_type;
186     using value_type = data_type;
emptynode_handle187     [[nodiscard]] bool empty() const noexcept
188     {
189         return pointer == nullptr;
190     }
191     explicit operator bool() const noexcept
192     {
193         return !empty();
194     }
keynode_handle195     key_type& key() const
196     {
197         return pointer->data.first;
198     }
mappednode_handle199     mapped_type& mapped() const
200     {
201         return pointer->data.second;
202     }
203     data_type* pointer { nullptr };
204 };
205 
206 template<class Key, class T>
207 class unordered_map_base {
208     constexpr static uint32_t DEFAULT_SHIFT_AMOUNT = 4; // 1<<4 (16) initial buckets, seems to match ms stl.
get_sa(size_t const count,uint32_t const sa)209     constexpr uint32_t get_sa(size_t const count, uint32_t const sa)
210     {
211         uint32_t ret = sa;
212         while ((size_t(1) << ret) < count) {
213             ret++;
214         }
215         return ret;
216     }
217 
218 public:
219     using key_type = Key;
220     using mapped_type = T;
221     using value_type = pair<const Key, T>;
222     using reference = value_type&;
223     using const_reference = const value_type&;
224     using pointer = value_type*;
225     using const_pointer = const value_type*;
226     using iterator = BASE_NS::unordered_map_iterator<unordered_map_base<Key, T>>;
227     using const_iterator = BASE_NS::const_unordered_map_iterator<unordered_map_base<Key, T>>;
228     struct list_node {
229         using value_type = pair<const Key, T>;
230         using first_type = typename value_type::first_type;
231         using second_type = typename value_type::second_type;
232         value_type data {};
233         struct list_node* prev { nullptr };
234         struct list_node* next { nullptr };
235 
list_nodelist_node236         list_node(const Key& x, const T& y) : data { x, y } {}
list_nodelist_node237         list_node(Key&& x, const T& y) : data { BASE_NS::forward<Key>(x), y } {}
list_nodelist_node238         list_node(Key&& x, T&& y) : data { BASE_NS::forward<Key>(x), BASE_NS::forward<T>(y) } {}
list_nodelist_node239         list_node(const Key& x, T&& y) : data { x, BASE_NS::forward<T>(y) } {}
list_nodelist_node240         explicit list_node(value_type&& val) : data { BASE_NS::forward<value_type>(val) } {}
list_nodelist_node241         explicit list_node(const value_type& val) : data { val } {}
242     };
243     using node_type = node_handle<list_node>;
244 
245     unordered_map_base() = default;
unordered_map_base(size_t bucket_count)246     explicit unordered_map_base(size_t bucket_count)
247         : shift_amount_ { get_sa(bucket_count, DEFAULT_SHIFT_AMOUNT) }, buckets_ { 1ull << shift_amount_ }
248     {}
~unordered_map_base()249     ~unordered_map_base()
250     {
251         clear();
252     }
reserve(size_t count)253     void reserve(size_t count)
254     {
255         const uint32_t new_shift_amount = get_sa(count, DEFAULT_SHIFT_AMOUNT);
256         if (new_shift_amount > shift_amount_) {
257             shift_amount_ = new_shift_amount - 1;
258             rehash();
259         }
260     }
clear()261     void clear()
262     {
263         if (!empty()) {
264             for (auto t : buckets_) {
265                 auto next = t;
266                 for (; next != nullptr;) {
267                     auto b = next;
268                     next = b->next;
269                     release(b);
270                 }
271             }
272             buckets_.clear();
273             buckets_.resize(1ull << shift_amount_);
274             size_ = 0;
275         }
276     }
unordered_map_base(unordered_map_base && other)277     unordered_map_base(unordered_map_base&& other) noexcept
278         : size_ { other.size_ }, shift_amount_ { other.shift_amount_ }, buckets_ { BASE_NS::move(other.buckets_) }
279     {
280         // move..
281         other.size_ = 0;
282         other.shift_amount_ = 0;
283     }
unordered_map_base(const unordered_map_base & other)284     unordered_map_base(const unordered_map_base& other)
285         : shift_amount_ { other.shift_amount_ }, buckets_ { 1ull << shift_amount_ }
286     {
287         // copy.
288         for (auto b : other.buckets_) {
289             list_node* last = nullptr;
290             if (b) {
291                 auto ind = index(b->data.first);
292                 for (; b != nullptr; b = b->next) {
293                     auto nb = allocate(b->data);
294                     if (last == nullptr) {
295                         last = buckets_[ind] = nb;
296                     } else {
297                         nb->prev = last;
298                         last = last->next = nb;
299                     }
300                     size_++;
301                 }
302             }
303         }
304     }
empty()305     bool empty() const
306     {
307         return size_ == 0;
308     }
size()309     size_t size() const
310     {
311         return size_;
312     }
erase(const_iterator pos)313     iterator erase(const_iterator pos)
314     {
315         if (pos.owner_ != this || !pos.it_) {
316             return end();
317         }
318         list_node* node = nullptr;
319         const auto ind = index(pos.it_->data.first);
320 
321         auto next = pos.it_->next;
322         // link prev to next or set bucket start to next if this was the first node.
323         // if this was the last prev-next will be null or bucket will be empty.
324         if (pos.it_->prev) {
325             BASE_ASSERT(pos.it_ == pos.it_->prev->next);
326             node = pos.it_->prev->next;
327             pos.it_->prev->next = next;
328         } else {
329             BASE_ASSERT(pos.it_ == buckets_[ind]);
330             node = buckets_[ind];
331             buckets_[ind] = next;
332         }
333 
334         // link next to prev or if this was the last node look or the next bucket with items.
335         // if this was the first next-prev will be null
336         if (next) {
337             next->prev = node->prev;
338         } else {
339             // okay, advance to next bucket..
340             for (auto i = ind + 1; i < buckets_.size(); ++i) {
341                 next = buckets_[i];
342                 if (next) {
343                     break;
344                 }
345             }
346         }
347         --size_;
348         release(node);
349 
350         return iterator { *this, next };
351     }
erase(const key_type & key)352     size_t erase(const key_type& key)
353     {
354         if (auto entry = detach_entry(key)) {
355             release(entry);
356             return 1u;
357         }
358         return 0u;
359     }
extract(const key_type & key)360     node_type extract(const key_type& key)
361     {
362         return node_type { detach_entry(key) };
363     }
insert(value_type && v)364     pair<iterator, bool> insert(value_type&& v)
365     {
366         const auto ind = index(v.first);
367         auto entry = get_entry(ind, v.first);
368         if (entry) {
369             return { iterator { *this, entry }, false };
370         }
371         return { iterator { *this, create_entry(ind, BASE_NS::forward<value_type>(v)) }, true };
372     }
insert(const value_type & v)373     pair<iterator, bool> insert(const value_type& v)
374     {
375         const auto ind = index(v.first);
376         auto entry = get_entry(ind, v.first);
377         if (entry) {
378             return { iterator { *this, entry }, false };
379         }
380         return { iterator { *this, create_entry(ind, v.first, v.second) }, true };
381     }
insert(node_type && nh)382     auto insert(node_type&& nh)
383     {
384         struct insert_return_type {
385             iterator position;
386             bool inserted;
387             node_type node;
388         };
389         if (nh.empty()) {
390             return insert_return_type { end(), false };
391         }
392         const auto& key = nh.pointer->data.first;
393         const auto ind = index(key);
394         auto res = get_entry(ind, key);
395         if (res) {
396             return insert_return_type { iterator { *this, res }, false, nh };
397         }
398         auto nl = nh.pointer;
399         nh.pointer = nullptr;
400         return insert_return_type { iterator { *this, create_entry(ind, nl) }, true };
401     }
402     template<class M>
insert_or_assign(const key_type & key,M && value)403     pair<iterator, bool> insert_or_assign(const key_type& key, M&& value)
404     {
405         const auto ind = index(key);
406         auto res = get_entry(ind, key);
407         if (res) {
408             res->data.second = BASE_NS::forward<M>(value);
409             return { iterator { *this, res }, false };
410         }
411         auto nl = allocate(key, BASE_NS::forward<M>(value));
412         return { iterator { *this, create_entry(ind, nl) }, true };
413     }
begin()414     iterator begin() noexcept
415     {
416         for (auto t : buckets_) {
417             if (t) {
418                 return iterator { *this, t };
419             }
420         }
421         return end();
422     }
begin()423     const_iterator begin() const noexcept
424     {
425         for (auto t : buckets_) {
426             if (t) {
427                 return const_iterator { *this, t };
428             }
429         }
430         return end();
431     }
cbegin()432     const_iterator cbegin() const noexcept
433     {
434         return begin();
435     }
436 
end()437     iterator end() noexcept
438     {
439         return iterator { *this, nullptr };
440     }
end()441     const_iterator end() const noexcept
442     {
443         return const_iterator { *this, nullptr };
444     }
cend()445     const_iterator cend() const noexcept
446     {
447         return end();
448     }
find(const key_type & key)449     iterator find(const key_type& key)
450     {
451         return iterator { *this, get_entry(index(key), key) };
452     }
find(const key_type & key)453     const_iterator find(const key_type& key) const
454     {
455         return const_iterator { *this, get_entry(index(key), key) };
456     }
contains(const key_type & key)457     bool contains(const key_type& key) const
458     {
459         return (get_entry(index(key), key) != nullptr);
460     }
count(const key_type & key)461     size_t count(const key_type& key) const
462     {
463         return contains(key) ? 1U : 0U;
464     }
465     mapped_type& operator[](const key_type& key)
466     {
467         const auto ind = index(key);
468         auto b = get_entry(ind, key);
469         if (b) {
470             return b->data.second;
471         }
472         return create_entry(ind, key)->data.second;
473     }
474     mapped_type& operator[](key_type&& key)
475     {
476         const auto ind = index(key);
477         auto b = get_entry(ind, key);
478         if (b) {
479             return b->data.second;
480         }
481         return create_entry(ind, BASE_NS::forward<key_type>(key))->data.second;
482     }
483 
484     unordered_map_base& operator=(unordered_map_base&& other) noexcept
485     {
486         if (&other != this) {
487             // move..
488             clear();
489             size_ = other.size_;
490             other.size_ = 0;
491             shift_amount_ = other.shift_amount_;
492             buckets_ = BASE_NS::move(other.buckets_);
493         }
494         return *this;
495     }
496     unordered_map_base& operator=(const unordered_map_base& other)
497     {
498         if (&other != this) {
499             // copy.
500             clear();
501             shift_amount_ = other.shift_amount_;
502             buckets_.resize(1ull << shift_amount_);
503             for (auto b : other.buckets_) {
504                 list_node* last = nullptr;
505                 if (b) {
506                     uint32_t ind = index(b->data.first);
507                     for (; b != nullptr; b = b->next) {
508                         auto nb = allocate(b->data);
509                         if (last == nullptr) {
510                             last = buckets_[ind] = nb;
511                         } else {
512                             nb->prev = last;
513                             last = last->next = nb;
514                         }
515                         size_++;
516                     }
517                 }
518             }
519         }
520         return *this;
521     }
522 
523 protected:
524     friend iterator;
525     friend const_iterator;
526 
527     // helpers for the iterators. (perhaps link the nodes across buckets?)
528     list_node* advance(const const_iterator& it, uint32_t count = 1) const
529     {
530         list_node* next = nullptr;
531         while (count--) {
532             if (it.it_->next) {
533                 next = it.it_->next;
534             } else {
535                 // okay, advance to next bucket..
536                 uint32_t ind = index(it.it_->data.first);
537                 for (;;) {
538                     ind++;
539                     if (ind == buckets_.size()) {
540                         next = nullptr;
541                         break;
542                     }
543                     next = buckets_[ind];
544                     if (next) {
545                         break;
546                     }
547                 }
548             }
549         }
550         return next;
551     }
552 
create_entry(uint32_t ind,const key_type & key)553     list_node* create_entry(uint32_t ind, const key_type& key)
554     {
555         return create_entry(ind, allocate(key, mapped_type {}));
556     }
create_entry(uint32_t ind,key_type && key)557     list_node* create_entry(uint32_t ind, key_type&& key)
558     {
559         return create_entry(ind, allocate(BASE_NS::forward<key_type>(key), mapped_type {}));
560     }
create_entry(uint32_t ind,const key_type & key,const mapped_type & value)561     list_node* create_entry(uint32_t ind, const key_type& key, const mapped_type& value)
562     {
563         return create_entry(ind, allocate(key, value));
564     }
create_entry(uint32_t ind,key_type && key,mapped_type && value)565     list_node* create_entry(uint32_t ind, key_type&& key, mapped_type&& value)
566     {
567         return create_entry(ind, allocate(BASE_NS::forward<key_type>(key), BASE_NS::forward<mapped_type>(value)));
568     }
create_entry(uint32_t ind,value_type && v)569     list_node* create_entry(uint32_t ind, value_type&& v)
570     {
571         return create_entry(ind, allocate(BASE_NS::forward<value_type>(v)));
572     }
573 
574     template<class... Args>
allocate(Args &&...args)575     list_node* allocate(Args&&... args)
576     {
577         auto alloc = buckets_.getAllocator();
578         auto node = static_cast<list_node*>(alloc.alloc(alloc.instance, sizeof(list_node)));
579         ::new (node) list_node { BASE_NS::forward<Args>(args)... };
580         return node;
581     }
582 
release(list_node * node)583     void release(list_node* node)
584     {
585         if constexpr (!__is_trivially_destructible(list_node)) {
586             node->~list_node();
587         }
588         auto alloc = buckets_.getAllocator();
589         alloc.free(alloc.instance, node);
590     }
591 
create_entry(uint32_t ind,list_node * nh)592     list_node* create_entry(uint32_t ind, list_node* nh)
593     {
594         // check load level.. and resize/rehash if needed
595         bool resize = false;
596         if (buckets_.empty()) {
597             resize = true;
598         } else {
599             const float load = size_ / static_cast<float>(buckets_.size());
600             resize = (load > 0.7f);
601         }
602         if (resize) {
603             rehash();
604             // need to recalculate/rehash the index
605             ind = index(nh->data.first);
606         }
607         // okay. so add it as first..
608         auto old = buckets_[ind];
609         buckets_[ind] = nh;
610         buckets_[ind]->next = old;
611         if (old) {
612             old->prev = buckets_[ind];
613         }
614         size_++;
615         return buckets_[ind];
616     }
617     template<class k>
get_entry(uint32_t index,const k & key)618     list_node* get_entry(uint32_t index, const k& key) const
619     {
620         if (index >= buckets_.size()) {
621             // invalid!
622             return nullptr;
623         }
624         auto b = buckets_[index];
625         while (b) {
626             if (b->data.first == key) {
627                 return b;
628             }
629             b = b->next;
630         }
631         // invalid!
632         return nullptr;
633     }
634     // maps key to bucket index.
635     // first uses the hash function to convert key to 64 bit hash.
636     // and then uses "fibonacci hashing"/"Knuth’s multiplicative method"
637     // with additional magic to  fix high bits
638     // and to map the hash to 0 - buckets_.size() range.
639     // see:
640     // https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/
641     template<class k>
index(const k & key)642     uint32_t index(const k& key) const
643     {
644         if (shift_amount_) {
645             const uint64_t GOLDEN_RATIO_64 = 0x9E3779B97F4A7C15ull;
646             const uint64_t shift = 64u - shift_amount_;
647             uint64_t h = hash(key);
648             h ^= h >> shift;
649             return static_cast<uint32_t>(((GOLDEN_RATIO_64 * h) >> shift));
650         }
651         return 0u;
652     }
653     template<class k>
detach_entry(const k & key)654     list_node* detach_entry(const k& key)
655     {
656         const auto ind = index(key);
657         auto entry = get_entry(ind, key);
658         if (entry) {
659             if (entry->prev) {
660                 entry->prev->next = entry->next;
661             } else {
662                 buckets_[ind] = entry->next;
663             }
664             if (entry->next) {
665                 entry->next->prev = entry->prev;
666             }
667             entry->prev = nullptr;
668             entry->next = nullptr;
669             --size_;
670         }
671         return entry;
672     }
rehash()673     void rehash()
674     {
675         BASE_NS::vector<list_node*> tmp(BASE_NS::move(buckets_));
676         shift_amount_++;
677         if (shift_amount_ < DEFAULT_SHIFT_AMOUNT) {
678             // make sure that we always have atleast 1<<DEFAULT_SHIFT_AMOUNT buckets.
679             shift_amount_ = DEFAULT_SHIFT_AMOUNT;
680         }
681         buckets_.resize(1ull << shift_amount_);
682         for (auto b : tmp) {
683             for (; b != nullptr;) {
684                 auto next = b->next;
685                 const uint32_t ind = index(b->data.first);
686                 b->next = buckets_[ind];
687                 buckets_[ind] = b;
688                 if (buckets_[ind]->next) {
689                     buckets_[ind]->next->prev = buckets_[ind];
690                 }
691                 b->prev = nullptr;
692                 b = next;
693             }
694         }
695     }
696 
697     // linked list in buckets_...
698     size_t size_ { 0 };
699     uint32_t shift_amount_ { 0 };
700     BASE_NS::vector<list_node*> buckets_;
make_iterator(list_node * node)701     iterator make_iterator(list_node* node)
702     {
703         return iterator { *this, node };
704     }
make_const_iterator(const list_node * node)705     const_iterator make_const_iterator(const list_node* node) const
706     {
707         return const_iterator { *this, node };
708     }
709 };
710 
711 template<class Key, class T>
712 class unordered_map : public unordered_map_base<Key, T> {
713 public:
714     using unordered_map_base<Key, T>::unordered_map_base;
715 };
716 
717 template<class T>
718 class unordered_map<BASE_NS::string, T> : public unordered_map_base<BASE_NS::string, T> {
719     // Specialization of unordered_map with string key but
720     // accepts string_view as key in certain basic operations..
721     using base = unordered_map_base<BASE_NS::string, T>;
722 
723 public:
724     using unordered_map_base<BASE_NS::string, T>::unordered_map_base;
find(const string_view & key)725     auto find(const string_view& key)
726     {
727         return base::make_iterator(base::get_entry(base::index(key), key));
728     }
find(const string_view & key)729     auto find(const string_view& key) const
730     {
731         return base::make_const_iterator(base::get_entry(base::index(key), key));
732     }
contains(const string_view & key)733     bool contains(const string_view& key) const
734     {
735         return (base::get_entry(base::index(key), key) != nullptr);
736     }
count(const string_view & key)737     size_t count(const string_view& key) const
738     {
739         return contains(key) ? 1U : 0U;
740     }
741 
742     // expose string overloads as well
743     using base::operator[];
744     using base::erase;
745 
746     auto& operator[](const string_view& key)
747     {
748         const auto ind = base::index(key);
749         if (auto b = base::get_entry(ind, key)) {
750             return b->data.second;
751         }
752         return base::create_entry(ind, typename base::key_type(key))->data.second;
753     }
754 
erase(const string_view & key)755     size_t erase(const string_view& key)
756     {
757         if (auto* entry = base::detach_entry(key)) {
758             base::release(entry);
759             return 1u;
760         }
761         return 0u;
762     }
763 
insert(pair<string_view,T> && v)764     pair<typename base::iterator, bool> insert(pair<string_view, T>&& v)
765     {
766         const auto ind = base::index(v.first);
767         auto entry = base::get_entry(ind, v.first);
768         if (entry) {
769             return { base::make_iterator(entry), false };
770         }
771         entry = base::create_entry(
772             ind, typename base::key_type(v.first), BASE_NS::forward<typename base::mapped_type>(v.second));
773         return { base::make_iterator(entry), true };
774     }
775 
insert(const pair<string_view,T> & v)776     pair<typename base::iterator, bool> insert(const pair<string_view, T>& v)
777     {
778         const auto ind = base::index(v.first);
779         auto entry = base::get_entry(ind, v.first);
780         if (entry) {
781             return { base::make_iterator(entry), false };
782         }
783         entry = base::create_entry(ind, typename base::key_type(v.first), v.second);
784         return { base::make_iterator(entry), true };
785     }
786 
787     template<class M>
insert_or_assign(const string_view & key,M && value)788     pair<typename base::iterator, bool> insert_or_assign(const string_view& key, M&& value)
789     {
790         const auto ind = base::index(key);
791         auto entry = base::get_entry(ind, key);
792         if (entry) {
793             entry->data.second = BASE_NS::forward<M>(value);
794             return { base::make_iterator(entry), false };
795         }
796         entry = base::create_entry(ind, typename base::key_type(key), BASE_NS::forward<M>(value));
797         return { base::make_iterator(entry), true };
798     }
799 };
800 BASE_END_NAMESPACE()
801 
802 #endif // API_BASE_CONTAINERS_UNORDERED_MAP_H
803