source: mainline/uspace/lib/cpp/include/impl/unordered_map.hpp@ 544eae5

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 544eae5 was 544eae5, checked in by Dzejrou <dzejrou@…>, 7 years ago

cpp: added try_emplace and insert_or_assign to unordered_map

  • Property mode set to 100644
File size: 29.9 KB
Line 
1/*
2 * Copyright (c) 2018 Jaroslav Jindrak
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#ifndef LIBCPP_UNORDERED_MAP
30#define LIBCPP_UNORDERED_MAP
31
32#include <initializer_list>
33#include <internal/hash_table.hpp>
34#include <functional>
35#include <memory>
36#include <type_traits>
37#include <utility>
38
39namespace std
40{
41 /**
42 * 23.5.4, class template unordered_map:
43 */
44
45 template<
46 class Key, class Value,
47 class Hash = hash<Key>,
48 class Pred = equal_to<Key>,
49 class Alloc = allocator<pair<const Key, Value>>
50 >
51 class unordered_map
52 {
53 public:
54 using key_type = Key;
55 using mapped_type = Value;
56 using value_type = pair<const key_type, mapped_type>;
57 using hasher = Hash;
58 using key_equal = Pred;
59 using allocator_type = Alloc;
60 using pointer = typename allocator_traits<allocator_type>::pointer;
61 using const_pointer = typename allocator_traits<allocator_type>::const_pointer;
62 using reference = value_type&;
63 using const_reference = const value_type&;
64 using size_type = size_t;
65 using difference_type = ptrdiff_t;
66
67 using iterator = aux::hash_table_iterator<
68 value_type, reference, pointer, size_type
69 >;
70 using const_iterator = aux::hash_table_const_iterator<
71 value_type, const_reference, const_pointer, size_type
72 >;
73 using local_iterator = aux::hash_table_local_iterator<
74 value_type, reference, pointer
75 >;
76 using const_local_iterator = aux::hash_table_const_local_iterator<
77 value_type, const_reference, const_pointer
78 >;
79
80 unordered_map()
81 : unordered_map{default_bucket_count_}
82 { /* DUMMY BODY */ }
83
84 explicit unordered_map(size_type bucket_count,
85 const hasher& hf = hasher{},
86 const key_equal& eql = key_equal{},
87 const allocator_type& alloc = allocator_type{})
88 : table_{bucket_count, hf, eql}, allocator_{alloc}
89 { /* DUMMY BODY */ }
90
91 template<class InputIterator>
92 unordered_map(InputIterator first, InputIterator last,
93 size_type bucket_count = default_bucket_count_,
94 const hasher& hf = hasher{},
95 const key_equal& eql = key_equal{},
96 const allocator_type& alloc = allocator_type{})
97 : unordered_map{bucket_count, hf, eql, alloc}
98 {
99 insert(first, last);
100 }
101
102 unordered_map(const unordered_map& other)
103 : unordered_map{other, other.allocator_}
104 { /* DUMMY BODY */ }
105
106 unordered_map(unordered_map&& other)
107 : table_{move(other.table_)}, allocator_{move(other.allocator_)}
108 { /* DUMMY BODY */ }
109
110 explicit unordered_map(const allocator_type& alloc)
111 : table_{default_bucket_count_}, allocator_{alloc}
112 { /* DUMMY BODY */ }
113
114 unordered_map(const unordered_map& other, const allocator_type& alloc)
115 : table_{other.table_}, allocator_{alloc}
116 { /* DUMMY BODY */ }
117
118 unordered_map(unordered_map&& other, const allocator_type& alloc)
119 : table_{move(other.table_)}, allocator_{alloc}
120 { /* DUMMY BODY */ }
121
122 unordered_map(initializer_list<value_type> init,
123 size_type bucket_count = default_bucket_count_,
124 const hasher& hf = hasher{},
125 const key_equal& eql = key_equal{},
126 const allocator_type& alloc = allocator_type{})
127 : unordered_map{bucket_count, hf, eql, alloc}
128 {
129 insert(init.begin(), init.end());
130 }
131
132 unordered_map(size_type bucket_count, const allocator_type& alloc)
133 : unordered_map{bucket_count, hasher{}, key_equal{}, alloc}
134 { /* DUMMY BODY */ }
135
136 unordered_map(size_type bucket_count, const hasher& hf, const allocator_type& alloc)
137 : unordered_map{bucket_count, hf, key_equal{}, alloc}
138 { /* DUMMY BODY */ }
139
140 template<class InputIterator>
141 unordered_map(InputIterator first, InputIterator last,
142 size_type bucket_count, const allocator_type& alloc)
143 : unordered_map{first, last, bucket_count, hasher{}, key_equal{}, alloc}
144 { /* DUMMY BODY */ }
145
146 template<class InputIterator>
147 unordered_map(InputIterator first, InputIterator last,
148 size_type bucket_count, const hasher& hf, const allocator_type& alloc)
149 : unordered_map{first, last, bucket_count, hf, key_equal{}, alloc}
150 { /* DUMMY BODY */ }
151
152 unordered_map(initializer_list<value_type> init, size_type bucket_count,
153 const allocator_type& alloc)
154 : unordered_map{init, bucket_count, hasher{}, key_equal{}, alloc}
155 { /* DUMMY BODY */ }
156
157 unordered_map(initializer_list<value_type> init, size_type bucket_count,
158 const hasher& hf, const allocator_type& alloc)
159 : unordered_map{init, bucket_count, hf, key_equal{}, alloc}
160 { /* DUMMY BODY */ }
161
162 ~unordered_map()
163 { /* DUMMY BODY */ }
164
165 unordered_map& operator=(const unordered_map& other)
166 {
167 table_ = other.table_;
168 allocator_ = other.allocator_;
169
170 return *this;
171 }
172
173 unordered_map& operator=(unordered_map&& other)
174 noexcept(allocator_traits<allocator_type>::is_always_equal::value &&
175 is_nothrow_move_assignable<hasher>::value &&
176 is_nothrow_move_assignable<key_equal>::value)
177 {
178 table_ = move(other.table_);
179 allocator_ = move(other.allocator_);
180
181 return *this;
182 }
183
184 unordered_map& operator=(initializer_list<value_type>& init)
185 {
186 table_.clear();
187 table_.reserve(init.size());
188
189 insert(init.begin(), init.end());
190
191 return *this;
192 }
193
194 allocator_type get_allocator() const noexcept
195 {
196 return allocator_;
197 }
198
199 bool empty() const noexcept
200 {
201 return table_.empty();
202 }
203
204 size_type size() const noexcept
205 {
206 return table_.size();
207 }
208
209 size_type max_size() const noexcept
210 {
211 return table_.max_size();
212 }
213
214 iterator begin() noexcept
215 {
216 return table_.begin();
217 }
218
219 const_iterator begin() const noexcept
220 {
221 return table_.begin();
222 }
223
224 iterator end() noexcept
225 {
226 return table_.end();
227 }
228
229 const_iterator end() const noexcept
230 {
231 return table_.end();
232 }
233
234 const_iterator cbegin() const noexcept
235 {
236 return table_.cbegin();
237 }
238
239 const_iterator cend() const noexcept
240 {
241 return table_.cend();
242 }
243
244 template<class... Args>
245 pair<iterator, bool> emplace(Args&&... args)
246 {
247 /**
248 * Note: Some of these modifier functions start off
249 * by incrementing the table's element count and
250 * decrement it when insertion does not take place.
251 * This is because we need to cause rehashing if
252 * there are too many elements, but rehashing itself
253 * would invalidate all pointers we get from
254 * find_insertion_spot, which would require us to
255 * do another find. But this way we avoid two searches
256 * with the possibility of a rehash that is not necessary
257 * (but hardly a bad thing as the table needs just one
258 * element more to rehash).
259 *
260 * Also, there are 3 functions with similar bodies,
261 * but the duplicit code cannot be moved to a common
262 * sub-function because all three require different
263 * handling of the value (move, forward and copy).
264 */
265 table_.increment_size();
266
267 auto val = value_type{forward<Args>(args)...};
268 auto key = table_.get_key(val);
269 auto spot = table_.find_insertion_spot(key);
270 auto bucket = get<0>(spot);
271 auto idx = get<2>(spot);
272
273 if (!bucket)
274 return make_pair(end(), false);
275
276 auto target = table_.find_node_or_return_head(key, *bucket);
277 if (target && table_.keys_equal(key, target->value))
278 {
279 table_.decrement_size();
280 return make_pair(
281 iterator{
282 table_.table(), idx, table_.bucket_count(),
283 target
284 },
285 false
286 );
287 }
288 else
289 {
290 auto node = new node_type{move(val)};
291 bucket->append(node);
292
293 return make_pair(iterator{
294 table_.table(), idx,
295 table_.bucket_count(),
296 node
297 }, true);
298 }
299 }
300
301 template<class... Args>
302 iterator emplace_hint(const_iterator, Args&&... args)
303 {
304 return emplace(forward<Args>(args)...).first;
305 }
306
307 pair<iterator, bool> insert(const value_type& val)
308 {
309 table_.increment_size();
310
311 auto key = table_.get_key(val);
312 auto spot = table_.find_insertion_spot(key);
313 auto bucket = get<0>(spot);
314 auto idx = get<2>(spot);
315
316 if (!bucket)
317 return make_pair(end(), false);
318
319 auto target = table_.find_node_or_return_head(key, *bucket);
320 if (target && table_.keys_equal(key, target->value))
321 {
322 table_.decrement_size();
323 return make_pair(
324 iterator{
325 table_.table(), idx, table_.bucket_count(),
326 target
327 },
328 false
329 );
330 }
331 else
332 {
333 auto node = new node_type{val};
334 bucket->append(node);
335
336 return make_pair(iterator{
337 table_.table(), idx,
338 table_.bucket_count(),
339 node
340 }, true);
341 }
342 }
343
344 pair<iterator, bool> insert(value_type&& val)
345 {
346 table_.increment_size();
347
348 auto key = table_.get_key(val);
349 auto spot = table_.find_insertion_spot(key);
350 auto bucket = get<0>(spot);
351 auto idx = get<2>(spot);
352
353 if (!bucket)
354 return make_pair(end(), false);
355
356 auto target = table_.find_node_or_return_head(key, *bucket);
357 if (target && table_.keys_equal(key, target->value))
358 {
359 table_.decrement_size();
360 return make_pair(
361 iterator{
362 table_.table(), idx, table_.bucket_count(),
363 target
364 },
365 false
366 );
367 }
368 else
369 {
370 auto node = new node_type{forward<value_type>(val)};
371 bucket->append(node);
372
373 return make_pair(iterator{
374 table_.table(), idx,
375 table_.bucket_count(),
376 node
377 }, true);
378 }
379 }
380
381 template<
382 class T,
383 class = enable_if_t<is_constructible_v<value_type, T&&>, void>
384 >
385 pair<iterator, bool> insert(T&& val)
386 {
387 return emplace(forward<T>(val));
388 }
389
390 iterator insert(const_iterator, const value_type& val)
391 {
392 return insert(val).first;
393 }
394
395 iterator insert(const_iterator, value_type&& val)
396 {
397 return insert(forward<value_type>(val)).first;
398 }
399
400 template<
401 class T,
402 class = enable_if_t<is_constructible_v<value_type, T&&>, void>
403 >
404 iterator insert(const_iterator hint, T&& val)
405 {
406 return emplace_hint(hint, forward<T>(val));
407 }
408
409 template<class InputIterator>
410 void insert(InputIterator first, InputIterator last)
411 {
412 while (first != last)
413 insert(*first++);
414 }
415
416 void insert(initializer_list<value_type> init)
417 {
418 insert(init.begin(), init.end());
419 }
420
421 template<class... Args>
422 pair<iterator, bool> try_emplace(const key_type& key, Args&&... args)
423 {
424 /**
425 * Note: If anyone (like me) wonders what is the difference between
426 * emplace and try_emplace, the former always constructs the value
427 * (in order to get the key) and the latter does it only if
428 * an insertion takes place.
429 */
430
431 table_.increment_size();
432
433 auto spot = table_.find_insertion_spot(key);
434 auto bucket = get<0>(spot);
435 auto idx = get<2>(spot);
436
437 if (!bucket)
438 return make_pair(end(), false);
439
440 auto target = table_.find_node_or_return_head(key, *bucket);
441 if (target && table_.keys_equal(key, target->value))
442 {
443 table_.decrement_size();
444
445 return make_pair(
446 iterator{
447 table_.table(), idx, table_.bucket_count(),
448 target
449 },
450 false
451 );
452 }
453 else
454 {
455 auto node = new node_type{key, forward<Args>(args)...};
456 bucket->append(node);
457
458 return make_pair(iterator{
459 table_.table(), idx,
460 table_.bucket_count(),
461 node
462 }, true);
463 }
464 }
465
466 template<class... Args>
467 pair<iterator, bool> try_emplace(key_type&& key, Args&&... args)
468 {
469 table_.increment_size();
470
471 auto spot = table_.find_insertion_spot(key);
472 auto bucket = get<0>(spot);
473 auto idx = get<2>(spot);
474
475 if (!bucket)
476 return make_pair(end(), false);
477
478 auto target = table_.find_node_or_return_head(key, *bucket);
479 if (target && table_.keys_equal(key, target->value))
480 {
481 table_.decrement_size();
482
483 return make_pair(
484 iterator{
485 table_.table(), idx, table_.bucket_count(),
486 target
487 },
488 false
489 );
490 }
491 else
492 {
493 auto node = new node_type{move(key), forward<Args>(args)...};
494 bucket->append(node);
495
496 return make_pair(iterator{
497 table_.table(), idx,
498 table_.bucket_count(),
499 node
500 }, true);
501 }
502 }
503
504 template<class... Args>
505 iterator try_emplace(const_iterator, const key_type& key, Args&&... args)
506 {
507 return try_emplace(key, forward<Args>(args)...).first;
508 }
509
510 template<class... Args>
511 iterator try_emplace(const_iterator, key_type&& key, Args&&... args)
512 {
513 return try_emplace(move(key), forward<Args>(args)...).first;
514 }
515
516 template<class T>
517 pair<iterator, bool> insert_or_assign(const key_type& key, T&& val)
518 {
519 table_.increment_size();
520
521 auto spot = table_.find_insertion_spot(key);
522 auto bucket = get<0>(spot);
523 auto idx = get<2>(spot);
524
525 if (!bucket)
526 return make_pair(end(), false);
527
528 auto target = table_.find_node_or_return_head(key, *bucket);
529 if (target && table_.keys_equal(key, target->value))
530 {
531 table_.decrement_size();
532 target->value.second = forward<T>(val);
533
534 return make_pair(
535 iterator{
536 table_.table(), idx, table_.bucket_count(),
537 target
538 },
539 false
540 );
541 }
542 else
543 {
544 auto node = new node_type{key, forward<T>(val)};
545 bucket->append(node);
546
547 return make_pair(iterator{
548 table_.table(), idx,
549 table_.bucket_count(),
550 node
551 }, true);
552 }
553 }
554
555 template<class T>
556 pair<iterator, bool> insert_or_assign(key_type&& key, T&& val)
557 {
558 table_.increment_size();
559
560 auto spot = table_.find_insertion_spot(key);
561 auto bucket = get<0>(spot);
562 auto idx = get<2>(spot);
563
564 if (!bucket)
565 return make_pair(end(), false);
566
567 auto target = table_.find_node_or_return_head(key, *bucket);
568 if (target && table_.keys_equal(key, target->value))
569 {
570 table_.decrement_size();
571 target->value.second = forward<T>(val);
572
573 return make_pair(
574 iterator{
575 table_.table(), idx, table_.bucket_count(),
576 target
577 },
578 false
579 );
580 }
581 else
582 {
583 auto node = new node_type{move(key), forward<T>(val)};
584 bucket->append(node);
585
586 return make_pair(iterator{
587 table_.table(), idx,
588 table_.bucket_count(),
589 node
590 }, true);
591 }
592 }
593
594 template<class T>
595 iterator insert_or_assign(const_iterator, const key_type& key, T&& val)
596 {
597 return insert_or_assign(key, forward<T>(val)).first;
598 }
599
600 template<class T>
601 iterator insert_or_assign(const_iterator, key_type&& key, T&& val)
602 {
603 return insert_or_assign(move(key), forward<T>(val)).first;
604 }
605
606 iterator erase(const_iterator position)
607 {
608 return table_.erase(position);
609 }
610
611 size_type erase(const key_type& key)
612 {
613 return table_.erase(key);
614 }
615
616 iterator erase(const_iterator first, const_iterator last)
617 {
618 while (first != last)
619 first = erase(first);
620
621 return iterator{
622 table_.table(), first.idx(),
623 table_.bucket_count(), table_.head(first.idx())
624 };
625 }
626
627 void clear() noexcept
628 {
629 table_.clear();
630 }
631
632 void swap(unordered_map& other)
633 noexcept(allocator_traits<allocator_type>::is_always_equal::value &&
634 noexcept(std::swap(declval<hasher>(), declval<hasher>())) &&
635 noexcept(std::swap(declval<key_equal>(), declval<key_equal>())))
636 {
637 table_.swap(other.table_);
638 std::swap(allocator_, other.allocator_);
639 }
640
641 hasher hash_function() const
642 {
643 return table_.hash_function();
644 }
645
646 key_equal key_eq() const
647 {
648 return table_.key_eq();
649 }
650
651 iterator find(const key_type& key)
652 {
653 return table_.find(key);
654 }
655
656 const_iterator find(const key_type& key) const
657 {
658 return table_.find(key);
659 }
660
661 size_type count(const key_type& key) const
662 {
663 return table_.count(key);
664 }
665
666 pair<iterator, iterator> equal_range(const key_type& key)
667 {
668 return table_.equal_range(key);
669 }
670
671 pair<const_iterator, const_iterator> equal_range(const key_type& key) const
672 {
673 return table_.equal_range(key);
674 }
675
676 mapped_type& operator[](const key_type& key)
677 {
678 auto spot = table_.find_insertion_spot(key);
679 auto bucket = get<0>(spot);
680
681 if (bucket->head)
682 {
683 auto head = bucket->head;
684 auto current = bucket->head;
685
686 do
687 {
688 if (table_.keys_equal(key, current->value))
689 return current->value.second;
690 else
691 current = current->next;
692 }
693 while (current != head);
694 }
695
696 auto node = new node_type{key, mapped_type{}};
697 bucket->append(node);
698
699 table_.increment_size();
700 table_.rehash_if_needed();
701 return node->value.second;
702 }
703
704 mapped_type& operator[](key_type&& key)
705 {
706 auto spot = table_.find_insertion_spot(key);
707 auto bucket = get<0>(spot);
708
709 if (bucket->head)
710 {
711 auto head = bucket->head;
712 auto current = bucket->head;
713
714 do
715 {
716 if (table_.keys_equal(key, current->value))
717 return current->value.second;
718 else
719 current = current->next;
720 }
721 while (current != head);
722 }
723
724 auto node = new node_type{move(key), mapped_type{}};
725 bucket->append(node);
726
727 table_.increment_size();
728 table_.rehash_if_needed();
729 return node->value.second;
730 }
731
732 mapped_type& at(const key_type& key)
733 {
734 auto it = find(key);
735
736 // TODO: throw out_of_range if it == end()
737 return it->second;
738 }
739
740 const mapped_type& at(const key_type& key) const
741 {
742 auto it = find(key);
743
744 // TODO: throw out_of_range if it == end()
745 return it->second;
746 }
747
748 size_type bucket_count() const noexcept
749 {
750 return table_.bucket_count();
751 }
752
753 size_type max_bucket_count() const noexcept
754 {
755 return table_.max_bucket_count();
756 }
757
758 size_type bucket_size(size_type idx) const
759 {
760 return table_.bucket_size(idx);
761 }
762
763 size_type bucket(const key_type& key) const
764 {
765 return table_.bucket(key);
766 }
767
768 local_iterator begin(size_type idx)
769 {
770 return table_.begin(idx);
771 }
772
773 const_local_iterator begin(size_type idx) const
774 {
775 return table_.begin(idx);
776 }
777
778 local_iterator end(size_type idx)
779 {
780 return table_.end(idx);
781 }
782
783 const_local_iterator end(size_type idx) const
784 {
785 return table_.end(idx);
786 }
787
788 const_local_iterator cbegin(size_type idx) const
789 {
790 return table_.cbegin(idx);
791 }
792
793 const_local_iterator cend(size_type idx) const
794 {
795 return table_.cend(idx);
796 }
797
798 float load_factor() const noexcept
799 {
800 return table_.load_factor();
801 }
802
803 float max_load_factor() const noexcept
804 {
805 return table_.max_load_factor();
806 }
807
808 void max_load_factor(float factor)
809 {
810 table_.max_load_factor(factor);
811 }
812
813 void rehash(size_type bucket_count)
814 {
815 table_.rehash(bucket_count);
816 }
817
818 void reserve(size_type count)
819 {
820 table_.reserve(count);
821 }
822
823 private:
824 using table_type = aux::hash_table<
825 value_type, key_type, aux::key_value_key_extractor<key_type, mapped_type>,
826 hasher, key_equal, allocator_type, size_type,
827 iterator, const_iterator, local_iterator, const_local_iterator,
828 aux::hash_single_policy
829 >;
830 using node_type = typename table_type::node_type;
831
832 table_type table_;
833 allocator_type allocator_;
834
835 static constexpr size_type default_bucket_count_{16};
836 };
837
838 /**
839 * 23.5.5, class template unordered_multimap:
840 */
841
842 template<
843 class Key, class Value,
844 class Hash = hash<Key>,
845 class Pred = equal_to<Key>,
846 class Alloc = allocator<pair<const Key, Value>>
847 >
848 class unordered_multimap;
849
850 template<class Key, class Value, class Hash, class Pred, class Alloc>
851 void swap(unordered_map<Key, Value, Hash, Pred, Alloc>& lhs,
852 unordered_map<Key, Value, Hash, Pred, Alloc>& rhs)
853 noexcept(noexcept(lhs.swap(rhs)))
854 {
855 lhs.swap(rhs);
856 }
857
858 template<class Key, class Value, class Hash, class Pred, class Alloc>
859 void swap(unordered_multimap<Key, Value, Hash, Pred, Alloc>& lhs,
860 unordered_multimap<Key, Value, Hash, Pred, Alloc>& rhs)
861 noexcept(noexcept(lhs.swap(rhs)))
862 {
863 lhs.swap(rhs);
864 }
865
866 template<class Key, class Value, class Hash, class Pred, class Alloc>
867 bool operator==(unordered_map<Key, Value, Hash, Pred, Alloc>& lhs,
868 unordered_map<Key, Value, Hash, Pred, Alloc>& rhs)
869 {
870 // TODO: implement
871 return false;
872 }
873
874 template<class Key, class Value, class Hash, class Pred, class Alloc>
875 bool operator!=(unordered_map<Key, Value, Hash, Pred, Alloc>& lhs,
876 unordered_map<Key, Value, Hash, Pred, Alloc>& rhs)
877 {
878 return !(lhs == rhs);
879 }
880
881 template<class Key, class Value, class Hash, class Pred, class Alloc>
882 bool operator==(unordered_multimap<Key, Value, Hash, Pred, Alloc>& lhs,
883 unordered_multimap<Key, Value, Hash, Pred, Alloc>& rhs)
884 {
885 // TODO: implement
886 return false;
887 }
888
889 template<class Key, class Value, class Hash, class Pred, class Alloc>
890 bool operator!=(unordered_multimap<Key, Value, Hash, Pred, Alloc>& lhs,
891 unordered_multimap<Key, Value, Hash, Pred, Alloc>& rhs)
892 {
893 return !(lhs == rhs);
894 }
895}
896
897#endif
Note: See TracBrowser for help on using the repository browser.