2 This file is a part of libcds - Concurrent Data Structures library
4 (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2017
6 Source code repo: http://github.com/khizmax/libcds/
7 Download: http://sourceforge.net/projects/libcds/files/
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
12 * Redistributions of source code must retain the above copyright notice, this
13 list of conditions and the following disclaimer.
15 * Redistributions in binary form must reproduce the above copyright notice,
16 this list of conditions and the following disclaimer in the documentation
17 and/or other materials provided with the distribution.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #ifndef CDSLIB_CONTAINER_FELDMAN_HASHMAP_RCU_H
32 #define CDSLIB_CONTAINER_FELDMAN_HASHMAP_RCU_H
34 #include <cds/intrusive/feldman_hashset_rcu.h>
35 #include <cds/container/details/feldman_hashmap_base.h>
37 namespace cds { namespace container {
39 /// Hash map based on multi-level array
40 /** @ingroup cds_nonintrusive_map
41 @anchor cds_container_FeldmanHashMap_rcu
44 - [2013] Steven Feldman, Pierre LaBorde, Damian Dechev "Concurrent Multi-level Arrays:
45 Wait-free Extensible Hash Maps"
47 See algorithm short description @ref cds_container_FeldmanHashMap_hp "here"
49 @note Two important things you should keep in mind when you're using \p %FeldmanHashMap:
50 - all keys is converted to fixed-size bit-string by hash functor provided.
51 You can use variable-length keys, for example, \p std::string as a key for \p %FeldmanHashMap,
52 but real key in the map will be fixed-size hash values of your keys.
53 For the strings you may use well-known hashing algorithms like <a href="https://en.wikipedia.org/wiki/Secure_Hash_Algorithm">SHA1, SHA2</a>,
54 <a href="https://en.wikipedia.org/wiki/MurmurHash">MurmurHash</a>, <a href="https://en.wikipedia.org/wiki/CityHash">CityHash</a>
55 or its successor <a href="https://code.google.com/p/farmhash/">FarmHash</a> and so on, which
56 converts variable-length strings to fixed-length bit-strings, and such hash values will be the keys in \p %FeldmanHashMap.
57 If your key is fixed-sized the hash functor is optional, see \p feldman_hashmap::traits::hash for explanation and examples.
58 - \p %FeldmanHashMap uses a perfect hashing. It means that if two different keys, for example, of type \p std::string,
59 have identical hash then you cannot insert both that keys in the map. \p %FeldmanHashMap does not maintain the key,
60 it maintains its fixed-size hash value.
62 The map supports @ref cds_container_FeldmanHashMap_rcu_iterators "bidirectional thread-safe iterators".
65 - \p RCU - one of \ref cds_urcu_gc "RCU type"
66 - \p Key - a key type to be stored in the map
67 - \p T - a value type to be stored in the map
68 - \p Traits - type traits, the structure based on \p feldman_hashmap::traits or result of \p feldman_hashmap::make_traits metafunction.
70 @note Before including <tt><cds/intrusive/feldman_hashset_rcu.h></tt> you should include appropriate RCU header file,
71 see \ref cds_urcu_gc "RCU type" for list of existing RCU class and corresponding header files.
77 #ifdef CDS_DOXYGEN_INVOKED
78 ,class Traits = feldman_hashmap::traits
83 class FeldmanHashMap< cds::urcu::gc< RCU >, Key, T, Traits >
84 #ifdef CDS_DOXYGEN_INVOKED
85 : protected cds::intrusive::FeldmanHashSet< cds::urcu::gc< RCU >, std::pair<Key const, T>, Traits >
87 : protected cds::container::details::make_feldman_hashmap< cds::urcu::gc< RCU >, Key, T, Traits >::type
91 typedef cds::container::details::make_feldman_hashmap< cds::urcu::gc< RCU >, Key, T, Traits > maker;
92 typedef typename maker::type base_class;
95 typedef cds::urcu::gc< RCU > gc; ///< RCU garbage collector
96 typedef Key key_type; ///< Key type
97 typedef T mapped_type; ///< Mapped type
98 typedef std::pair< key_type const, mapped_type> value_type; ///< Key-value pair to be stored in the map
99 typedef Traits traits; ///< Map traits
100 #ifdef CDS_DOXYGEN_INVOKED
101 typedef typename traits::hash hasher; ///< Hash functor, see \p feldman_hashmap::traits::hash
103 typedef typename maker::hasher hasher;
106 typedef typename maker::hash_type hash_type; ///< Hash type deduced from \p hasher return type
107 typedef typename base_class::hash_comparator hash_comparator; ///< hash compare functor based on \p Traits::compare and \p Traits::less
108 typedef typename traits::item_counter item_counter; ///< Item counter type
109 typedef typename traits::allocator allocator; ///< Element allocator
110 typedef typename traits::node_allocator node_allocator; ///< Array node allocator
111 typedef typename traits::memory_model memory_model; ///< Memory model
112 typedef typename traits::back_off back_off; ///< Back-off strategy
113 typedef typename traits::stat stat; ///< Internal statistics type
114 typedef typename traits::rcu_check_deadlock rcu_check_deadlock; ///< Deadlock checking policy
115 typedef typename gc::scoped_lock rcu_lock; ///< RCU scoped lock
116 static CDS_CONSTEXPR const bool c_bExtractLockExternal = false; ///< Group of \p extract_xxx functions does not require external locking
119 typedef feldman_hashmap::level_statistics level_statistics;
123 typedef typename maker::node_type node_type;
124 typedef typename maker::cxx_node_allocator cxx_node_allocator;
125 typedef std::unique_ptr< node_type, typename maker::node_disposer > scoped_node_ptr;
126 typedef typename base_class::check_deadlock_policy check_deadlock_policy;
130 value_type * operator()(node_type * p) const
132 return p ? &p->m_Value : nullptr;
137 /// pointer to extracted node
138 using exempt_ptr = cds::urcu::exempt_ptr< gc, node_type, value_type, typename base_class::disposer, node_cast >;
141 template <bool IsConst>
142 class bidirectional_iterator: public base_class::iterator_base
144 friend class FeldmanHashMap;
145 typedef typename base_class::iterator_base iterator_base;
148 static CDS_CONSTEXPR bool const c_bConstantIterator = IsConst;
151 typedef typename std::conditional< IsConst, value_type const*, value_type*>::type value_ptr; ///< Value pointer
152 typedef typename std::conditional< IsConst, value_type const&, value_type&>::type value_ref; ///< Value reference
155 bidirectional_iterator() CDS_NOEXCEPT
158 bidirectional_iterator( bidirectional_iterator const& rhs ) CDS_NOEXCEPT
159 : iterator_base( rhs )
162 bidirectional_iterator& operator=(bidirectional_iterator const& rhs) CDS_NOEXCEPT
164 iterator_base::operator=( rhs );
168 bidirectional_iterator& operator++()
170 iterator_base::operator++();
174 bidirectional_iterator& operator--()
176 iterator_base::operator--();
180 value_ptr operator ->() const CDS_NOEXCEPT
182 node_type * p = iterator_base::pointer();
183 return p ? &p->m_Value : nullptr;
186 value_ref operator *() const CDS_NOEXCEPT
188 node_type * p = iterator_base::pointer();
195 iterator_base::release();
198 template <bool IsConst2>
199 bool operator ==(bidirectional_iterator<IsConst2> const& rhs) const CDS_NOEXCEPT
201 return iterator_base::operator==( rhs );
204 template <bool IsConst2>
205 bool operator !=(bidirectional_iterator<IsConst2> const& rhs) const CDS_NOEXCEPT
207 return !( *this == rhs );
210 public: // for internal use only!
211 bidirectional_iterator( base_class const& set, typename base_class::array_node * pNode, size_t idx, bool )
212 : iterator_base( set, pNode, idx, false )
215 bidirectional_iterator( base_class const& set, typename base_class::array_node * pNode, size_t idx )
216 : iterator_base( set, pNode, idx )
220 /// Reverse bidirectional iterator
221 template <bool IsConst>
222 class reverse_bidirectional_iterator : public base_class::iterator_base
224 friend class FeldmanHashMap;
225 typedef typename base_class::iterator_base iterator_base;
228 typedef typename std::conditional< IsConst, value_type const*, value_type*>::type value_ptr; ///< Value pointer
229 typedef typename std::conditional< IsConst, value_type const&, value_type&>::type value_ref; ///< Value reference
232 reverse_bidirectional_iterator() CDS_NOEXCEPT
236 reverse_bidirectional_iterator( reverse_bidirectional_iterator const& rhs ) CDS_NOEXCEPT
237 : iterator_base( rhs )
240 reverse_bidirectional_iterator& operator=( reverse_bidirectional_iterator const& rhs) CDS_NOEXCEPT
242 iterator_base::operator=( rhs );
246 reverse_bidirectional_iterator& operator++()
248 iterator_base::operator--();
252 reverse_bidirectional_iterator& operator--()
254 iterator_base::operator++();
258 value_ptr operator ->() const CDS_NOEXCEPT
260 node_type * p = iterator_base::pointer();
261 return p ? &p->m_Value : nullptr;
264 value_ref operator *() const CDS_NOEXCEPT
266 node_type * p = iterator_base::pointer();
273 iterator_base::release();
276 template <bool IsConst2>
277 bool operator ==(reverse_bidirectional_iterator<IsConst2> const& rhs) const
279 return iterator_base::operator==( rhs );
282 template <bool IsConst2>
283 bool operator !=(reverse_bidirectional_iterator<IsConst2> const& rhs)
285 return !( *this == rhs );
288 public: // for internal use only!
289 reverse_bidirectional_iterator( base_class const& set, typename base_class::array_node * pNode, size_t idx, bool )
290 : iterator_base( set, pNode, idx, false )
293 reverse_bidirectional_iterator( base_class const& set, typename base_class::array_node * pNode, size_t idx )
294 : iterator_base( set, pNode, idx, false )
296 iterator_base::backward();
302 #ifdef CDS_DOXYGEN_INVOKED
303 typedef implementation_defined iterator; ///< @ref cds_container_FeldmanHashMap_rcu_iterators "bidirectional iterator" type
304 typedef implementation_defined const_iterator; ///< @ref cds_container_FeldmanHashMap_rcu_iterators "bidirectional const iterator" type
305 typedef implementation_defined reverse_iterator; ///< @ref cds_container_FeldmanHashMap_rcu_iterators "bidirectional reverse iterator" type
306 typedef implementation_defined const_reverse_iterator; ///< @ref cds_container_FeldmanHashMap_rcu_iterators "bidirectional reverse const iterator" type
308 typedef bidirectional_iterator<false> iterator;
309 typedef bidirectional_iterator<true> const_iterator;
310 typedef reverse_bidirectional_iterator<false> reverse_iterator;
311 typedef reverse_bidirectional_iterator<true> const_reverse_iterator;
320 /// Creates empty map
322 @param head_bits - 2<sup>head_bits</sup> specifies the size of head array, minimum is 4.
323 @param array_bits - 2<sup>array_bits</sup> specifies the size of array node, minimum is 2.
325 Equation for \p head_bits and \p array_bits:
327 sizeof(hash_type) * 8 == head_bits + N * array_bits
329 where \p N is multi-level array depth.
331 FeldmanHashMap( size_t head_bits = 8, size_t array_bits = 4 )
332 : base_class( head_bits, array_bits )
335 /// Destructs the map and frees all data
339 /// Inserts new element with key and default value
341 The function creates an element with \p key and default value, and then inserts the node created into the map.
344 - The \p key_type should be constructible from a value of type \p K.
345 In trivial case, \p K is equal to \p key_type.
346 - The \p mapped_type should be default-constructible.
348 Returns \p true if inserting successful, \p false otherwise.
350 The function locks RCU internally.
352 template <typename K>
353 bool insert( K&& key )
355 scoped_node_ptr sp( cxx_node_allocator().MoveNew( m_Hasher, std::forward<K>(key)));
356 if ( base_class::insert( *sp )) {
363 /// Inserts new element
365 The function creates a node with copy of \p val value
366 and then inserts the node created into the map.
369 - The \p key_type should be constructible from \p key of type \p K.
370 - The \p value_type should be constructible from \p val of type \p V.
372 Returns \p true if \p val is inserted into the map, \p false otherwise.
374 The function locks RCU internally.
376 template <typename K, typename V>
377 bool insert( K&& key, V&& val )
379 scoped_node_ptr sp( cxx_node_allocator().MoveNew( m_Hasher, std::forward<K>(key), std::forward<V>(val)));
380 if ( base_class::insert( *sp )) {
387 /// Inserts new element and initialize it by a functor
389 This function inserts new element with key \p key and if inserting is successful then it calls
390 \p func functor with signature
393 void operator()( value_type& item );
397 The argument \p item of user-defined functor \p func is the reference
398 to the map's item inserted:
399 - <tt>item.first</tt> is a const reference to item's key that cannot be changed.
400 - <tt>item.second</tt> is a reference to item's value that may be changed.
402 \p key_type should be constructible from value of type \p K.
404 The function allows to split creating of new item into two part:
405 - create item from \p key;
406 - insert new item into the map;
407 - if inserting is successful, initialize the value of item by calling \p func functor
409 This can be useful if complete initialization of object of \p value_type is heavyweight and
410 it is preferable that the initialization should be completed only if inserting is successful.
412 The function locks RCU internally.
414 template <typename K, typename Func>
415 bool insert_with( K&& key, Func func )
417 scoped_node_ptr sp( cxx_node_allocator().MoveNew( m_Hasher, std::forward<K>(key)));
418 if ( base_class::insert( *sp, [&func]( node_type& item ) { func( item.m_Value ); } )) {
425 /// For key \p key inserts data of type \p value_type created in-place from <tt>std::forward<Args>(args)...</tt>
427 Returns \p true if inserting successful, \p false otherwise.
429 The function locks RCU internally.
431 template <typename K, typename... Args>
432 bool emplace( K&& key, Args&&... args )
434 scoped_node_ptr sp( cxx_node_allocator().MoveNew( m_Hasher, std::forward<K>(key), std::forward<Args>(args)... ));
435 if ( base_class::insert( *sp )) {
442 /// Updates data by \p key
444 The operation performs inserting or replacing the element with lock-free manner.
446 If the \p key not found in the map, then the new item created from \p key
447 will be inserted into the map iff \p bInsert is \p true
448 (note that in this case the \ref key_type should be constructible from type \p K).
449 Otherwise, if \p key is found, it is replaced with a new item created from
451 The functor \p Func signature:
454 void operator()( value_type& item, value_type * old );
458 - \p item - item of the map
459 - \p old - old item of the map, if \p nullptr - the new item was inserted
461 The functor may change any fields of the \p item.second.
463 Returns <tt> std::pair<bool, bool> </tt> where \p first is \p true if operation is successful,
464 \p second is \p true if new item has been added or \p false if \p key already exists.
466 The function locks RCU internally.
468 @warning See \ref cds_intrusive_item_creating "insert item troubleshooting"
470 template <typename K, typename Func>
471 std::pair<bool, bool> update( K&& key, Func func, bool bInsert = true )
473 scoped_node_ptr sp( cxx_node_allocator().MoveNew( m_Hasher, std::forward<K>(key)));
474 std::pair<bool, bool> result = base_class::do_update( *sp,
475 [&func]( node_type& node, node_type * old ) { func( node.m_Value, old ? &old->m_Value : nullptr );},
482 /// Delete \p key from the map
484 \p key_type must be constructible from value of type \p K.
485 The function deletes the element with hash value equal to <tt>hash( key_type( key ))</tt>
487 Return \p true if \p key is found and deleted, \p false otherwise.
489 RCU should not be locked. The function locks RCU internally.
491 template <typename K>
492 bool erase( K const& key )
494 return base_class::erase(m_Hasher(key_type(key)));
497 /// Delete \p key from the map
499 The function searches an item with hash value equal to <tt>hash( key_type( key ))</tt>,
500 calls \p f functor and deletes the item. If \p key is not found, the functor is not called.
502 The functor \p Func interface:
505 void operator()(value_type& item) { ... }
508 where \p item is the element found.
510 \p key_type must be constructible from value of type \p K.
512 Return \p true if key is found and deleted, \p false otherwise
514 RCU should not be locked. The function locks RCU internally.
516 template <typename K, typename Func>
517 bool erase( K const& key, Func f )
519 return base_class::erase(m_Hasher(key_type(key)), [&f]( node_type& node) { f( node.m_Value ); });
522 /// Extracts the item from the map with specified \p key
524 The function searches an item with key equal to <tt>hash( key_type( key ))</tt> in the map,
525 unlinks it from the map, and returns a guarded pointer to the item found.
526 If \p key is not found the function returns an empty guarded pointer.
528 RCU \p synchronize method can be called. RCU should NOT be locked.
529 The function does not call the disposer for the item found.
530 The disposer will be implicitly invoked when the returned object is destroyed or when
531 its \p release() member function is called.
534 typedef cds::container::FeldmanHashMap< cds::urcu::gc< cds::urcu::general_buffered<>>, int, foo, my_traits > map_type;
538 typename map_type::exempt_ptr ep( theMap.extract( 5 ));
543 // Dispose returned item.
548 template <typename K>
549 exempt_ptr extract( K const& key )
551 check_deadlock_policy::check();
556 p = base_class::do_erase( m_Hasher( key_type(key)), [](node_type const&) -> bool {return true;});
558 return exempt_ptr(p);
561 /// Checks whether the map contains \p key
563 The function searches the item by its hash that is equal to <tt>hash( key_type( key ))</tt>
564 and returns \p true if it is found, or \p false otherwise.
566 template <typename K>
567 bool contains( K const& key )
569 return base_class::contains( m_Hasher( key_type( key )));
572 /// Find the key \p key
575 The function searches the item by its hash that is equal to <tt>hash( key_type( key ))</tt>
576 and calls the functor \p f for item found.
577 The interface of \p Func functor is:
580 void operator()( value_type& item );
583 where \p item is the item found.
585 The functor may change \p item.second.
587 The function returns \p true if \p key is found, \p false otherwise.
589 template <typename K, typename Func>
590 bool find( K const& key, Func f )
592 return base_class::find( m_Hasher( key_type( key )), [&f](node_type& node) { f( node.m_Value );});
595 /// Finds the key \p key and return the item found
597 The function searches the item by its \p hash
598 and returns the pointer to the item found.
599 If \p hash is not found the function returns \p nullptr.
601 RCU should be locked before the function invocation.
602 Returned pointer is valid only while RCU is locked.
606 typedef cds::container::FeldmanHashMap< your_template_params > my_map;
613 foo * p = theMap.get( 5 );
621 template <typename K>
622 value_type * get( K const& key )
624 node_type * p = base_class::get( m_Hasher( key_type( key )));
625 return p ? &p->m_Value : nullptr;
628 /// Clears the map (non-atomic)
630 The function unlink all data node from the map.
631 The function is not atomic but is thread-safe.
632 After \p %clear() the map may not be empty because another threads may insert items.
639 /// Checks if the map is empty
641 Emptiness is checked by item counting: if item count is zero then the map is empty.
642 Thus, the correct item counting feature is an important part of the map implementation.
646 return base_class::empty();
649 /// Returns item count in the map
652 return base_class::size();
655 /// Returns const reference to internal statistics
656 stat const& statistics() const
658 return base_class::statistics();
661 /// Returns the size of head node
662 size_t head_size() const
664 return base_class::head_size();
667 /// Returns the size of the array node
668 size_t array_node_size() const
670 return base_class::array_node_size();
673 /// Collects tree level statistics into \p stat
675 The function traverses the set and collects statistics for each level of the tree
676 into \p feldman_hashset::level_statistics struct. The element of \p stat[i]
677 represents statistics for level \p i, level 0 is head array.
678 The function is thread-safe and may be called in multi-threaded environment.
680 Result can be useful for estimating efficiency of hash functor you use.
682 void get_level_statistics(std::vector< feldman_hashmap::level_statistics>& stat) const
684 base_class::get_level_statistics(stat);
688 ///@name Thread-safe iterators
689 /** @anchor cds_container_FeldmanHashMap_rcu_iterators
690 The map supports thread-safe iterators: you may iterate over the map in multi-threaded environment
691 under explicit RCU lock.
692 RCU lock requirement means that inserting or searching is allowed but you must not erase the items from the map
693 since erasing under RCU lock can lead to a deadlock. However, another thread can call \p erase() safely
694 while your thread is iterating.
696 A typical example is:
700 uint32_t payload; // only for example
702 typedef cds::urcu::gc< cds::urcu::general_buffered<>> rcu;
703 typedef cds::container::FeldmanHashMap< rcu, std::string, foo> map_type;
709 // iterate over the map
712 typename set_type::rcu_lock l; // scoped RCU lock
715 for ( auto i = m.begin(); i != s.end(); ++i ) {
716 // deal with i. Remember, erasing is prohibited here!
719 } // at this point RCU lock is released
722 Each iterator object supports the common interface:
723 - dereference operators:
725 value_type [const] * operator ->() noexcept
726 value_type [const] & operator *() noexcept
728 - pre-increment and pre-decrement. Post-operators is not supported
729 - equality operators <tt>==</tt> and <tt>!=</tt>.
730 Iterators are equal iff they point to the same cell of the same array node.
731 Note that for two iterators \p it1 and \p it2 the condition <tt> it1 == it2 </tt>
732 does not entail <tt> &(*it1) == &(*it2) </tt>: welcome to concurrent containers
734 @note It is possible the item can be iterated more that once, for example, if an iterator points to the item
735 in an array node that is being splitted.
738 /// Returns an iterator to the beginning of the map
741 return base_class::template init_begin<iterator>();
744 /// Returns an const iterator to the beginning of the map
745 const_iterator begin() const
747 return base_class::template init_begin<const_iterator>();
750 /// Returns an const iterator to the beginning of the map
751 const_iterator cbegin()
753 return base_class::template init_begin<const_iterator>();
756 /// Returns an iterator to the element following the last element of the map. This element acts as a placeholder; attempting to access it results in undefined behavior.
759 return base_class::template init_end<iterator>();
762 /// Returns a const iterator to the element following the last element of the map. This element acts as a placeholder; attempting to access it results in undefined behavior.
763 const_iterator end() const
765 return base_class::template init_end<const_iterator>();
768 /// Returns a const iterator to the element following the last element of the map. This element acts as a placeholder; attempting to access it results in undefined behavior.
769 const_iterator cend()
771 return base_class::template init_end<const_iterator>();
774 /// Returns a reverse iterator to the first element of the reversed map
775 reverse_iterator rbegin()
777 return base_class::template init_rbegin<reverse_iterator>();
780 /// Returns a const reverse iterator to the first element of the reversed map
781 const_reverse_iterator rbegin() const
783 return base_class::template init_rbegin<const_reverse_iterator>();
786 /// Returns a const reverse iterator to the first element of the reversed map
787 const_reverse_iterator crbegin()
789 return base_class::template init_rbegin<const_reverse_iterator>();
792 /// Returns a reverse iterator to the element following the last element of the reversed map
794 It corresponds to the element preceding the first element of the non-reversed container.
795 This element acts as a placeholder, attempting to access it results in undefined behavior.
797 reverse_iterator rend()
799 return base_class::template init_rend<reverse_iterator>();
802 /// Returns a const reverse iterator to the element following the last element of the reversed map
804 It corresponds to the element preceding the first element of the non-reversed container.
805 This element acts as a placeholder, attempting to access it results in undefined behavior.
807 const_reverse_iterator rend() const
809 return base_class::template init_rend<const_reverse_iterator>();
812 /// Returns a const reverse iterator to the element following the last element of the reversed map
814 It corresponds to the element preceding the first element of the non-reversed container.
815 This element acts as a placeholder, attempting to access it results in undefined behavior.
817 const_reverse_iterator crend()
819 return base_class::template init_rend<const_reverse_iterator>();
823 }} // namespace cds::container
825 #endif // #ifndef CDSLIB_CONTAINER_FELDMAN_HASHMAP_RCU_H