///////////////////////////////////////////////////////////////////////////// // Copyright (c) Electronic Arts Inc. All rights reserved. ///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // This file implements an intrusive hash table, which is a hash table whereby // the container nodes are the hash table objects themselves. This has benefits // primarily in terms of memory management. There are some minor limitations // that result from this. // /////////////////////////////////////////////////////////////////////////////// #ifndef EASTL_INTERNAL_INTRUSIVE_HASHTABLE_H #define EASTL_INTERNAL_INTRUSIVE_HASHTABLE_H #include #if defined(EA_PRAGMA_ONCE_SUPPORTED) #pragma once #endif #include #include #include #include #include #include #include EA_DISABLE_ALL_VC_WARNINGS(); #include #include #include EA_RESTORE_ALL_VC_WARNINGS(); namespace eastl { /// intrusive_hash_node /// /// A hash_node stores an element in a hash table, much like a /// linked list node stores an element in a linked list. /// An intrusive_hash_node additionally can, via template parameter, /// store a hash code in the node to speed up hash calculations /// and comparisons in some cases. /// /// To consider: Make a version of intrusive_hash_node which is /// templated on the container type. This would allow for the /// mpNext pointer to be the container itself and thus allow /// for easier debugging. /// /// Example usage: /// struct Widget : public intrusive_hash_node{ ... }; /// /// struct Dagget : public intrusive_hash_node_key{ ... }; /// struct intrusive_hash_node { intrusive_hash_node* mpNext; }; template struct intrusive_hash_node_key : public intrusive_hash_node { typedef Key key_type; Key mKey; }; /// intrusive_node_iterator /// /// Node iterators iterate nodes within a given bucket. /// /// The bConst parameter defines if the iterator is a const_iterator /// or an iterator. /// template struct intrusive_node_iterator { public: typedef intrusive_node_iterator this_type; typedef Value value_type; typedef Value node_type; typedef ptrdiff_t difference_type; typedef typename type_select::type pointer; typedef typename type_select::type reference; typedef EASTL_ITC_NS::forward_iterator_tag iterator_category; public: node_type* mpNode; public: intrusive_node_iterator() : mpNode(NULL) { } explicit intrusive_node_iterator(value_type* pNode) : mpNode(pNode) { } intrusive_node_iterator(const intrusive_node_iterator& x) : mpNode(x.mpNode) { } reference operator*() const { return *mpNode; } pointer operator->() const { return mpNode; } this_type& operator++() { mpNode = static_cast(mpNode->mpNext); return *this; } this_type operator++(int) { this_type temp(*this); mpNode = static_cast(mpNode->mpNext); return temp; } }; // intrusive_node_iterator /// intrusive_hashtable_iterator_base /// /// An intrusive_hashtable_iterator_base iterates the entire hash table and /// not just nodes within a single bucket. Users in general will use a hash /// table iterator much more often, as it is much like other container /// iterators (e.g. vector::iterator). /// /// We define a base class here because it is shared by both const and /// non-const iterators. /// template struct intrusive_hashtable_iterator_base { public: typedef Value value_type; protected: template friend class intrusive_hashtable; template friend struct intrusive_hashtable_iterator; template friend bool operator==(const intrusive_hashtable_iterator_base&, const intrusive_hashtable_iterator_base&); template friend bool operator!=(const intrusive_hashtable_iterator_base&, const intrusive_hashtable_iterator_base&); value_type* mpNode; // Current node within current bucket. value_type** mpBucket; // Current bucket. public: intrusive_hashtable_iterator_base(value_type* pNode, value_type** pBucket) : mpNode(pNode), mpBucket(pBucket) { } void increment_bucket() { ++mpBucket; while(*mpBucket == NULL) // We store an extra bucket with some non-NULL value at the end ++mpBucket; // of the bucket array so that finding the end of the bucket mpNode = *mpBucket; // array is quick and simple. } void increment() { mpNode = static_cast(mpNode->mpNext); while(mpNode == NULL) mpNode = *++mpBucket; } }; // intrusive_hashtable_iterator_base /// intrusive_hashtable_iterator /// /// An intrusive_hashtable_iterator iterates the entire hash table and not /// just nodes within a single bucket. Users in general will use a hash /// table iterator much more often, as it is much like other container /// iterators (e.g. vector::iterator). /// /// The bConst parameter defines if the iterator is a const_iterator /// or an iterator. /// template struct intrusive_hashtable_iterator : public intrusive_hashtable_iterator_base { public: typedef intrusive_hashtable_iterator_base base_type; typedef intrusive_hashtable_iterator this_type; typedef intrusive_hashtable_iterator this_type_non_const; typedef typename base_type::value_type value_type; typedef typename type_select::type pointer; typedef typename type_select::type reference; typedef ptrdiff_t difference_type; typedef EASTL_ITC_NS::forward_iterator_tag iterator_category; public: intrusive_hashtable_iterator() : base_type(NULL, NULL) { } explicit intrusive_hashtable_iterator(value_type* pNode, value_type** pBucket) : base_type(pNode, pBucket) { } explicit intrusive_hashtable_iterator(value_type** pBucket) : base_type(*pBucket, pBucket) { } intrusive_hashtable_iterator(const this_type_non_const& x) : base_type(x.mpNode, x.mpBucket) { } reference operator*() const { return *base_type::mpNode; } pointer operator->() const { return base_type::mpNode; } this_type& operator++() { base_type::increment(); return *this; } this_type operator++(int) { this_type temp(*this); base_type::increment(); return temp; } }; // intrusive_hashtable_iterator /// use_intrusive_key /// /// operator()(x) returns x.mKey. Used in maps, as opposed to sets. /// This is a template policy implementation; it is an alternative to /// the use_self template implementation, which is used for sets. /// template struct use_intrusive_key // : public unary_function // Perhaps we want to make it a subclass of unary_function. { typedef Key result_type; const result_type& operator()(const Node& x) const { return x.mKey; } }; /////////////////////////////////////////////////////////////////////////// /// intrusive_hashtable /// template class intrusive_hashtable { public: typedef intrusive_hashtable this_type; typedef Key key_type; typedef Value value_type; typedef Value mapped_type; typedef Value node_type; typedef uint32_t hash_code_t; typedef Equal key_equal; typedef ptrdiff_t difference_type; typedef eastl_size_t size_type; // See config.h for the definition of eastl_size_t, which defaults to size_t. typedef value_type& reference; typedef const value_type& const_reference; typedef intrusive_node_iterator local_iterator; typedef intrusive_node_iterator const_local_iterator; typedef intrusive_hashtable_iterator iterator; typedef intrusive_hashtable_iterator const_iterator; typedef typename type_select, iterator>::type insert_return_type; typedef typename type_select, eastl::use_intrusive_key >::type extract_key; enum { kBucketCount = bucketCount }; protected: node_type* mBucketArray[kBucketCount + 1]; // '+1' because we have an end bucket which is non-NULL so iterators always stop on it. size_type mnElementCount; Hash mHash; // To do: Use base class optimization to make this go away when it is of zero size. Equal mEqual; // To do: Use base class optimization to make this go away when it is of zero size. public: intrusive_hashtable(const Hash&, const Equal&); void swap(this_type& x); iterator begin() EA_NOEXCEPT { iterator i(mBucketArray); if(!i.mpNode) i.increment_bucket(); return i; } const_iterator begin() const EA_NOEXCEPT { const_iterator i(const_cast(mBucketArray)); if(!i.mpNode) i.increment_bucket(); return i; } const_iterator cbegin() const EA_NOEXCEPT { return begin(); } iterator end() EA_NOEXCEPT { return iterator(mBucketArray + kBucketCount); } const_iterator end() const EA_NOEXCEPT { return const_iterator(const_cast(mBucketArray) + kBucketCount); } const_iterator cend() const EA_NOEXCEPT { return const_iterator(const_cast(mBucketArray) + kBucketCount); } local_iterator begin(size_type n) EA_NOEXCEPT { return local_iterator(mBucketArray[n]); } const_local_iterator begin(size_type n) const EA_NOEXCEPT { return const_local_iterator(mBucketArray[n]); } const_local_iterator cbegin(size_type n) const EA_NOEXCEPT { return const_local_iterator(mBucketArray[n]); } local_iterator end(size_type) EA_NOEXCEPT { return local_iterator(NULL); } const_local_iterator end(size_type) const EA_NOEXCEPT { return const_local_iterator(NULL); } const_local_iterator cend(size_type) const EA_NOEXCEPT { return const_local_iterator(NULL); } size_type size() const EA_NOEXCEPT { return mnElementCount; } bool empty() const EA_NOEXCEPT { return mnElementCount == 0; } size_type bucket_count() const EA_NOEXCEPT // This function is unnecessary, as the user can directly reference { return kBucketCount; } // intrusive_hashtable::kBucketCount as a constant. size_type bucket_size(size_type n) const EA_NOEXCEPT { return (size_type)eastl::distance(begin(n), end(n)); } size_type bucket(const key_type& k) const EA_NOEXCEPT { return (size_type)(mHash(k) % kBucketCount); } public: float load_factor() const EA_NOEXCEPT { return (float)mnElementCount / (float)kBucketCount; } public: insert_return_type insert(value_type& value) { return DoInsertValue(value, integral_constant()); } insert_return_type insert(const_iterator, value_type& value) { return insert(value); } // To consider: We might be able to use the iterator argument to specify a specific insertion location. template void insert(InputIterator first, InputIterator last); public: iterator erase(const_iterator position); iterator erase(const_iterator first, const_iterator last); size_type erase(const key_type& k); iterator remove(value_type& value); // Removes by value instead of by iterator. This is an O(1) operation, due to this hashtable being 'intrusive'. void clear(); public: iterator find(const key_type& k); const_iterator find(const key_type& k) const; /// Implements a find whereby the user supplies a comparison of a different type /// than the hashtable value_type. A useful case of this is one whereby you have /// a container of string objects but want to do searches via passing in char pointers. /// The problem is that without this kind of find, you need to do the expensive operation /// of converting the char pointer to a string so it can be used as the argument to the /// find function. /// /// Example usage: /// hash_set hashSet; /// hashSet.find_as("hello"); // Use default hash and compare. /// /// Example usage (namespaces omitted for brevity): /// hash_set hashSet; /// hashSet.find_as("hello", hash(), equal_to_2()); /// template iterator find_as(const U& u, UHash uhash, BinaryPredicate predicate); template const_iterator find_as(const U& u, UHash uhash, BinaryPredicate predicate) const; template iterator find_as(const U& u); template const_iterator find_as(const U& u) const; size_type count(const key_type& k) const; // The use for equal_range in a hash_table seems somewhat questionable. // The primary reason for its existence is to replicate the interface of set/map. eastl::pair equal_range(const key_type& k); eastl::pair equal_range(const key_type& k) const; public: bool validate() const; int validate_iterator(const_iterator i) const; public: Hash hash_function() const { return mHash; } Equal equal_function() const // Deprecated. Use key_eq() instead, as key_eq is what the new C++ standard { return mEqual; } // has specified in its hashtable (unordered_*) proposal. const key_equal& key_eq() const { return mEqual; } key_equal& key_eq() { return mEqual; } protected: eastl::pair DoInsertValue(value_type&, true_type); // true_type means bUniqueKeys is true. iterator DoInsertValue(value_type&, false_type); // false_type means bUniqueKeys is false. node_type* DoFindNode(node_type* pNode, const key_type& k) const; template node_type* DoFindNode(node_type* pNode, const U& u, BinaryPredicate predicate) const; }; // class intrusive_hashtable /////////////////////////////////////////////////////////////////////// // node_iterator_base /////////////////////////////////////////////////////////////////////// template inline bool operator==(const intrusive_node_iterator& a, const intrusive_node_iterator& b) { return a.mpNode == b.mpNode; } template inline bool operator!=(const intrusive_node_iterator& a, const intrusive_node_iterator& b) { return a.mpNode != b.mpNode; } /////////////////////////////////////////////////////////////////////// // hashtable_iterator_base /////////////////////////////////////////////////////////////////////// template inline bool operator==(const intrusive_hashtable_iterator_base& a, const intrusive_hashtable_iterator_base& b) { return a.mpNode == b.mpNode; } template inline bool operator!=(const intrusive_hashtable_iterator_base& a, const intrusive_hashtable_iterator_base& b) { return a.mpNode != b.mpNode; } /////////////////////////////////////////////////////////////////////// // intrusive_hashtable /////////////////////////////////////////////////////////////////////// template inline intrusive_hashtable::intrusive_hashtable(const H& h, const Eq& eq) : mnElementCount(0), mHash(h), mEqual(eq) { memset(mBucketArray, 0, kBucketCount * sizeof(mBucketArray[0])); mBucketArray[kBucketCount] = reinterpret_cast((uintptr_t)~0); } template void intrusive_hashtable::swap(this_type& x) { for(size_t i = 0; i < kBucketCount; i++) eastl::swap(mBucketArray[i], x.mBucketArray[i]); eastl::swap(mnElementCount, x.mnElementCount); eastl::swap(mHash, x.mHash); eastl::swap(mEqual, x.mEqual); } template inline typename intrusive_hashtable::iterator intrusive_hashtable::find(const key_type& k) { const size_type n = (size_type)(mHash(k) % kBucketCount); node_type* const pNode = DoFindNode(mBucketArray[n], k); return pNode ? iterator(pNode, mBucketArray + n) : iterator(mBucketArray + kBucketCount); } template inline typename intrusive_hashtable::const_iterator intrusive_hashtable::find(const key_type& k) const { const size_type n = (size_type)(mHash(k) % kBucketCount); node_type* const pNode = DoFindNode(mBucketArray[n], k); return pNode ? const_iterator(pNode, const_cast(mBucketArray) + n) : const_iterator(const_cast(mBucketArray) + kBucketCount); } template template inline typename intrusive_hashtable::iterator intrusive_hashtable::find_as(const U& other, UHash uhash, BinaryPredicate predicate) { const size_type n = (size_type)(uhash(other) % kBucketCount); node_type* const pNode = DoFindNode(mBucketArray[n], other, predicate); return pNode ? iterator(pNode, mBucketArray + n) : iterator(mBucketArray + kBucketCount); } template template inline typename intrusive_hashtable::const_iterator intrusive_hashtable::find_as(const U& other, UHash uhash, BinaryPredicate predicate) const { const size_type n = (size_type)(uhash(other) % kBucketCount); node_type* const pNode = DoFindNode(mBucketArray[n], other, predicate); return pNode ? const_iterator(pNode, const_cast(mBucketArray) + n) : const_iterator(const_cast(mBucketArray) + kBucketCount); } /// intrusive_hashtable_find /// /// Helper function that defaults to using hash and equal_to_2. /// This makes it so that by default you don't need to provide these. /// Note that the default hash functions may not be what you want, though. /// /// Example usage. Instead of this: /// hash_set hashSet; /// hashSet.find("hello", hash(), equal_to_2()); /// /// You can use this: /// hash_set hashSet; /// hashtable_find(hashSet, "hello"); /// template inline typename H::iterator intrusive_hashtable_find(H& hashTable, const U& u) { return hashTable.find_as(u, eastl::hash(), eastl::equal_to_2()); } template inline typename H::const_iterator intrusive_hashtable_find(const H& hashTable, const U& u) { return hashTable.find_as(u, eastl::hash(), eastl::equal_to_2()); } template template inline typename intrusive_hashtable::iterator intrusive_hashtable::find_as(const U& other) { return eastl::intrusive_hashtable_find(*this, other); } // VC++ doesn't appear to like the following, though it seems correct to me. // So we implement the workaround above until we can straighten this out. //{ return find_as(other, eastl::hash(), eastl::equal_to_2()); } template template inline typename intrusive_hashtable::const_iterator intrusive_hashtable::find_as(const U& other) const { return eastl::intrusive_hashtable_find(*this, other); } // VC++ doesn't appear to like the following, though it seems correct to me. // So we implement the workaround above until we can straighten this out. //{ return find_as(other, eastl::hash(), eastl::equal_to_2()); } template typename intrusive_hashtable::size_type intrusive_hashtable::count(const key_type& k) const { const size_type n = (size_type)(mHash(k) % kBucketCount); size_type result = 0; extract_key extractKey; // extract_key is empty and thus this ctor is a no-op. // To do: Make a specialization for bU (unique keys) == true and take // advantage of the fact that the count will always be zero or one in that case. for(node_type* pNode = mBucketArray[n]; pNode; pNode = static_cast(pNode->mpNext)) { if(mEqual(k, extractKey(*pNode))) ++result; } return result; } template eastl::pair::iterator, typename intrusive_hashtable::iterator> intrusive_hashtable::equal_range(const key_type& k) { const size_type n = (size_type)(mHash(k) % kBucketCount); node_type** head = mBucketArray + n; node_type* pNode = DoFindNode(*head, k); extract_key extractKey; // extract_key is empty and thus this ctor is a no-op. if(pNode) { node_type* p1 = static_cast(pNode->mpNext); for(; p1; p1 = static_cast(p1->mpNext)) { if(!mEqual(k, extractKey(*p1))) break; } iterator first(pNode, head); iterator last(p1, head); if(!p1) last.increment_bucket(); return eastl::pair(first, last); } return eastl::pair(iterator(mBucketArray + kBucketCount), iterator(mBucketArray + kBucketCount)); } template eastl::pair::const_iterator, typename intrusive_hashtable::const_iterator> intrusive_hashtable::equal_range(const key_type& k) const { const size_type n = (size_type)(mHash(k) % kBucketCount); node_type** head = const_cast(mBucketArray + n); node_type* pNode = DoFindNode(*head, k); extract_key extractKey; // extract_key is empty and thus this ctor is a no-op. if(pNode) { node_type* p1 = static_cast(pNode->mpNext); for(; p1; p1 = static_cast(p1->mpNext)) { if(!mEqual(k, extractKey(*p1))) break; } const_iterator first(pNode, head); const_iterator last(p1, head); if(!p1) last.increment_bucket(); return eastl::pair(first, last); } return eastl::pair(const_iterator(const_cast(mBucketArray) + kBucketCount), const_iterator(const_cast(mBucketArray) + kBucketCount)); } template inline typename intrusive_hashtable::node_type* intrusive_hashtable::DoFindNode(node_type* pNode, const key_type& k) const { extract_key extractKey; // extract_key is empty and thus this ctor is a no-op. for(; pNode; pNode = static_cast(pNode->mpNext)) { if(mEqual(k, extractKey(*pNode))) return pNode; } return NULL; } template template inline typename intrusive_hashtable::node_type* intrusive_hashtable::DoFindNode(node_type* pNode, const U& other, BinaryPredicate predicate) const { extract_key extractKey; // extract_key is empty and thus this ctor is a no-op. for(; pNode; pNode = static_cast(pNode->mpNext)) { if(predicate(extractKey(*pNode), other)) // Intentionally compare with key as first arg and other as second arg. return pNode; } return NULL; } template eastl::pair::iterator, bool> intrusive_hashtable::DoInsertValue(value_type& value, true_type) // true_type means bUniqueKeys is true. { // For sets (as opposed to maps), one could argue that all insertions are successful, // as all elements are unique. However, the equal function might not think so. extract_key extractKey; // extract_key is empty and thus this ctor is a no-op. const size_type n = (size_type)(mHash(extractKey(value)) % kBucketCount); node_type* const pNode = DoFindNode(mBucketArray[n], extractKey(value)); if(pNode == NULL) { value.mpNext = mBucketArray[n]; mBucketArray[n] = &value; ++mnElementCount; return eastl::pair(iterator(&value, mBucketArray + n), true); } return eastl::pair(iterator(pNode, mBucketArray + n), false); } template typename intrusive_hashtable::iterator intrusive_hashtable::DoInsertValue(value_type& value, false_type) // false_type means bUniqueKeys is false. { extract_key extractKey; // extract_key is empty and thus this ctor is a no-op. const size_type n = (size_type)(mHash(extractKey(value)) % kBucketCount); node_type* const pNodePrev = DoFindNode(mBucketArray[n], extractKey(value)); if(pNodePrev == NULL) { value.mpNext = mBucketArray[n]; mBucketArray[n] = &value; } else { value.mpNext = pNodePrev->mpNext; pNodePrev->mpNext = &value; } ++mnElementCount; return iterator(&value, mBucketArray + n); } template template inline void intrusive_hashtable::insert(InputIterator first, InputIterator last) { for(; first != last; ++first) insert(*first); } template typename intrusive_hashtable::iterator intrusive_hashtable::erase(const_iterator i) { iterator iNext(i.mpNode, i.mpBucket); ++iNext; node_type* pNode = i.mpNode; node_type* pNodeCurrent = *i.mpBucket; if(pNodeCurrent == pNode) *i.mpBucket = static_cast(pNodeCurrent->mpNext); else { // We have a singly-linked list, so we have no choice but to // walk down it till we find the node before the node at 'i'. node_type* pNodeNext = static_cast(pNodeCurrent->mpNext); while(pNodeNext != pNode) { pNodeCurrent = pNodeNext; pNodeNext = static_cast(pNodeCurrent->mpNext); } pNodeCurrent->mpNext = static_cast(pNodeNext->mpNext); } // To consider: In debug builds set the node mpNext to NULL. --mnElementCount; return iNext; } template inline typename intrusive_hashtable::iterator intrusive_hashtable::erase(const_iterator first, const_iterator last) { while(first != last) first = erase(first); return iterator(first.mpNode, first.mpBucket); } template typename intrusive_hashtable::size_type intrusive_hashtable::erase(const key_type& k) { const size_type n = (size_type)(mHash(k) % kBucketCount); const size_type nElementCountSaved = mnElementCount; node_type*& pNodeBase = mBucketArray[n]; extract_key extractKey; // extract_key is empty and thus this ctor is a no-op. // Note by Paul Pedriana: // We have two loops here, and I'm not finding any easy way to having just one // loop without changing the requirements of the hashtable node definition. // It's a problem of taking an address of a variable and converting it to the // address of another type without knowing what that type is. Perhaps I'm a // little overly tired, so if there is a simple solution I am probably missing it. while(pNodeBase && mEqual(k, extractKey(*pNodeBase))) { pNodeBase = static_cast(pNodeBase->mpNext); --mnElementCount; } node_type* pNodePrev = pNodeBase; if(pNodePrev) { node_type* pNodeCur; while((pNodeCur = static_cast(pNodePrev->mpNext)) != NULL) { if(mEqual(k, extractKey(*pNodeCur))) { pNodePrev->mpNext = static_cast(pNodeCur->mpNext); --mnElementCount; // To consider: In debug builds set the node mpNext to NULL. } else pNodePrev = static_cast(pNodePrev->mpNext); } } return nElementCountSaved - mnElementCount; } template inline typename intrusive_hashtable::iterator intrusive_hashtable::remove(value_type& value) { extract_key extractKey; // extract_key is empty and thus this ctor is a no-op. const size_type n = (size_type)(mHash(extractKey(value)) % kBucketCount); return erase(iterator(&value, &mBucketArray[n])); } template inline void intrusive_hashtable::clear() { // To consider: In debug builds set the node mpNext to NULL. memset(mBucketArray, 0, kBucketCount * sizeof(mBucketArray[0])); mnElementCount = 0; } template inline bool intrusive_hashtable::validate() const { // Verify that the element count matches mnElementCount. size_type nElementCount = 0; for(const_iterator temp = begin(), tempEnd = end(); temp != tempEnd; ++temp) ++nElementCount; if(nElementCount != mnElementCount) return false; // To do: Verify that individual elements are in the expected buckets. return true; } template int intrusive_hashtable::validate_iterator(const_iterator i) const { // To do: Come up with a more efficient mechanism of doing this. for(const_iterator temp = begin(), tempEnd = end(); temp != tempEnd; ++temp) { if(temp == i) return (isf_valid | isf_current | isf_can_dereference); } if(i == end()) return (isf_valid | isf_current); return isf_none; } /////////////////////////////////////////////////////////////////////// // global operators /////////////////////////////////////////////////////////////////////// template inline bool operator==(const intrusive_hashtable& a, const intrusive_hashtable& b) { return (a.size() == b.size()) && eastl::equal(a.begin(), a.end(), b.begin()); } template inline bool operator!=(const intrusive_hashtable& a, const intrusive_hashtable& b) { return !(a == b); } // Comparing hash tables for less-ness is an odd thing to do. We provide it for // completeness, though the user is advised to be wary of how they use this. template inline bool operator<(const intrusive_hashtable& a, const intrusive_hashtable& b) { // This requires hash table elements to support operator<. Since the hash table // doesn't compare elements via less (it does so via equals), we must use the // globally defined operator less for the elements. return eastl::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); } template inline bool operator>(const intrusive_hashtable& a, const intrusive_hashtable& b) { return b < a; } template inline bool operator<=(const intrusive_hashtable& a, const intrusive_hashtable& b) { return !(b < a); } template inline bool operator>=(const intrusive_hashtable& a, const intrusive_hashtable& b) { return !(a < b); } template inline void swap(const intrusive_hashtable& a, const intrusive_hashtable& b) { a.swap(b); } } // namespace eastl #endif // Header include guard