X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=gdax-orderbook-hpp%2Fdemo%2Fdependencies%2Flibcds-2.3.2%2Fcds%2Fintrusive%2Fmichael_set_rcu.h;fp=gdax-orderbook-hpp%2Fdemo%2Fdependencies%2Flibcds-2.3.2%2Fcds%2Fintrusive%2Fmichael_set_rcu.h;h=d5efd0cf928f0b36b08583c8ad586f703bf02300;hb=4223430d9168223029c7639149025c79e69b4f37;hp=0000000000000000000000000000000000000000;hpb=7ea7751a31c0388bf888052517be181a2989b113;p=c11concurrency-benchmarks.git diff --git a/gdax-orderbook-hpp/demo/dependencies/libcds-2.3.2/cds/intrusive/michael_set_rcu.h b/gdax-orderbook-hpp/demo/dependencies/libcds-2.3.2/cds/intrusive/michael_set_rcu.h new file mode 100644 index 0000000..d5efd0c --- /dev/null +++ b/gdax-orderbook-hpp/demo/dependencies/libcds-2.3.2/cds/intrusive/michael_set_rcu.h @@ -0,0 +1,772 @@ +/* + This file is a part of libcds - Concurrent Data Structures library + + (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2017 + + Source code repo: http://github.com/khizmax/libcds/ + Download: http://sourceforge.net/projects/libcds/files/ + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef CDSLIB_INTRUSIVE_MICHAEL_SET_RCU_H +#define CDSLIB_INTRUSIVE_MICHAEL_SET_RCU_H + +#include + +namespace cds { namespace intrusive { + + /// Michael's hash set, \ref cds_urcu_desc "RCU" specialization + /** @ingroup cds_intrusive_map + \anchor cds_intrusive_MichaelHashSet_rcu + + Source: + - [2002] Maged Michael "High performance dynamic lock-free hash tables and list-based sets" + + Michael's hash table algorithm is based on lock-free ordered list and it is very simple. + The main structure is an array \p T of size \p M. Each element in \p T is basically a pointer + to a hash bucket, implemented as a singly linked list. The array of buckets cannot be dynamically expanded. + However, each bucket may contain unbounded number of items. + + Template parameters are: + - \p RCU - one of \ref cds_urcu_gc "RCU type" + - \p OrderedList - ordered list implementation used as bucket for hash set, for example, MichaelList, LazyList. + The intrusive ordered list implementation specifies the type \p T stored in the hash-set, the reclamation + schema \p GC used by hash-set, the comparison functor for the type \p T and other features specific for + the ordered list. + - \p Traits - type traits, default is \p michael_set::traits. + Instead of defining \p Traits struct you can use option-based syntax with \p michael_set::make_traits metafunction. + + \par Usage + Before including you should include appropriate RCU header file, + see \ref cds_urcu_gc "RCU type" for list of existing RCU class and corresponding header files. + For example, for \ref cds_urcu_general_buffered_gc "general-purpose buffered RCU" you should include: + \code + #include + #include + #include + + struct Foo { ... }; + // Hash functor for struct Foo + struct foo_hash { + size_t operator()( Foo const& foo ) const { return ... } + }; + + // Now, you can declare Michael's list for type Foo and default traits: + typedef cds::intrusive::MichaelList >, Foo > rcu_michael_list; + + // Declare Michael's set with MichaelList as bucket type + typedef cds::intrusive::MichaelSet< + cds::urcu::gc< general_buffered<> >, + rcu_michael_list, + cds::intrusive::michael_set::make_traits< + cds::opt::::hash< foo_hash > + >::type + > rcu_michael_set; + + // Declares hash set for 1000000 items with load factor 2 + rcu_michael_set theSet( 1000000, 2 ); + + // Now you can use theSet object in many threads without any synchronization. + \endcode + */ + template < + class RCU, + class OrderedList, +#ifdef CDS_DOXYGEN_INVOKED + class Traits = michael_set::traits +#else + class Traits +#endif + > + class MichaelHashSet< cds::urcu::gc< RCU >, OrderedList, Traits > + { + public: + typedef cds::urcu::gc< RCU > gc; ///< RCU schema + typedef OrderedList ordered_list; ///< type of ordered list used as a bucket implementation + typedef Traits traits; ///< Set traits + + typedef typename ordered_list::value_type value_type; ///< type of value stored in the list + typedef typename ordered_list::key_comparator key_comparator; ///< key comparing functor + typedef typename ordered_list::disposer disposer; ///< Node disposer functor +#ifdef CDS_DOXYGEN_INVOKED + typedef typename ordered_list::stat stat; ///< Internal statistics +#endif + + /// Hash functor for \ref value_type and all its derivatives that you use + typedef typename cds::opt::v::hash_selector< typename traits::hash >::type hash; + typedef typename traits::item_counter item_counter; ///< Item counter type + typedef typename traits::allocator allocator; ///< Bucket table allocator + + typedef typename ordered_list::rcu_lock rcu_lock; ///< RCU scoped lock + /// Group of \p extract_xxx functions require external locking if underlying ordered list requires that + static constexpr const bool c_bExtractLockExternal = ordered_list::c_bExtractLockExternal; + + // GC and OrderedList::gc must be the same + static_assert(std::is_same::value, "GC and OrderedList::gc must be the same"); + + protected: + //@cond + typedef typename ordered_list::template select_stat_wrapper< typename ordered_list::stat > bucket_stat; + + typedef typename ordered_list::template rebind_traits< + cds::opt::item_counter< cds::atomicity::empty_item_counter > + , cds::opt::stat< typename bucket_stat::wrapped_stat > + >::type internal_bucket_type; + + typedef typename allocator::template rebind< internal_bucket_type >::other bucket_table_allocator; + //@endcond + + public: + typedef typename internal_bucket_type::exempt_ptr exempt_ptr; ///< pointer to extracted node + typedef typename internal_bucket_type::raw_ptr raw_ptr; ///< Return type of \p get() member function and its derivatives + + //@cond + typedef typename bucket_stat::stat stat; + //@endcond + + private: + //@cond + hash m_HashFunctor; ///< Hash functor + size_t const m_nHashBitmask; + internal_bucket_type* m_Buckets; ///< bucket table + item_counter m_ItemCounter; ///< Item counter + stat m_Stat; ///< Internal statistics + //@endcond + + public: + ///@name Forward iterators (thread-safe under RCU lock) + //@{ + /// Forward iterator + /** + The forward iterator for Michael's set is based on \p OrderedList forward iterator and has some features: + - it has no post-increment operator + - it iterates items in unordered fashion + + You may safely use iterators in multi-threaded environment only under RCU lock. + Otherwise, a crash is possible if another thread deletes the element the iterator points to. + + The iterator interface: + \code + class iterator { + public: + // Default constructor + iterator(); + + // Copy construtor + iterator( iterator const& src ); + + // Dereference operator + value_type * operator ->() const; + + // Dereference operator + value_type& operator *() const; + + // Preincrement operator + iterator& operator ++(); + + // Assignment operator + iterator& operator = (iterator const& src); + + // Equality operators + bool operator ==(iterator const& i ) const; + bool operator !=(iterator const& i ) const; + }; + \endcode + */ + typedef michael_set::details::iterator< internal_bucket_type, false > iterator; + + /// Const forward iterator + /** + For iterator's features and requirements see \ref iterator + */ + typedef michael_set::details::iterator< internal_bucket_type, true > const_iterator; + + /// Returns a forward iterator addressing the first element in a set + /** + For empty set \code begin() == end() \endcode + */ + iterator begin() + { + return iterator( m_Buckets[0].begin(), m_Buckets, m_Buckets + bucket_count()); + } + + /// Returns an iterator that addresses the location succeeding the last element in a set + /** + Do not use the value returned by end function to access any item. + The returned value can be used only to control reaching the end of the set. + For empty set \code begin() == end() \endcode + */ + iterator end() + { + return iterator( m_Buckets[bucket_count() - 1].end(), m_Buckets + bucket_count() - 1, m_Buckets + bucket_count()); + } + + /// Returns a forward const iterator addressing the first element in a set + const_iterator begin() const + { + return cbegin(); + } + + /// Returns a forward const iterator addressing the first element in a set + const_iterator cbegin() const + { + return const_iterator( m_Buckets[0].cbegin(), m_Buckets, m_Buckets + bucket_count()); + } + + /// Returns an const iterator that addresses the location succeeding the last element in a set + const_iterator end() const + { + return cend(); + } + + /// Returns an const iterator that addresses the location succeeding the last element in a set + const_iterator cend() const + { + return const_iterator( m_Buckets[bucket_count() - 1].cend(), m_Buckets + bucket_count() - 1, m_Buckets + bucket_count()); + } + //@} + + public: + /// Initialize hash set + /** + The Michael's hash set is an unbounded container, but its hash table is non-expandable. + At construction time you should pass estimated maximum item count and a load factor. + The load factor is average size of one bucket - a small number between 1 and 10. + The bucket is an ordered single-linked list, the complexity of searching in the bucket is linear O(nLoadFactor). + The constructor defines hash table size as rounding nMaxItemCount / nLoadFactor up to nearest power of two. + */ + MichaelHashSet( + size_t nMaxItemCount, ///< estimation of max item count in the hash set + size_t nLoadFactor ///< load factor: average size of the bucket + ) : m_nHashBitmask( michael_set::details::init_hash_bitmask( nMaxItemCount, nLoadFactor )) + , m_Buckets( bucket_table_allocator().allocate( bucket_count())) + { + for ( auto it = m_Buckets, itEnd = m_Buckets + bucket_count(); it != itEnd; ++it ) + construct_bucket( it ); + } + + /// Clear hash set and destroy it + ~MichaelHashSet() + { + clear(); + + for ( auto it = m_Buckets, itEnd = m_Buckets + bucket_count(); it != itEnd; ++it ) + it->~internal_bucket_type(); + bucket_table_allocator().deallocate( m_Buckets, bucket_count()); + } + + /// Inserts new node + /** + The function inserts \p val in the set if it does not contain + an item with key equal to \p val. + + Returns \p true if \p val is placed into the set, \p false otherwise. + */ + bool insert( value_type& val ) + { + bool bRet = bucket( val ).insert( val ); + if ( bRet ) + ++m_ItemCounter; + return bRet; + } + + /// Inserts new node + /** + This function is intended for derived non-intrusive containers. + + The function allows to split creating of new item into two part: + - create item with key only + - insert new item into the set + - if inserting is success, calls \p f functor to initialize value-field of \p val. + + The functor signature is: + \code + void func( value_type& val ); + \endcode + where \p val is the item inserted. + The user-defined functor is called only if the inserting is success. + + @warning For \ref cds_intrusive_MichaelList_rcu "MichaelList" as the bucket see \ref cds_intrusive_item_creating "insert item troubleshooting". + \ref cds_intrusive_LazyList_rcu "LazyList" provides exclusive access to inserted item and does not require any node-level + synchronization. + */ + template + bool insert( value_type& val, Func f ) + { + bool bRet = bucket( val ).insert( val, f ); + if ( bRet ) + ++m_ItemCounter; + return bRet; + } + + /// Updates the element + /** + The operation performs inserting or changing data with lock-free manner. + + If the item \p val not found in the set, then \p val is inserted iff \p bAllowInsert is \p true. + Otherwise, the functor \p func is called with item found. + The functor signature is: + \code + struct functor { + void operator()( bool bNew, value_type& item, value_type& val ); + }; + \endcode + with arguments: + - \p bNew - \p true if the item has been inserted, \p false otherwise + - \p item - item of the set + - \p val - argument \p val passed into the \p %update() function + If new item has been inserted (i.e. \p bNew is \p true) then \p item and \p val arguments + refers to the same thing. + + The functor may change non-key fields of the \p item. + + Returns std::pair where \p first is \p true if operation is successful, + \p second is \p true if new item has been added or \p false if the item with \p key + already is in the set. + + @warning For \ref cds_intrusive_MichaelList_hp "MichaelList" as the bucket see \ref cds_intrusive_item_creating "insert item troubleshooting". + \ref cds_intrusive_LazyList_hp "LazyList" provides exclusive access to inserted item and does not require any node-level + synchronization. + */ + template + std::pair update( value_type& val, Func func, bool bAllowInsert = true ) + { + std::pair bRet = bucket( val ).update( val, func, bAllowInsert ); + if ( bRet.second ) + ++m_ItemCounter; + return bRet; + } + //@cond + template + CDS_DEPRECATED("ensure() is deprecated, use update()") + std::pair ensure( value_type& val, Func func ) + { + return update( val, func, true ); + } + //@endcond + + /// Unlinks the item \p val from the set + /** + The function searches the item \p val in the set and unlink it from the set + if it is found and is equal to \p val. + + The function returns \p true if success and \p false otherwise. + */ + bool unlink( value_type& val ) + { + bool bRet = bucket( val ).unlink( val ); + if ( bRet ) + --m_ItemCounter; + return bRet; + } + + /// Deletes the item from the set + /** \anchor cds_intrusive_MichaelHashSet_rcu_erase + The function searches an item with key equal to \p key in the set, + unlinks it from the set, and returns \p true. + If the item with key equal to \p key is not found the function return \p false. + + Note the hash functor should accept a parameter of type \p Q that may be not the same as \p value_type. + */ + template + bool erase( Q const& key ) + { + if ( bucket( key ).erase( key )) { + --m_ItemCounter; + return true; + } + return false; + } + + /// Deletes the item from the set using \p pred predicate for searching + /** + The function is an analog of \ref cds_intrusive_MichaelHashSet_rcu_erase "erase(Q const&)" + but \p pred is used for key comparing. + \p Less functor has the interface like \p std::less. + \p pred must imply the same element order as the comparator used for building the set. + */ + template + bool erase_with( Q const& key, Less pred ) + { + if ( bucket( key ).erase_with( key, pred )) { + --m_ItemCounter; + return true; + } + return false; + } + + /// Deletes the item from the set + /** \anchor cds_intrusive_MichaelHashSet_rcu_erase_func + The function searches an item with key equal to \p key in the set, + call \p f functor with item found, and unlinks it from the set. + The \ref disposer specified in \p OrderedList class template parameter is called + by garbage collector \p GC asynchronously. + + The \p Func interface is + \code + struct functor { + void operator()( value_type const& item ); + }; + \endcode + + If the item with key equal to \p key is not found the function return \p false. + + Note the hash functor should accept a parameter of type \p Q that can be not the same as \p value_type. + */ + template + bool erase( const Q& key, Func f ) + { + if ( bucket( key ).erase( key, f )) { + --m_ItemCounter; + return true; + } + return false; + } + + /// Deletes the item from the set using \p pred predicate for searching + /** + The function is an analog of \ref cds_intrusive_MichaelHashSet_rcu_erase_func "erase(Q const&)" + but \p pred is used for key comparing. + \p Less functor has the interface like \p std::less. + \p pred must imply the same element order as the comparator used for building the set. + */ + template + bool erase_with( const Q& key, Less pred, Func f ) + { + if ( bucket( key ).erase_with( key, pred, f )) { + --m_ItemCounter; + return true; + } + return false; + } + + /// Extracts an item from the set + /** \anchor cds_intrusive_MichaelHashSet_rcu_extract + The function searches an item with key equal to \p key in the set, + unlinks it from the set, and returns \ref cds::urcu::exempt_ptr "exempt_ptr" pointer to the item found. + If the item with the key equal to \p key is not found the function returns an empty \p exempt_ptr. + + Depends on \p ordered_list you should or should not lock RCU before calling of this function: + - for the set based on \ref cds_intrusive_MichaelList_rcu "MichaelList" RCU should not be locked + - for the set based on \ref cds_intrusive_LazyList_rcu "LazyList" RCU should be locked + + See ordered list implementation for details. + + \code + #include + #include + #include + + typedef cds::urcu::gc< general_buffered<> > rcu; + typedef cds::intrusive::MichaelList< rcu, Foo > rcu_michael_list; + typedef cds::intrusive::MichaelHashSet< rcu, rcu_michael_list, foo_traits > rcu_michael_set; + + rcu_michael_set theSet; + // ... + + typename rcu_michael_set::exempt_ptr p; + + // For MichaelList we should not lock RCU + + // Now, you can apply extract function + // Note that you must not delete the item found inside the RCU lock + p = theSet.extract( 10 ) + if ( p ) { + // do something with p + ... + } + + // We may safely release p here + // release() passes the pointer to RCU reclamation cycle: + // it invokes RCU retire_ptr function with the disposer you provided for rcu_michael_list. + p.release(); + \endcode + */ + template + exempt_ptr extract( Q const& key ) + { + exempt_ptr p( bucket( key ).extract( key )); + if ( p ) + --m_ItemCounter; + return p; + } + + /// Extracts an item from the set using \p pred predicate for searching + /** + The function is an analog of \p extract(Q const&) but \p pred is used for key comparing. + \p Less functor has the interface like \p std::less. + \p pred must imply the same element order as the comparator used for building the set. + */ + template + exempt_ptr extract_with( Q const& key, Less pred ) + { + exempt_ptr p( bucket( key ).extract_with( key, pred )); + if ( p ) + --m_ItemCounter; + return p; + } + + /// Checks whether the set contains \p key + /** + + The function searches the item with key equal to \p key + and returns \p true if the key is found, and \p false otherwise. + + Note the hash functor specified for class \p Traits template parameter + should accept a parameter of type \p Q that can be not the same as \p value_type. + */ + template + bool contains( Q const& key ) + { + return bucket( key ).contains( key ); + } + //@cond + template + CDS_DEPRECATED("use contains()") + bool find( Q const& key ) + { + return contains( key ); + } + //@endcond + + /// Checks whether the set contains \p key using \p pred predicate for searching + /** + The function is an analog of contains( key ) but \p pred is used for key comparing. + \p Less functor has the interface like \p std::less. + \p Less must imply the same element order as the comparator used for building the set. + */ + template + bool contains( Q const& key, Less pred ) + { + return bucket( key ).contains( key, pred ); + } + //@cond + template + CDS_DEPRECATED("use contains()") + bool find_with( Q const& key, Less pred ) + { + return contains( key, pred ); + } + //@endcond + + /// Find the key \p key + /** \anchor cds_intrusive_MichaelHashSet_rcu_find_func + The function searches the item with key equal to \p key and calls the functor \p f for item found. + The interface of \p Func functor is: + \code + struct functor { + void operator()( value_type& item, Q& key ); + }; + \endcode + where \p item is the item found, \p key is the find function argument. + + The functor can change non-key fields of \p item. + The functor does not serialize simultaneous access to the set \p item. If such access is + possible you must provide your own synchronization schema on item level to exclude unsafe item modifications. + + The \p key argument is non-const since it can be used as \p f functor destination i.e., the functor + can modify both arguments. + + Note the hash functor specified for class \p Traits template parameter + should accept a parameter of type \p Q that can be not the same as \p value_type. + + The function returns \p true if \p key is found, \p false otherwise. + */ + template + bool find( Q& key, Func f ) + { + return bucket( key ).find( key, f ); + } + //@cond + template + bool find( Q const& key, Func f ) + { + return bucket( key ).find( key, f ); + } + //@endcond + + /// Finds the key \p key using \p pred predicate for searching + /** + The function is an analog of \ref cds_intrusive_MichaelHashSet_rcu_find_func "find(Q&, Func)" + but \p pred is used for key comparing. + \p Less functor has the interface like \p std::less. + \p pred must imply the same element order as the comparator used for building the set. + */ + template + bool find_with( Q& key, Less pred, Func f ) + { + return bucket( key ).find_with( key, pred, f ); + } + //@cond + template + bool find_with( Q const& key, Less pred, Func f ) + { + return bucket( key ).find_with( key, pred, f ); + } + //@endcond + + /// Finds the key \p key and return the item found + /** \anchor cds_intrusive_MichaelHashSet_rcu_get + The function searches the item with key equal to \p key and returns the pointer to item found. + If \p key is not found it returns \p nullptr. + Note the type of returned value depends on underlying \p ordered_list. + For details, see documentation of ordered list you use. + + Note the compare functor should accept a parameter of type \p Q that can be not the same as \p value_type. + + RCU should be locked before call of this function. + Returned item is valid only while RCU is locked: + \code + typedef cds::intrusive::MichaelHashSet< your_template_parameters > hash_set; + hash_set theSet; + // ... + // Result of get() call + typename hash_set::raw_ptr ptr; + { + // Lock RCU + hash_set::rcu_lock lock; + + ptr = theSet.get( 5 ); + if ( ptr ) { + // Deal with ptr + //... + } + // Unlock RCU by rcu_lock destructor + // ptr can be reclaimed by disposer at any time after RCU has been unlocked + } + \endcode + */ + template + raw_ptr get( Q const& key ) + { + return bucket( key ).get( key ); + } + + /// Finds the key \p key and return the item found + /** + The function is an analog of \ref cds_intrusive_MichaelHashSet_rcu_get "get(Q const&)" + but \p pred is used for comparing the keys. + + \p Less functor has the semantics like \p std::less but should take arguments of type \ref value_type and \p Q + in any order. + \p pred must imply the same element order as the comparator used for building the set. + */ + template + raw_ptr get_with( Q const& key, Less pred ) + { + return bucket( key ).get_with( key, pred ); + } + + /// Clears the set (non-atomic) + /** + The function unlink all items from the set. + The function is not atomic. It cleans up each bucket and then resets the item counter to zero. + If there are a thread that performs insertion while \p clear is working the result is undefined in general case: + empty() may return \p true but the set may contain item(s). + Therefore, \p clear may be used only for debugging purposes. + + For each item the \p disposer is called after unlinking. + */ + void clear() + { + for ( size_t i = 0; i < bucket_count(); ++i ) + m_Buckets[i].clear(); + m_ItemCounter.reset(); + } + + + /// Checks if the set is empty + /** + @warning If you use \p atomicity::empty_item_counter in \p traits::item_counter, + the function always returns \p true. + */ + bool empty() const + { + return size() == 0; + } + + /// Returns item count in the set + /** + If you use \p atomicity::empty_item_counter in \p traits::item_counter, + the function always returns 0. + */ + size_t size() const + { + return m_ItemCounter; + } + + /// Returns the size of hash table + /** + Since %MichaelHashSet cannot dynamically extend the hash table size, + the value returned is an constant depending on object initialization parameters; + see \ref cds_intrusive_MichaelHashSet_hp "MichaelHashSet" for explanation. + */ + size_t bucket_count() const + { + return m_nHashBitmask + 1; + } + + /// Returns const reference to internal statistics + stat const& statistics() const + { + return m_Stat; + } + + private: + //@cond + template + typename std::enable_if< Stat::empty >::type construct_bucket( internal_bucket_type * bkt ) + { + new (bkt) internal_bucket_type; + } + + template + typename std::enable_if< !Stat::empty >::type construct_bucket( internal_bucket_type * bkt ) + { + new (bkt) internal_bucket_type( m_Stat ); + } + + /// Calculates hash value of \p key + template + size_t hash_value( Q const& key ) const + { + return m_HashFunctor( key ) & m_nHashBitmask; + } + + /// Returns the bucket (ordered list) for \p key + template + internal_bucket_type& bucket( Q const& key ) + { + return m_Buckets[hash_value( key )]; + } + template + internal_bucket_type const& bucket( Q const& key ) const + { + return m_Buckets[hash_value( key )]; + } + //@endcond + }; + +}} // namespace cds::intrusive + +#endif // #ifndef CDSLIB_INTRUSIVE_MICHAEL_SET_NOGC_H