// bdlcc_stripedunorderedcontainerimpl.h                              -*-C++-*-
#ifndef INCLUDED_BDLCC_STRIPEDUNORDEREDCONTAINERIMPL
#define INCLUDED_BDLCC_STRIPEDUNORDEREDCONTAINERIMPL

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide common implementation of *striped* un-ordered map/multimap.
//
//@CLASSES:
//  bdlcc::StripedUnorderedContainerImpl: striped container for key-value types
//
//@SEE_ALSO: bdlcc_stripedunorderedmap, bdlcc_stripedunorderedmultimap
//
//@DESCRIPTON: This component provides 'bdlcc::StripedUnorderedContainerImpl',
// a common implementation for 'bdlcc::StripedUnorderedMap' and
// 'bdlcc::StripedUnorderedMultiMap', that are concurrent (fully thread-safe)
// associative containers that partition their underlying hash tables into a
// (user-defined) number of "bucket groups" and control access to each part of
// their hash tables by separate read-write locks.  For most methods, the "map"
// and "multimap" classes forward to the analogous method in this "impl" class
// with an additional argument that specifies if the calling class has unique
// keys or not.
//
///Thread Safety
///-------------
// The 'bdlcc::StripedUnorderedContrainerImpl' class template is fully
// thread-safe (see {'bsldoc_glossary'|Fully Thread-Safe}), assuming that the
// allocator is fully thread-safe.  Each method is executed by the calling
// thread.
//
///Runtime Complexity
///------------------
//..
//  +----------------------------------------------------+--------------------+
//  | Operation                                          | Complexity         |
//  +====================================================+====================+
//  | insert, setValue, setComputedValue, update         | Average: O[1]      |
//  |                                                    | Worst:   O[n]      |
//  +----------------------------------------------------+--------------------+
//  | erase, getValue                                    | Average: O[1]      |
//  |                                                    | Worst:   O[n]      |
//  +----------------------------------------------------+--------------------+
//  | visit(key, visitor)                                | Average: O[1]      |
//  | visitReadOnly(key, visitor)                        | Worst:   O[n]      |
//  +----------------------------------------------------+--------------------+
//  | insertBulk, k elements                             | Average: O[k]      |
//  |                                                    | Worst:   O[n*k]    |
//  +----------------------------------------------------+--------------------+
//  | eraseBulk, k elements                              | Average: O[k]      |
//  |                                                    | Worst:   O[n*k]    |
//  +----------------------------------------------------+--------------------+
//  | rehash                                             | O[n]               |
//  +----------------------------------------------------+--------------------+
//  | visit(visitor), visitReadOnly(visitor)             | O[n]               |
//  +----------------------------------------------------+--------------------+
//..
//
///Number of Stripes
///-----------------
// Performance improves monotonically when the number of stripes increases.
// However, the rate of improvement decreases, and reaches a plateau.  The
// plateau is reached roughly at four times the number of the threads
// *concurrently* using the hash map.
//
///Rehash
///------
//
///Concurrent Rehash
///- - - - - - - - -
// A rehash operation is a re-organization of the hash map to a different
// number of buckets.  This is a heavy operation that interferes with, but does
// *not* disallow, other operations on the container.  Rehash is warranted when
// the current load factor exceeds the current maximum allowed load factor.
// Expressed explicitly:
//..
//  bucketCount() <= maxLoadFactor() * size();
//..
// This above condition is tested implicitly by several methods and if found
// true (and if rehash is enabled and rehash is not underway), a rehash is
// started.  The methods that check the load factor are:
//
//: o All methods that insert elements (i.e., increase 'size()').
//: o The 'maxLoadFactor(newMaxLoadFactor)' method.
//: o The 'rehash' method.
//
///Rehash Control
/// - - - - - - -
// 'enableRehash' and 'disableRehash' methods are provided to control the
// rehash enable flag.  Note that disabling rehash does not impact a rehash in
// progress.
//
///Usage
///-----
// There is no usage example for this component since it is not meant for
// direct client use.

#include <bdlscm_version.h>

#include <bslalg_hashtableimputil.h>

#include <bslim_printer.h>

#include <bslstl_hash.h>
#include <bslstl_pair.h>

#include <bslma_allocator.h>
#include <bslma_constructionutil.h>
#include <bslma_default.h>
#include <bslma_destructionutil.h>
#include <bslma_destructorproctor.h>
#include <bslma_rawdeleterproctor.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_movableref.h>
#include <bslmf_nestedtraitdeclaration.h>

#include <bslmt_readerwritermutex.h>
#include <bslmt_readlockguard.h>
#include <bslmt_writelockguard.h>

#include <bsls_assert.h>
#include <bsls_atomic.h>
#include <bsls_libraryfeatures.h>
#include <bsls_objectbuffer.h>
#include <bsls_platform.h>   // BSLS_PLATFORM_CPU_X86_64

#include <bsl_algorithm.h>
#include <bsl_cstddef.h>     // 'NULL'
#include <bsl_functional.h>
#include <bsl_list.h>
#include <bsl_vector.h>
#include <bsl_iostream.h>
#include <bsl_sstream.h>

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
# include <memory_resource>
#endif

#include <vector>

namespace BloombergLP {
namespace bdlcc {

template <class KEY,
          class VALUE,
          class HASH  = bsl::hash<KEY>,
          class EQUAL = bsl::equal_to<KEY> >
class StripedUnorderedContainerImpl;

                 // ========================================
                 // class StripedUnorderedContainerImpl_Node
                 // ========================================

template <class KEY, class VALUE>
class StripedUnorderedContainerImpl_Node {
    // This class template represents a node in the singly-linked list of
    // '(KEY, VALUE)' elements for each bucket of a hash map.

  private:
    // DATA
    mutable StripedUnorderedContainerImpl_Node *d_next_p;
        // Pointer to next element of the bucket

    bsls::ObjectBuffer<KEY>            d_key;
        // footprint of key

    bsls::ObjectBuffer<VALUE>          d_value;
        // Footprint of value

    bslma::Allocator                  *d_allocator_p;
        // memory allocator (held, not owned).

    // NOT IMPLEMENTED
    StripedUnorderedContainerImpl_Node(
                        const StripedUnorderedContainerImpl_Node<KEY, VALUE>&);
                                                                    // = delete
    StripedUnorderedContainerImpl_Node<KEY, VALUE>&
              operator=(const StripedUnorderedContainerImpl_Node<KEY, VALUE>&);
                                                                    // = delete

  public:
    // CREATORS
    StripedUnorderedContainerImpl_Node(
                       const KEY&                          key,
                       const VALUE&                        value,
                       StripedUnorderedContainerImpl_Node *nextPtr,
                       bslma::Allocator                   *basicAllocator = 0);
    StripedUnorderedContainerImpl_Node(
                       const KEY&                          key,
                       bslmf::MovableRef<VALUE>            value,
                       StripedUnorderedContainerImpl_Node *nextPtr,
                       bslma::Allocator                   *basicAllocator = 0);
        // Create a 'bdlcc::StripedUnorderedContainerImpl_Node' object having
        // the specified 'key' and 'value', and with the specified 'nextPtr'
        // pointer to the next node.  Optionally specify a 'basicAllocator'
        // used to supply memory.  If 'basicAllocator' is 0, the currently
        // installed default allocator is used.

    StripedUnorderedContainerImpl_Node(
                       const KEY&                          key,
                       StripedUnorderedContainerImpl_Node *nextPtr,
                       bslma::Allocator                   *basicAllocator = 0);
        // Create a 'bdlcc::StripedUnorderedContainerImpl_Node' object having
        // the specified 'key' and a value initialized to 'VALUE()', and with
        // the specified 'nextPtr' pointer to the next node.  Optionally
        // specify a 'basicAllocator' used to supply memory.  If
        // 'basicAllocator' is 0, the currently installed default allocator is
        // used.

    ~StripedUnorderedContainerImpl_Node();
        // Destroy this object.

    // MANIPULATORS
    StripedUnorderedContainerImpl_Node **nextAddress();
        // Return the address of the pointer to the next node.

    void setNext(StripedUnorderedContainerImpl_Node *nextPtr);
        // Set this node's pointer-to-next-node to the specified 'nextPtr'.

    VALUE& value();
        // Return a reference providing modifiable access to the 'value'
        // attribute of this object.

    // ACCESSORS
    const KEY& key() const;
        // Return a 'const' reference to the 'key' attribute of this object.

    StripedUnorderedContainerImpl_Node *next() const;
        // Return the pointer to the next node.

    const VALUE& value() const;
        // Return a 'const' reference to the 'value' attribute of this object.

                               // Aspects

    bslma::Allocator *allocator() const;
        // Return the allocator used by 'StripedUnorderedContainerImpl_Node' to
        // allocate memory.
};

                // ==========================================
                // class StripedUnorderedContainerImpl_Bucket
                // ==========================================

template <class KEY, class VALUE>
class StripedUnorderedContainerImpl_Bucket {
    // This class represents a bucket of the hash map.  This class template
    // represents the head of in the singly-linked list of '(KEY, VALUE)'
    // elements in a hash map.

  private:
    // PRIVATE TYPES
    typedef BloombergLP::bslmf::MovableRefUtil MoveUtil;
        // This 'typedef' is a convenient alias for the utility associated with
        // movable references.

    // DATA
    StripedUnorderedContainerImpl_Node<KEY, VALUE> *d_head_p;
        // Pointer to the first element in the bucket

    StripedUnorderedContainerImpl_Node<KEY, VALUE> *d_tail_p;
        // Pointer to the last element in the bucket

    bsl::size_t                            d_size;
        // Number of nodes in this bucket

    bslma::Allocator                      *d_allocator_p;
        // memory allocator (held, not owned).

    // NOT IMPLEMENTED
    StripedUnorderedContainerImpl_Bucket(
          const StripedUnorderedContainerImpl_Bucket<KEY, VALUE>&); // = delete
    StripedUnorderedContainerImpl_Bucket<KEY, VALUE>& operator=(
          const StripedUnorderedContainerImpl_Bucket<KEY, VALUE>&); // = delete

  public:
    // TYPES
    enum BucketScope {
        // Enumeration to differentiate between processing all elements with
        // the same key, or just the first one (and typically the only one).

        e_BUCKETSCOPE_FIRST = 0,  // Act on first matching element found.
        e_BUCKETSCOPE_ALL         // Act on all matching elements.
    };

    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(StripedUnorderedContainerImpl_Bucket,
                                   bslma::UsesBslmaAllocator);

    // CREATORS
    explicit StripedUnorderedContainerImpl_Bucket(
                                         bslma::Allocator *basicAllocator = 0);
        // Create an empty 'bdlcc::StripedUnorderedContainerImpl_Bucket'
        // object.  Optionally specify a 'basicAllocator' used to supply
        // memory.  If 'basicAllocator' is 0, the currently installed default
        // allocator is used.

    StripedUnorderedContainerImpl_Bucket(
           bslmf::MovableRef<StripedUnorderedContainerImpl_Bucket<KEY, VALUE> >
                                                                      original,
           bslma::Allocator                                          *);
        // Create a 'bdlcc::StripedUnorderedContainerImpl_Bucket' object having
        // the same value as the specified 'original' object.  The 'original'
        // object is left in a valid but unspecified state.

    ~StripedUnorderedContainerImpl_Bucket();
        // Destroy this object.

    // MANIPULATORS
    void addNode(StripedUnorderedContainerImpl_Node<KEY, VALUE> *nodePtr);
        // Add the specified 'nodePtr' node at the end of this bucket.

    void clear();
        // Empty 'StripedUnorderedContainerImpl_Bucket' and delete all nodes.

    StripedUnorderedContainerImpl_Node<KEY, VALUE> **headAddress();
        // Return the address of the head (node) of this bucket list.

    void incrementSize(int amount);
        // Increment the 'size' attribute of this bucket by the specified
        // 'amount'.

    void setHead(StripedUnorderedContainerImpl_Node<KEY, VALUE> *value);
        // Set the address of the head of this bucket list to the specified
        // 'value'.

    void setSize(bsl::size_t value);
        // Set the 'size' attribute of this bucket to the specified 'value'.

    void setTail(StripedUnorderedContainerImpl_Node<KEY, VALUE> *value);
        // Set the pointer of the tail of this bucket list to the specified
        // 'value'.

    template <class EQUAL>
    bsl::size_t setValue(const KEY&   key,
                         const EQUAL& equal,
                         const VALUE& value,
                         BucketScope  scope);
        // Set the value attribute of the element in this bucket having the
        // specified 'key' to the specified 'value', using the specified
        // 'equal' to compare keys.  If no such element exists, insert
        // '(key, value)'.  The behavior with respect to duplicate key values
        // in the bucket depends on the specified 'scope':
        //
        //: 'e_BUCKETSCOPE_ALL':
        //:   Set 'value' to every element in the bucket having 'key'.
        //:
        //: 'e_BUCKETSCOPE_FIRST':
        //:   Set 'value' to the first element found having 'key'.
        //
        // Return the number of elements found having 'key' that had their
        // value set.  Note that, when there are multiple elements having
        // 'key', the selection of "first" is unspecified and subject to
        // change.  Also note that specifying 'e_BUCKETSCOPE_FIRST' is more
        // performant when there is a single element in the bucket having
        // 'key'.

    template <class EQUAL>
    bsl::size_t setValue(const KEY&               key,
                         const EQUAL&             equal,
                         bslmf::MovableRef<VALUE> value);
        // Set the value attribute of the element in this bucket having the
        // specified 'key' to the specified 'value', using the specified
        // 'equal' to compare keys.  If no such element exists, insert
        // '(key, value)'.  If there are multiple elements in this hash map
        // having 'key' then set the value of the first such element found.
        // Return the number of elements found having 'key' that had their
        // value set.  Note that, when there are multiple elements having
        // 'key', the selection of "first" is unspecified and subject to
        // change.

    // ACCESSORS
    bool empty() const;
        // Return 'true' if this bucket contains no elements, and 'false'
        // otherwise.

    StripedUnorderedContainerImpl_Node<KEY, VALUE> *head() const;
        // Return the head (node) of this bucket list.

    bsl::size_t size() const;
        // Return the current number of elements in this bucket.

    StripedUnorderedContainerImpl_Node<KEY, VALUE> *tail() const;
        // Return address of the tail (node) of this bucket list.

                               // Aspects

    bslma::Allocator *allocator() const;
        // Return the allocator used by 'StripedUnorderedContainerImpl_Bucket'
        // to allocate memory.
};

template <class KEY,
          class VALUE,
          class HASH  = bsl::hash<KEY>,
          class EQUAL = bsl::equal_to<KEY> >
class StripedUnorderedContainerImpl_TestUtil;

class StripedUnorderedContainerImpl_LockElement;
class StripedUnorderedContainerImpl_LockElementReadGuard;
class StripedUnorderedContainerImpl_LockElementWriteGuard;

                     // ===================================
                     // class StripedUnorderedContainerImpl
                     // ===================================

template <class KEY, class VALUE, class HASH, class EQUAL>
class StripedUnorderedContainerImpl {
    // This class implements the logic for a striped hash multimap with logic
    // that supports a (unique) map as a special case.

  public:
    // TYPES
    enum {
        k_DEFAULT_NUM_BUCKETS  = 16, // Default # of buckets
        k_DEFAULT_NUM_STRIPES  =  4  // Default # of stripes
    };

    typedef StripedUnorderedContainerImpl_Node<KEY, VALUE> Node;
        // Node in a bucket.

    typedef bsl::pair<KEY, VALUE> KVType;
        // Value type of a bulk insert entry.

    typedef bsl::function<bool (VALUE *, const KEY&)> VisitorFunction;
        // An alias to a function meeting the following contract:
        //..
        //  bool visitorFunction(VALUE *value, const KEY& key);
        //      // Visit the specified 'value' attribute associated with the
        //      // specified 'key'.  Return 'true' if this function may be
        //      // called on additional elements, and 'false' otherwise (i.e.,
        //      // if no other elements should be visited).  Note that this
        //      // functor can change the value associated with 'key'.
        //..

    typedef bsl::function<bool (const VALUE&, const KEY&)>
                                                       ReadOnlyVisitorFunction;
        // An alias to a function meeting the following contract:
        //..
        //  bool visitorFunction(const VALUE& value, const KEY& key);
        //      // Visit the specified 'value' attribute associated with the
        //      // specified 'key'.  Return 'true' if this function may be
        //      // called on additional elements, and 'false' otherwise (i.e.,
        //      // if no other elements should be visited).  Note that this
        //      // functor can *not* change the values associated with 'key'
        //      // and 'value'.
        //..

  private:
    // PRIVATE CONSTANTS
    static const int k_REHASH_IN_PROGRESS = 1; // d_state bit 0
    static const int k_REHASH_ENABLED     = 2; // d_state bit 1

    // PRIVATE TYPES
    enum {
    #if BSLS_PLATFORM_CPU_X86 || BSLS_PLATFORM_CPU_X86_64
        k_PREFETCH_ENABLED = 1,
    #else
        k_PREFETCH_ENABLED = 0,
    #endif
        // Can be 0 or 1; if prefetch, we use 2 cachelines at a time
        k_EFFECTIVE_CACHELINE_SIZE = (1 + k_PREFETCH_ENABLED) *
                                            bslmt::Platform::e_CACHE_LINE_SIZE,
        // Cacheline size to use; may be 1 or 2 cachelines
        k_INT_PADDING = k_EFFECTIVE_CACHELINE_SIZE - sizeof(bsls::AtomicInt)
    };

    enum Multiplicity {
        // Enumeration to differentiate between inserting only unique keys and
        // inserting multiple values for the same key.

        e_INSERT_UNIQUE = 0, // Insert a new element having 'key' if no element
                             // in hash map has 'key'; otherwise, update the
                             // value attribute of the existing element.

        e_INSERT_ALWAYS      // Insert a new element having 'key' even if the
                             // map already has an element(s) having the 'key'
                             // attribute.
    };

    enum Scope {
        // Enumeration to differentiate between processing all elements with
        // the same key, or just the first one (and typically the only one).

        e_SCOPE_FIRST = 0,  // Act on first matching element found.
        e_SCOPE_ALL         // Act on all matching elements.
    };

    typedef StripedUnorderedContainerImpl_LockElement           LockElement;
    typedef StripedUnorderedContainerImpl_LockElementReadGuard  LERGuard;
    typedef StripedUnorderedContainerImpl_LockElementWriteGuard LEWGuard;

    // DATA
    bsl::size_t                       d_numStripes;
        // number of stripes

    bsl::size_t                       d_numBuckets;
        // number of buckets

    bsl::size_t                       d_hashMask;
        // d_numStripes - 1; this value is used to provide an efficient modulo
        // (using bit-wise '&') of d_numStripes (where d_numStripes must be a
        // power of 2).

    float                             d_maxLoadFactor;
        // maxLoadFactor (defaults to 1.0)

    HASH                              d_hasher;
        // hashing function for keys

    EQUAL                             d_comparator;
        // comparison function for keys

    mutable bsls::AtomicInt           d_state;
        //: o bit 0: 0-rehash not in progress; 1-rehash in progress.
        //: o bit 1: 0-rehash disabled;        1-rehash enabled.

    const char                        d_statePad[k_INT_PADDING];
        // padding, so that 'd_state' will have its own cache line

    bsls::AtomicInt                   d_numElements;
        // # of elements in the hash map

    const char                        d_numElementsPad[k_INT_PADDING];
        // padding, so that 'd_numElements' will have its own cache line

    bsl::vector<StripedUnorderedContainerImpl_Bucket<KEY,VALUE> >
                                      d_buckets;
        // hash table data, storing key-value pairs

    LockElement                      *d_locks_p;
        // Pointer to an array of locks for the stripes.  Note that mutex can't
        // be moved or copied, hence can't be in a vector.

    bslma::Allocator                 *d_allocator_p;
        // memory allocator (held, not owned)

    // FRIENDS
    friend class
               StripedUnorderedContainerImpl_TestUtil<KEY, VALUE, HASH, EQUAL>;
    friend class StripedUnorderedContainerImpl_LockElement;

    // NOT IMPLEMENTED
    StripedUnorderedContainerImpl(
                const StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>&);
                                                                    // = delete
    StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>&
      operator=(const StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>&);
                                                                    // = delete

    // PRIVATE CLASS METHODS
    static bsl::size_t adjustBuckets(bsl::size_t numBuckets,
                                     bsl::size_t numStripes);
        // Return the number of buckets needed by this implementation for the
        // specified 'numBuckets' and 'numStripes'.  That value is the lowest
        // integer that is a power of 2 that is greater than or equal
        // 'numBuckets', 'numStripes', and 2.

    static bsl::size_t powerCeil(bsl::size_t num);
        // Return the nearest higher power of 2 for the specified 'num'.

    // PRIVATE MANIPULATORS
    void checkRehash();
        // Perform a rehash if the 'loadFactor() > maxLoadFactor()', and
        // 'true == canRehash()'.

    bsl::size_t erase(const KEY& key, Scope scope);
        // Remove from this hash map the element, if any, having the specified
        // 'key'.  If there a multiple elements having 'key' and the specified
        // 'scope' is 'e_SCOPE_ALL', erase them all; otherwise; erase just the
        // first element found.  Return the number of elements erased.  Note
        // that, when there are multiple elements having 'key', the selection
        // of "first" is unspecified and subject to change.

    template <class RANDOM_ITER>
    bsl::size_t eraseBulk(RANDOM_ITER first,
                          RANDOM_ITER last,
                          Scope       scope);
        // Erase from this hash map elements in this hash map having any of the
        // values in the keys contained between the specified 'first'
        // (inclusive) and 'last' (exclusive) random-access iterators.  The
        // iterators provide read access to a sequence of 'KEY' objects.  If
        // there are multiple elements for any key value and the specified
        // 'scope' is 'e_SCOPE_ALL' then erase them all; otherwise, erase just
        // the first such element found.  Return the number of elements erased.
        // The behavior is undefined unless 'first <= last'.  Note that, when
        // there are multiple elements having 'key', the selection of "first"
        // is unspecified and subject to change.

    bsl::size_t insert(const KEY&    key,
                       const VALUE&  value,
                       Multiplicity  multiplicity);
    bsl::size_t insert(const KEY&               key,
                       bslmf::MovableRef<VALUE> value,
                       Multiplicity             multiplicity);
        // Insert into this hash map an element having the specified 'key' and
        // 'value'.  The behavior with respect to duplicate key values in the
        // hash map depends on the specified 'multiplicity':
        //
        //: 'e_INSERT_ALWAYS':
        //:   The insertion occurs irrespective of other elements in the hash
        //:   map having the same 'key' value.
        //:
        //: 'e_INSERT_UNIQUE':
        //:   Insert a new element if no element in the hash map has the 'key'
        //:   value; otherwise, update the value attribute of the first element
        //:   found having 'key' to 'value'.
        //
        // Return the number of elements inserted.  Note that, when there are
        // multiple elements having 'key', the selection of "first" is
        // unspecified and subject to change.

    template <class RANDOM_ITER>
    bsl::size_t insertBulk(RANDOM_ITER  first,
                           RANDOM_ITER  last,
                           Multiplicity multiplicity);
        // Insert into this hash map elements having the key-value pairs
        // obtained between the specified 'first' (inclusive) and 'last'
        // (exclusive) random-access iterators.  The iterators provide read
        // access to a sequence of 'bsl::pair<KEY, VALUE>' objects.  The
        // behavior with respect to duplicate key values in the hash map
        // depends on the specified 'multiplicity':
        //
        //: 'e_INSERT_ALWAYS':
        //:   The insertion occurs irrespective of other elements in the hash
        //:   map having the same 'key' value.  Note that this is the only
        //:   way to adding elements with non-unique keys to the hash map.
        //:
        //: 'e_INSERT_UNIQUE':
        //:   Insert a new element if no element in the hash map has the 'key'
        //:   value; otherwise, update the value attribute of the first element
        //:   found having 'key' to 'value'.
        //
        // Return the number of elements inserted.  The behavior is undefined
        // unless 'first <= last'.  Note that, when there are multiple elements
        // having 'key', the selection of "first" is unspecified and subject to
        // change.

    int setComputedValue(const KEY&             key,
                         const VisitorFunction& visitor,
                         Scope                  scope);
        // Invoke the specified 'visitor' passing the specified 'key', and the
        // address of the attribute part of an element (of possibly many
        // elements) found in this hash map having 'key'.  That is, for
        // '(key, value)', invoke:
        //..
        //  bool visitor(&value, key);
        //..
        // If no element in the hash map has 'key', insert '(key, VALUE())' and
        // invoke to 'visitor' with 'value' pointing to the defsult constructed
        // value.  If there are multiple elements having 'key' and the
        // specified 'scope' is 'e_SCOPE_ALL' then apply 'visitor' to each of
        // them; otherwise, 'visitor' is applied to just the first such element
        // found.  Return the number of elements visited or the negation of
        // that value if visitations stopped because 'visitor' returned
        // 'false'.  'visitor' has exclusive access (i.e., write access) to the
        // element.  The behavior is undefined if hash map manipulators and
        // 'getValue*' methods are invoked from within 'visitor', as it may
        // lead to a deadlock.  Note that, when there are multiple elements
        // having 'key', the selection of "first" and the order applying
        // 'visitor' are unspecified and subject to change.  Also note that a
        // return value of '0' implies that an element was inserted.

    bsl::size_t setValue(const KEY&   key,
                         const VALUE& value,
                         Scope        scope);
        // Set the value attribute of the element in this hash map having the
        // specified 'key' to the specified 'value'.  If no such element
        // exists, insert '(key, value)'.  The behavior with respect to
        // duplicate key values in the bucket depends on the specified
        // 'scope':
        //
        //: 'e_SCOPE_ALL':
        //:   Set 'value' to every element in the bucket having 'key'.
        //:
        //: 'e_SCOPE_FIRST':
        //:   Set 'value' to the first element found having 'key'.
        //
        // Return the number of elements found having 'key'.  Note that if no
        // elements were found, and a new value was inserted, '0' is returned.
        // Also note that, when there are multiple elements having 'key', the
        // selection of "first" is unspecified and subject to change.  Also
        // note that specifying 'e_SCOPE_FIRST' is more performant when there
        // is a single element in the bucket having 'key'.

    // PRIVATE ACCESSORS
    bsl::size_t bucketIndex(const KEY& key, bsl::size_t numBuckets) const;
        // Return the index of the bucket, in the array of buckets maintained
        // by this hash map, where values having a key equivalent to the
        // specified 'key' would be inserted using the specified 'numBuckets'.
        // This operation does not lock the related stripe or check the rehash
        // state.  Note that 'numBuckets' does not have to be the current
        // number of buckets in this hash map.

    bsl::size_t bucketToStripe(bsl::size_t bucketIndex) const;
        // Return the stripe index associated with the specified 'bucketIndex'.

    template <class VECTOR>
    bsl::size_t getValueImpl(VECTOR *valuesPtr, const KEY& key) const;
        // Load, into the specified '*valuesPtr', the value attributes of every
        // element in this hash map having the specified 'key'.  Return the
        // number of elements found with 'key'.  Note that the order of the
        // values returned is not specified.

    LockElement *lockRead(bsl::size_t *bucketIdx, const KEY& key) const;
        // Lock for read the stripe related to the specified 'key', setting the
        // specified 'bucketIdx' to the bucket index associated with 'key'.
        // Return the address to the lock-element associated with the returned
        // 'bucketIdx'.

    LockElement *lockWrite(bsl::size_t *bucketIdx, const KEY& key) const;
        // Lock for write the stripe related to the specified 'key', setting
        // the specified 'bucketIdx' to the bucket index associated with 'key'.
        // Return the address to the lock-element associated with the returned
        // 'bucketIdx'.

  public:
    // CREATORS
    explicit StripedUnorderedContainerImpl(
                   bsl::size_t       numInitialBuckets = k_DEFAULT_NUM_BUCKETS,
                   bsl::size_t       numStripes        = k_DEFAULT_NUM_STRIPES,
                   bslma::Allocator *basicAllocator = 0);
        // Create an empty 'StripedUnorderedContainerImpl' object, a fully
        // thread-safe hash map where access is divided into "stripes" (a group
        // of buckets protected by a reader-write mutex).  Optionally specify
        // 'numInitialBuckets' and 'numStripes' which define the minimum number
        // of buckets and the (fixed) number of stripes in this map.
        // Optionally specify a 'basicAllocator' used to supply memory.  If
        // 'basicAllocator' is 0, the currently installed default allocator is
        // used.  The hash map has rehash enabled.

    ~StripedUnorderedContainerImpl();
        // Destroy this hash map.  This method is *not* thread-safe.

    // MANIPULATORS
    void clear();
        // Remove all elements from this striped hash map.  If rehash is in
        // progress, block until it completes.

    void disableRehash();
        // Prevent rehash until the 'enableRehash' method is called.

    void enableRehash();
        // Allow rehash.  If conditions warrant, rehash will be started by the
        // *next* method call that observes the load factor is exceeded (see
        // {Concurrent Rehash}).  Note that calling
        // 'maxLoadFactor(maxLoadFactor())' (i.e., setting the maximum load
        // factor to its current value) will trigger a rehash if needed but
        // otherwise does not change the hash map.

    bsl::size_t eraseAll(const KEY& key);
        // Erase from this hash map the elements having the specified 'key'.
        // Return the number of elements erased.

    template <class RANDOM_ITER>
    bsl::size_t eraseBulkAll(RANDOM_ITER first, RANDOM_ITER last);
        // Erase from this hash map elements in this hash map having any of the
        // values in the keys contained between the specified 'first'
        // (inclusive) and 'last' (exclusive) random-access iterators.  The
        // iterators provide read access to a sequence of 'KEY' objects.  All
        // erasures are done by the calling thread and the order of erasure is
        // not specified.  Return the number of elements removed.  The behavior
        // is undefined unless 'first <= last'.  Note that the map may not have
        // an element for every value in 'keys'.

    template <class RANDOM_ITER>
    bsl::size_t eraseBulkFirst(RANDOM_ITER first, RANDOM_ITER last);
        // Erase from this hash map elements in this hash map having any of the
        // values in the keys contained between the specified 'first'
        // (inclusive) and 'last' (exclusive) random-access iterators.  The
        // iterators provide read access to a sequence of 'KEY' objects.  If
        // there are multiple elements for any key value, erase just the first
        // such element found.  All erasures are done by the calling thread and
        // the order of erasure is not specified.  Return the number of
        // elements removed.  The behavior is undefined unless 'first <= last'.
        // Note that the map may not have an element for every value in 'keys'.

    bsl::size_t eraseFirst(const KEY& key);
        // Erase from this hash map the *first* element (of possibly many)
        // found to the specified 'key'.  Return the number of elements erased.
        // Note that method is more performant than 'eraseAll' when there is
        // one element having 'key'.

    void insertAlways(const KEY& key, const VALUE& value);
        // Insert into this hash map an element having the specified 'key' and
        // 'value'.  Note that other elements having the same 'key' may exist
        // in this hash map.

    void insertAlways(const KEY& key, bslmf::MovableRef<VALUE> value);
        // Insert into this hash map an element having the specified 'key' and
        // the specified move-insertable 'value'.  The 'value' object is left
        // in a valid but unspecified state.  If 'value' is allocator-enabled
        // and 'allocator() != value.allocator()' this operation may cost as
        // much as a copy.  Note that other elements having the same 'key' may
        // exist in this hash map.

    template <class RANDOM_ITER>
    void insertBulkAlways(RANDOM_ITER first, RANDOM_ITER last);
        // Insert into this hash map elements having the key-value pairs
        // obtained between the specified 'first' (inclusive) and 'last'
        // (exclusive) random-access iterators.  The iterators provide read
        // access to a sequence of 'bsl::pair<KEY, VALUE>' objects.  All
        // insertions are done by the calling thread and the order of insertion
        // is not specified.  The behavior is undefined unless 'first <= last'.

    template <class RANDOM_ITER>
    bsl::size_t insertBulkUnique(RANDOM_ITER first, RANDOM_ITER last);
        // Insert into this hash map elements having the key-value pairs
        // obtained between the specified 'first' (inclusive) and 'last'
        // (exclusive) random-access iterators.  The iterators provide read
        // access to a sequence of 'bsl::pair<KEY, VALUE>' objects.  If an
        // element having one of the keys already exists in this hash map, set
        // the value attribute to the corresponding value from 'data'.  All
        // insertions are done by the calling thread and the order of insertion
        // is not specified.  Return the number of elements inserted.  The
        // behavior is undefined unless 'first <= last'.

    bsl::size_t insertUnique(const KEY& key, const VALUE& value);
        // Insert into this hash map an element having the specified 'key' and
        // 'value'.  If 'key' already exists in this hash map, the value
        // attribute of that element is set to 'value'.  Return 1 if an element
        // is inserted, and 0 if an existing element is updated.  Note that the
        // return value equals the number of elements inserted.

    bsl::size_t insertUnique(const KEY& key, bslmf::MovableRef<VALUE> value);
        // Insert into this hash map an element having the specified 'key' and
        // the specified move-insertable 'value'.  If 'key' already exists in
        // this hash map, the value attribute of that element is set to
        // 'value'.  Return 1 if an element is inserted, and 0 if an existing
        // element is updated.  The 'value' object is left in a valid but
        // unspecified state.  If 'value' is allocator-enabled and
        // 'allocator() != value.allocator()' this operation may cost as much
        // as a copy.  Note that the return value equals the number of elements
        // inserted.

    void maxLoadFactor(float newMaxLoadFactor);
        // Set the maximum load factor of this hash map to the specified
        // 'newMaxLoadFactor'.  If 'newMaxLoadFactor < loadFactor()', this
        // operation will cause an immediate rehash; otherwise, this operation
        // has a constant-time cost.  The rehash will increase the number of
        // buckets by a power of 2.  The behavior is undefined unless
        // '0 < newMaxLoadFactor'.

    void rehash(bsl::size_t numBuckets);
        // Recreate this hash map to one having at least the specified
        // 'numBuckets'.  This operation is a no-op if *any* of the following
        // are true: 1) rehash is disabled; 2) 'numBuckets' less or equals the
        // current number of buckets.  See {Rehash}.

    int setComputedValueAll(const KEY&             key,
                            const VisitorFunction& visitor);
        // Serially invoke the specified 'visitor' passing the specified 'key',
        // and the address of the value of each element in this hash map having
        // 'key'.  If 'key' is not in the map, 'value' will be default
        // constructed.  That is, for each '(key, value)' found, invoke:
        //..
        //  bool visitor(VALUE *value, const Key& key);
        //..
        // If no element in the map has 'key', insert '(key, VALUE())' and
        // invoke 'visitor' with 'value' pointing to the defsult constructed
        // value.  Return the number of elements visited or the negation of
        // that value if visitations stopped because 'visitor' returned
        // 'false'.  'visitor', when invoked, has exclusive access (i.e., write
        // access) to each element during each invocation.  The behavior is
        // undefined if hash map manipulators and 'getValue*' methods are
        // invoked from within 'visitor', as it may lead to a deadlock.  Note
        // that the 'setComputedValueFirst' method is more performant than the
        // when the hash map contains a single element for 'key'.  Also note
        // that a return value of '0' implies that an element was inserted.

    int setComputedValueFirst(const KEY&             key,
                              const VisitorFunction& visitor);
        // Invoke the specified 'visitor' passing the specified 'key', and the
        // address of the value attribute of the *first* element (of possibly
        // many elements) found in this hash map having 'key'.  If 'key' is not
        // in the map, 'value' will be default constructed.  That is, for
        // '(key, value)', invoke:
        //..
        //  bool visitor(VALUE *value, const Key& key);
        //..
        // If no element in the map has 'key', insert '(key, VALUE())' and
        // invoke 'visitor' with 'value' pointing to the defsult constructed
        // value.  Return 1 if 'key' was found and 'visitor' returned 'true', 0
        // if 'key' was not found, and -1 if 'key' was found and 'visitor'
        // returned 'false'.  'visitor', when invoked, has exclusive access
        // (i.e., write access) to the element.  The behavior is undefined if
        // hash map manipulators and 'getValue*' methods are invoked from
        // within 'visitor', as it may lead to a deadlock.  Note that the
        // return value equals the number of elements inserted.  Also note
        // that, when there are multiple elements having 'key', the selection
        // of "first" is implementation specific and subject to change.  Also
        // note that this method is more performant than the
        // 'setComputedValueAll' method when the hash map contains a single
        // element for 'key'.  Also note that a return value of '0' implies
        // that an element was inserted.

    bsl::size_t setValueAll(const KEY& key, const VALUE& value);
        // Set the value attribute of every element in this hash map having the
        // specified 'key' to the specified 'value'.  If no such such element
        // exists, insert '(key, value)'.  Return the number of elements found
        // with 'key'.  Note that if no elements were found, and a new value
        // was inserted, '0' is returned.

    bsl::size_t setValueFirst(const KEY& key, const VALUE& value);
        // Set the value attribute of the *first* element in this hash map (of
        // possibly many) found to have the specified 'key' to the specified
        // 'value'.  If no such such element exists, insert '(key, value)'.
        // Return the number of elements found with 'key'.  Note that if no
        // elements were found, and a new value was inserted, '0' is returned.
        // Also note that this method is more performant than 'setValueAll'
        // when there is one element having 'key' in the hash map.

    bsl::size_t setValueFirst(const KEY& key, bslmf::MovableRef<VALUE> value);
        // Set the value attribute of the element in this hash map having the
        // specified 'key' to the specified 'value'.  If no such element
        // exists, insert '(key, value)'.  If there are multiple elements in
        // this hash map having 'key' then set the value of the first such
        // element found.  Return the number of elements found having 'key'.
        // Note that if no elements were found, and a new value was inserted,
        // '0' is returned.  Also note that, when there are multiple elements
        // having 'key', the selection of "first" is unspecified and subject to
        // change.

    int update(const KEY& key, const VisitorFunction& visitor);
        // !DEPRECATED!: Use 'visit(key, visitor)' instead.
        //
        // Serially call the specified 'visitor' on each element (if one
        // exists) in this hash map having the specified 'key' until every such
        // element has been updated or 'visitor' returns 'false'.  That is, for
        // '(key, value)', invoke:
        //..
        //  bool visitor(&value, key);
        //..
        // Return the number of elements visited or the negation of that value
        // if visitations stopped because 'visitor' returned 'false'.
        // 'visitor' has exclusive access (i.e., write access) to each element
        // for duration of each invocation.  The behavior is undefined if hash
        // map manipulators and 'getValue*' methods are invoked from within
        // 'visitor', as it may lead to a deadlock.

    int visit(const VisitorFunction& visitor);
        // Call the specified 'visitor' (in an unspecified order) on the
        // elements in this hash table until each element has been visited or
        // 'visitor' returns 'false'.  That is, for '(key, value)', invoke:
        //..
        //  bool visitor(&value, key);
        //..
        // Return the number of elements visited or the negation of that value
        // if visitations stopped because 'visitor' returned 'false'.
        // 'visitor' has exclusive access (i.e., write access) to each element
        // for duration of each invocation.  Every element present in this hash
        // map at the time 'visit' is invoked will be visited unless it is
        // removed before 'visitor' is called for that element.  Each
        // visitation is done by the calling thread and the order of visitation
        // is not specified.  Elements inserted during the execution of 'visit'
        // may or may not be visited.  The behavior is undefined if hash map
        // manipulators and 'getValue*' methods are invoked from within
        // 'visitor', as it may lead to a deadlock.  Note that 'visitor' can
        // change the value of the visited elements.

    int visit(const KEY& key, const VisitorFunction& visitor);
        // Serially call the specified 'visitor' on each element (if one
        // exists) in this hash map having the specified 'key' until every such
        // element has been updated or 'visitor' returns 'false'.  That is, for
        // '(key, value)', invoke:
        //..
        //  bool visitor(&value, key);
        //..
        // Return the number of elements visited or the negation of that value
        // if visitations stopped because 'visitor' returned 'false'.
        // 'visitor' has exclusive access (i.e., write access) to each element
        // for duration of each invocation.  The behavior is undefined if hash
        // map manipulators and 'getValue*' methods are invoked from within
        // 'visitor', as it may lead to a deadlock.

    // ACCESSORS
    bsl::size_t bucketIndex(const KEY& key) const;
        // Return the index of the bucket, in the array of buckets maintained
        // by this hash map, where elements having the specified 'key' are
        // inserted.  Note that unless rehash is disabled, the value returned
        // may be obsolete at the time it is returned.

    bsl::size_t bucketCount() const;
        // Return the number of buckets in the array of buckets maintained by
        // this hash map.  Note that unless rehash is disabled, the value
        // returned may be obsolete by the time it is received.

    bsl::size_t bucketSize(bsl::size_t index) const;
        // Return the number of elements contained in the bucket at the
        // specified 'index' in the array of buckets maintained by this hash
        // map.  The behavior is undefined unless
        // '0 <= index < bucketCount()'.

    bool canRehash() const;
        // Return 'true' if rehash is enabled and rehash is not in progress,
        // and 'false' otherwise.

    bool empty() const;
        // Return 'true' if this hash map contains no elements, and 'false'
        // otherwise.

    EQUAL equalFunction() const;
        // Return (a copy of) the key-equality functor used by this hash map
        // that returns 'true' if two 'KEY' objects have the same value, and
        // 'false' otherwise.

    bsl::size_t getValue(VALUE *value, const KEY& key) const;
        // Load, into the specified '*value', the value attribute of the first
        // element (of possibly many elements) found in this hash map having
        // the specified 'key'.  Return 1 on success, and 0 if 'key' does not
        // exist in this hash.  Note that the return value equals the number of
        // values returned.  Also note that, when there are multiple elements
        // having 'key', the selection of "first" is implementation specific
        // and subject to change.

    bsl::size_t getValue(bsl::vector<VALUE> *valuesPtr, const KEY& key) const;
    bsl::size_t getValue(std::vector<VALUE> *valuesPtr, const KEY& key) const;
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
    bsl::size_t getValue(std::pmr::vector<VALUE> *valuesPtr, const KEY& key)
                                                                         const;
#endif
        // Load, into the specified '*valuesPtr', the value attributes of every
        // element in this hash map having the specified 'key'.  Return the
        // number of elements found with 'key'.  Note that the order of the
        // values returned is not specified.

    HASH hashFunction() const;
        // Return (a copy of) the unary hash functor used by this hash map to
        // generate a hash value (of type 'std::size_t') for a 'KEY' object.

    bool isRehashEnabled() const;
        // Return 'true' if rehash is enabled, or 'false' otherwise.

    float loadFactor() const;
        // Return the current quotient of the size of this hash map and the
        // number of buckets.  Note that the load factor is a measure of
        // container "fullness"; that is, a high load factor typically implies
        // many collisions (many elements landing in the same bucket) and that
        // decreases performance.

    float maxLoadFactor() const;
        // Return the maximum load factor allowed for this hash map.  If an
        // insert operation would cause the load factor to exceed the
        // 'maxLoadFactor()' and rehashing is enabled, then that insert
        // increases the number of buckets and rehashes the elements of the
        // container into that larger set of buckets.

    bsl::size_t numStripes() const;
        // Return the number of stripes in the hash.

    int visitReadOnly(const ReadOnlyVisitorFunction& visitor) const;
        // Call the specified 'visitor' (in an unspecified order) on the
        // elements in this hash table until each element has been visited or
        // 'visitor' returns 'false'.  That is, for '(key, value)', invoke:
        //..
        //  bool visitor(value, key);
        //..
        // Return the number of elements visited or the negation of that value
        // if visitations stopped because 'visitor' returned 'false'.
        // 'visitor' has read-only access to each element for duration of each
        // invocation.  Every element present in this hash map at the time
        // 'visit' is invoked will be visited unless it is removed before
        // 'visitor' is called for that element.  Each visitation is done by
        // the calling thread and the order of visitation is not specified.
        // The behavior is undefined if hash map manipulators are invoked from
        // within 'visitor', as it may lead to a deadlock.  Note that 'visitor'
        // can *not* change the value of the visited elements.

    int visitReadOnly(const KEY&                     key,
                      const ReadOnlyVisitorFunction& visitor) const;
        // Serially call the specified 'visitor' on each element (if one
        // exists) in this hash map having the specified 'key' until every such
        // element has been visitd or 'visitor' returns 'false'.  That is, for
        // '(key, value)', invoke:
        //..
        //  bool visitor(value, key);
        //..
        // Return the number of elements visited or the negation of that value
        // if visitations stopped because 'visitor' returned 'false'.
        // 'visitor' has read-only access to each element for duration of each
        // invocation.  The behavior is undefined if hash map manipulators are
        // invoked from within 'visitor', as it may lead to a deadlock.

    bsl::size_t size() const;
        // Return the current number of elements in this hash.

                                 // Aspects

    bslma::Allocator *allocator() const;
        // Return the allocator used by this hash map to supply memory.  Note
        // that if no allocator was supplied at construction the default
        // allocator installed at that time is used.
};

                // ============================================
                // class StripedUnorderedContainerImpl_TestUtil
                // ============================================

template <class KEY, class VALUE, class HASH, class EQUAL>
class StripedUnorderedContainerImpl_TestUtil {
    // This class implements a test utility that gives the test driver access
    // to the lock / unlock method of the Read/Write mutex.  Its purpose is to
    // allow testing that the locking actually happens as planned.

    // PRIVATE TYPES
    typedef StripedUnorderedContainerImpl_LockElement LockElement;

    // DATA
    StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>& d_hash;

  public:
    // CREATORS
    explicit StripedUnorderedContainerImpl_TestUtil(
                 StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>& hash);
        // Create a 'StripedUnorderedContainerImpl_TestUtil' object to test
        // locking in the specified 'hash'.

    //! ~StripedUnorderedContainerImpl_TestUtil() = default;
        // Destroy this object.

        // MANIPULATORS
    void lockRead(const KEY& key);
        // Call the 'lockRead' method of 'bdlcc::StripedUnorderedContainerImpl'
        // 'd_locks_p' lock of the specified 'key'.

    void lockWrite(const KEY& key);
        // Call the 'lockWrite' method of
        // 'bdlcc::StripedUnorderedContainerImpl' 'd_locks_p' lock of the
        // specified 'key'.

    void unlockWrite(const KEY& key);
        // Call the 'unlockWrite' method of
        // 'bdlcc::StripedUnorderedContainerImpl' 'd_locks_p' lock of the
        // specified 'key'.

    void unlockRead(const KEY& key);
        // Call the 'unlockRead' method of
        // 'bdlcc::StripedUnorderedContainerImpl' 'd_locks_p' lock of the
        // specified 'key'.
};

                // ===============================================
                // class StripedUnorderedContainerImpl_LockElement
                // ===============================================

class StripedUnorderedContainerImpl_LockElement {
    // A mutex + support info; padded to cacheline size, one per stripe

  private:
    // PRIVATE TYPES
    typedef bslmt::ReaderWriterMutex   LockType;

    // PRIVATE CONSTANTS
    enum {
    #if BSLS_PLATFORM_CPU_X86 || BSLS_PLATFORM_CPU_X86_64
        k_PREFETCH_ENABLED = 1,
    #else
        k_PREFETCH_ENABLED = 0,
    #endif
        // Can be 0 or 1; if prefetch, we use 2 cachelines at a time
        k_EFFECTIVE_CACHELINE_SIZE = (1 + k_PREFETCH_ENABLED) *
                                            bslmt::Platform::e_CACHE_LINE_SIZE,
        // Cacheline size to use; may be 1 or 2 cachelines
        k_LOCK_PADDING = k_EFFECTIVE_CACHELINE_SIZE >= sizeof(LockType) ?
                         k_EFFECTIVE_CACHELINE_SIZE -  sizeof(LockType) :
                     2 * k_EFFECTIVE_CACHELINE_SIZE -  sizeof(LockType)
    };

    // DATA
    LockType        d_lock;
    const char      d_pad[k_LOCK_PADDING];

  public:
    // CREATORS
    StripedUnorderedContainerImpl_LockElement();
        // Create an empty 'StripedUnorderedContainerImpl_LockElement' object.

    // MANIPULATORS
    void lockR();
        // Read lock the lock element.

    void lockW();
        // Write lock the lock element.

    void unlockR();
        // Read unlock the lock element.

    void unlockW();
        // Write unlock the lock element.
};


         // ========================================================
         // class StripedUnorderedContainerImpl_LockElementReadGuard
         // ========================================================

class StripedUnorderedContainerImpl_LockElementReadGuard {
    // A guard pattern on StripedUnorderedContainerImpl_LockElement, to release
    // on exception, for a lock element locked as read.

  private:
    // DATA
    StripedUnorderedContainerImpl_LockElement *d_lockElement_p;
        // Guarded LockElement pointer

  public:
    // CREATORS
    explicit StripedUnorderedContainerImpl_LockElementReadGuard(
                    StripedUnorderedContainerImpl_LockElement *lockElementPtr);
        // Create a guard object
        // 'StripedUnorderedContainerImpl_LockElementReadGuard' for the
        // specified 'lockElementPtr'
        // 'bdlcc::StripedUnorderedContainerImpl_LockElement' object.

    ~StripedUnorderedContainerImpl_LockElementReadGuard();
        // Release the guarded object

    // MANIPULATORS
    void release();
        // Release the guarded object
};

          // =========================================================
          // class StripedUnorderedContainerImpl_LockElementWriteGuard
          // =========================================================

class StripedUnorderedContainerImpl_LockElementWriteGuard {
    // A guard pattern on StripedUnorderedContainerImpl_LockElement, to release
    // on exception, for a lock element locked as write.

  private:
    // DATA
    StripedUnorderedContainerImpl_LockElement *d_lockElement_p;
        // Guarded LockElement pointer

  public:
    // CREATORS
    explicit StripedUnorderedContainerImpl_LockElementWriteGuard(
                    StripedUnorderedContainerImpl_LockElement *lockElementPtr);
        // Create a guard object
        // 'StripedUnorderedContainerImpl_LockElementWriteGuard' for the
        // specified 'lockElementPtr'
        // 'bdlcc::StripedUnorderedContainerImpl_LockElement' object.

    ~StripedUnorderedContainerImpl_LockElementWriteGuard();
        // Release the guarded object

    // MANIPULATORS
    void release();
        // Release the guarded object
};

struct StripedUnorderedContainerImpl_SortItem {
    // A vector element needed for efficient sorting for the 'insertBulk' and
    // 'eraseBulk' methods.
  public:
    // PUBLIC DATA
    int         d_stripeIdx;
    int         d_dataIdx;
    bsl::size_t d_hashVal;
};

// FREE OPERATOR
bool operator<(const StripedUnorderedContainerImpl_SortItem& lhs,
               const StripedUnorderedContainerImpl_SortItem& rhs);
    // Return 'true' if the specified 'lhs' is smaller than the specified 'rhs'
    // in the order of stripe, and data.

// ============================================================================
//                             INLINE DEFINITIONS
// ============================================================================

                // ----------------------------------------
                // class StripedUnorderedContainerImpl_Node
                // ----------------------------------------

// CREATORS
template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Node<KEY, VALUE>::
                                            StripedUnorderedContainerImpl_Node(
                            const KEY&                          key,
                            const VALUE&                        value,
                            StripedUnorderedContainerImpl_Node *nextPtr,
                            bslma::Allocator                   *basicAllocator)
: d_next_p(nextPtr)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
    bslma::ConstructionUtil::construct(d_key.address(),
                                       d_allocator_p,
                                       key);
    bslma::DestructorProctor<KEY> proctor(&d_key.object());

    bslma::ConstructionUtil::construct(d_value.address(),
                                       d_allocator_p,
                                       value);
    proctor.release();
}

template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Node<KEY, VALUE>::
                                            StripedUnorderedContainerImpl_Node(
                            const KEY&                          key,
                            bslmf::MovableRef<VALUE>            value,
                            StripedUnorderedContainerImpl_Node *nextPtr,
                            bslma::Allocator                   *basicAllocator)
: d_next_p(nextPtr)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
    bslma::ConstructionUtil::construct(d_key.address(),
                                       d_allocator_p,
                                       key);
    bslma::DestructorProctor<KEY> proctor(&d_key.object());

    VALUE& dummy = value;
    bslalg::ScalarPrimitives::moveConstruct(d_value.address(),
                                            dummy,
                                            d_allocator_p);
    proctor.release();
}

template <class KEY, class VALUE>
StripedUnorderedContainerImpl_Node<KEY, VALUE>::
    StripedUnorderedContainerImpl_Node(
        const KEY&                          key,
        StripedUnorderedContainerImpl_Node *nextPtr,
        bslma::Allocator                   *basicAllocator)
: d_next_p(nextPtr)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
    bslma::ConstructionUtil::construct(d_key.address(),
                                       d_allocator_p,
                                       key);
    bslma::DestructorProctor<KEY> proctor(&d_key.object());

    bslma::ConstructionUtil::construct(d_value.address(), d_allocator_p);
    proctor.release();
}


template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Node<KEY, VALUE>::
                                          ~StripedUnorderedContainerImpl_Node()
{
    // Destroy the object buffers content
    bslma::DestructionUtil::destroy(d_key.address());
    bslma::DestructionUtil::destroy(d_value.address());
}


// MANIPULATORS
template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Node<KEY, VALUE> **
                  StripedUnorderedContainerImpl_Node<KEY, VALUE>::nextAddress()
{
    return &d_next_p;
}

template <class KEY, class VALUE>
inline
void StripedUnorderedContainerImpl_Node<KEY, VALUE>::setNext(
                       StripedUnorderedContainerImpl_Node<KEY, VALUE> *nextPtr)
{
    d_next_p = nextPtr;
}

template <class KEY, class VALUE>
inline
VALUE& StripedUnorderedContainerImpl_Node<KEY, VALUE>::value()
{
    return d_value.object();
}

// ACCESSORS
template <class KEY, class VALUE>
inline
const KEY& StripedUnorderedContainerImpl_Node<KEY, VALUE>::key() const
{
    return d_key.object();
}

template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Node<KEY, VALUE> *
                   StripedUnorderedContainerImpl_Node<KEY, VALUE>::next() const
{
    return d_next_p;
}

template <class KEY, class VALUE>
inline
const VALUE& StripedUnorderedContainerImpl_Node<KEY, VALUE>::value() const
{
    return d_value.object();
}

                               // Aspects

template <class KEY, class VALUE>
inline
bslma::Allocator *
              StripedUnorderedContainerImpl_Node<KEY, VALUE>::allocator() const
{
    return d_allocator_p;
}

               // ------------------------------------------
               // class StripedUnorderedContainerImpl_Bucket
               // ------------------------------------------

// CREATORS
template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::
                                          StripedUnorderedContainerImpl_Bucket(
                                              bslma::Allocator *basicAllocator)
: d_head_p(NULL)
, d_tail_p(NULL)
, d_size(0)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
}

template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::
                                          StripedUnorderedContainerImpl_Bucket(
           bslmf::MovableRef<StripedUnorderedContainerImpl_Bucket<KEY, VALUE> >
                                                                      original,
           bslma::Allocator                                          *)
: d_head_p(MoveUtil::move(MoveUtil::access(original).d_head_p))
, d_tail_p(MoveUtil::move(MoveUtil::access(original).d_tail_p))
, d_size(  MoveUtil::access(original).d_size)
, d_allocator_p(MoveUtil::access(original).d_allocator_p)
{
    MoveUtil::access(original).d_head_p = NULL;
    MoveUtil::access(original).d_tail_p = NULL;
    MoveUtil::access(original).d_size   = 0;
}

template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::
                                        ~StripedUnorderedContainerImpl_Bucket()
{
    clear();
}

// MANIPULATORS
template <class KEY, class VALUE>
inline
void StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::addNode(
                       StripedUnorderedContainerImpl_Node<KEY, VALUE> *nodePtr)
{
    BSLS_ASSERT(nodePtr->next() == NULL);

    if (d_head_p == NULL) {
        d_head_p = nodePtr;
    }
    else {
        d_tail_p->setNext(nodePtr);
    }
    d_tail_p = nodePtr;
    ++d_size;
}

template <class KEY, class VALUE>
inline
void StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::clear()
{
    // Delete all content in a loop
    for (StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode = d_head_p;
                                                            curNode != NULL;) {
        StripedUnorderedContainerImpl_Node<KEY, VALUE> *nextPtr =
                                                               curNode->next();
        d_allocator_p->deleteObject(curNode);
        curNode = nextPtr;
    }
    d_head_p = NULL;
    d_tail_p = NULL;
    d_size = 0;
}

template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Node<KEY, VALUE>
              **StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::headAddress()
{
    return &d_head_p;
}

template <class KEY, class VALUE>
inline
void StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::incrementSize(
                                                                    int amount)
{
    d_size += amount;
}

template <class KEY, class VALUE>
inline
void StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::setHead(
                         StripedUnorderedContainerImpl_Node<KEY, VALUE> *value)
{
    d_head_p = value;
}

template <class KEY, class VALUE>
inline
void StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::setSize(
                                                             bsl::size_t value)
{
    d_size = value;
}

template <class KEY, class VALUE>
inline
void StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::setTail(
                         StripedUnorderedContainerImpl_Node<KEY, VALUE> *value)
{
    d_tail_p = value;
}

template <class KEY, class VALUE>
template <class EQUAL>
bsl::size_t StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::setValue(
                                                            const KEY&   key,
                                                            const EQUAL& equal,
                                                            const VALUE& value,
                                                            BucketScope  scope)
{
    if (d_head_p == NULL) {
        d_head_p = new (*d_allocator_p)
                                StripedUnorderedContainerImpl_Node<KEY, VALUE>(
                                                                key,
                                                                value,
                                                                NULL,
                                                                d_allocator_p);
        d_tail_p = d_head_p;
        d_size = 1;
        return 0;                                                     // RETURN
    }

    StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode = d_head_p;
    int                                             count   = 0;
    for (; curNode != NULL; curNode = curNode->next()) {
        if (equal(curNode->key(), key)) {
            curNode->value() = value;
            if (e_BUCKETSCOPE_FIRST == scope) {
                return 1;                                             // RETURN
            }
            ++count;
        }
    }
    if (count > 0) {
        return count;                                                 // RETURN
    }
    StripedUnorderedContainerImpl_Node<KEY, VALUE> *newNode =
           new (*d_allocator_p) StripedUnorderedContainerImpl_Node<KEY, VALUE>(
                                                                key,
                                                                value,
                                                                NULL,
                                                                d_allocator_p);
    d_tail_p->setNext(newNode);
    d_tail_p = d_tail_p->next();
    ++d_size;
    return 0;
}

template <class KEY, class VALUE>
template <class EQUAL>
bsl::size_t StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::setValue(
                                                const KEY&               key,
                                                const EQUAL&             equal,
                                                bslmf::MovableRef<VALUE> value)
{
    if (d_head_p == NULL) {
        d_head_p = new (*d_allocator_p)
                                StripedUnorderedContainerImpl_Node<KEY, VALUE>(
                                            key,
                                            bslmf::MovableRefUtil::move(value),
                                            NULL,
                                            d_allocator_p);
        d_tail_p = d_head_p;
        d_size = 1;
        return 0;                                                     // RETURN
    }
    StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode = d_head_p;
    for (; curNode != NULL; curNode = curNode->next()) {
        if (equal(curNode->key(), key)) {
#if defined(BSLMF_MOVABLEREF_USES_RVALUE_REFERENCES)
            curNode->value() = bslmf::MovableRefUtil::move(value);
#else
            curNode->value() = value;
#endif
            return 1;                                                 // RETURN
        }
    }
    StripedUnorderedContainerImpl_Node<KEY, VALUE> *newNode =
           new (*d_allocator_p) StripedUnorderedContainerImpl_Node<KEY, VALUE>(
                                            key,
                                            bslmf::MovableRefUtil::move(value),
                                            NULL,
                                            d_allocator_p);
    d_tail_p->setNext(newNode);
    d_tail_p = d_tail_p->next();
    ++d_size;
    return 0;
}

// ACCESSORS
template <class KEY, class VALUE>
inline
bool StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::empty() const
{
    return d_size == 0;
}

template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Node<KEY, VALUE>
                *StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::head() const
{
    return d_head_p;
}

template <class KEY, class VALUE>
inline
bsl::size_t StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::size() const
{
    return d_size;
}

template <class KEY, class VALUE>
inline
StripedUnorderedContainerImpl_Node<KEY, VALUE> *
                 StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::tail() const
{
    return d_tail_p;
}

                               // Aspects

template <class KEY, class VALUE>
inline
bslma::Allocator *StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::allocator()
                                                                          const
{
    return d_allocator_p;
}

             // -----------------------------------------------
             // class StripedUnorderedContainerImpl_LockElement
             // -----------------------------------------------

// CREATORS
inline
StripedUnorderedContainerImpl_LockElement::
                                    StripedUnorderedContainerImpl_LockElement()
: d_pad()
{
    (void)d_pad;
}

// MANIPULATORS
inline
void StripedUnorderedContainerImpl_LockElement::lockR()
{
    d_lock.lockRead();
}

inline
void StripedUnorderedContainerImpl_LockElement::lockW()
{
    d_lock.lockWrite();
}

inline
void StripedUnorderedContainerImpl_LockElement::unlockR()
{
    d_lock.unlockRead();
}

inline
void StripedUnorderedContainerImpl_LockElement::unlockW()
{
    d_lock.unlockWrite();
}

         // --------------------------------------------------------
         // class StripedUnorderedContainerImpl_LockElementReadGuard
         // --------------------------------------------------------

// CREATORS
inline
StripedUnorderedContainerImpl_LockElementReadGuard::
                            StripedUnorderedContainerImpl_LockElementReadGuard(
                     StripedUnorderedContainerImpl_LockElement *lockElementPtr)
: d_lockElement_p(lockElementPtr)
{
}

// MANIPULATORS
inline
StripedUnorderedContainerImpl_LockElementReadGuard::
                          ~StripedUnorderedContainerImpl_LockElementReadGuard()
{
    release();
}

inline
void StripedUnorderedContainerImpl_LockElementReadGuard::release()
{
    if (d_lockElement_p) {
        d_lockElement_p->unlockR();
        d_lockElement_p = NULL;
    }
}

          // ---------------------------------------------------------
          // class StripedUnorderedContainerImpl_LockElementWriteGuard
          // ---------------------------------------------------------

// CREATORS
inline
StripedUnorderedContainerImpl_LockElementWriteGuard::
                           StripedUnorderedContainerImpl_LockElementWriteGuard(
                     StripedUnorderedContainerImpl_LockElement *lockElementPtr)
: d_lockElement_p(lockElementPtr)
{
}

// MANIPULATORS
inline
StripedUnorderedContainerImpl_LockElementWriteGuard::
                         ~StripedUnorderedContainerImpl_LockElementWriteGuard()
{
    release();
}

inline
void StripedUnorderedContainerImpl_LockElementWriteGuard::release()
{
    if (d_lockElement_p) {
        d_lockElement_p->unlockW();
        d_lockElement_p = NULL;
    }
}

                      // -----------------------------------
                      // class StripedUnorderedContainerImpl
                      // -----------------------------------

// PRIVATE CLASS METHODS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
         StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::adjustBuckets(
                                                        bsl::size_t numBuckets,
                                                        bsl::size_t numStripes)
{
    // 'numBuckets' must not less than 2, and must be a power of 2.  To avoid
    // unused stripes, we also require numBuckets >= numStripes.
    if (numBuckets < 2) {
        numBuckets = 2;
    }
    if (numBuckets < numStripes) {
        numBuckets = numStripes;
    }
    numBuckets = powerCeil(numBuckets);
    return numBuckets;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::powerCeil(
                                                               bsl::size_t num)
{
    if (num <= 1) {
        return 1;                                                     // RETURN
    }
    int power = 2;
    --num;
    while (num >>= 1) {
        power <<= 1;
    }
    return power;
}

// PRIVATE MANIPULATORS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::checkRehash()
{
    float loadF = loadFactor();
    if (d_maxLoadFactor < loadF && canRehash()) {
        int ratio = static_cast<int>(loadF / d_maxLoadFactor);
        int growthFactor = 2;
        while (growthFactor < ratio) {
            growthFactor <<= 1;
        }
        bsl::size_t newNumBuckets = d_numBuckets * growthFactor;
        rehash(newNumBuckets);
    }
}

template <class KEY, class VALUE, class HASH, class EQUAL>
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::erase(
                                                              const KEY& key,
                                                              Scope      scope)
{
    bool        eraseAll = scope == e_SCOPE_ALL;
    bsl::size_t bucketIdx;
    LEWGuard    guard(lockWrite(&bucketIdx, key));

    StripedUnorderedContainerImpl_Bucket<KEY, VALUE> &bucket =
                                                          d_buckets[bucketIdx];

    typedef StripedUnorderedContainerImpl_Node<KEY, VALUE> Node;

    bsl::size_t count = 0;

    Node **prevNodeAddress = bucket.headAddress();
    Node  *prevNode        = NULL;
    while (*prevNodeAddress) {
        Node *node = *prevNodeAddress;
        if (d_comparator(node->key(), key)) {
            *prevNodeAddress = node->next();
            if (bucket.tail() == node) {
                bucket.setTail(prevNode);
            }
            d_allocator_p->deleteObject(node);
            bucket.incrementSize(-1);
            d_numElements.addRelaxed(-1);
            ++count;
            if (!eraseAll) {
                return count;                                         // RETURN
            }
        }
        else {
            prevNode        = node;
            prevNodeAddress = node->nextAddress();
        }
    }
    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
template <class RANDOM_ITER>
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::eraseBulk(
                                                             RANDOM_ITER first,
                                                             RANDOM_ITER last,
                                                             Scope       scope)
{
    BSLS_ASSERT(first <= last);

    bool        eraseAll = scope == e_SCOPE_ALL;
    bsl::size_t count    = 0;
    // For each key, store in a vector its stripe, location, and hash value.
    int dataSize = static_cast<int>(last - first);

    bsl::vector<StripedUnorderedContainerImpl_SortItem>
                        sortIdxs(dataSize, bslma::Default::defaultAllocator());
    for (int i = 0; i < dataSize; ++i) {
        sortIdxs[i].d_hashVal   = d_hasher(first[i]);
        bsl::size_t bucketIdx   =
            bslalg::HashTableImpUtil::computeBucketIndex(sortIdxs[i].d_hashVal,
                                                         d_numBuckets);
        sortIdxs[i].d_stripeIdx = static_cast<int>(bucketToStripe(bucketIdx));
        sortIdxs[i].d_dataIdx   = i;
    }
    // Sort it by stripe, and location
    bsl::sort(sortIdxs.begin(), sortIdxs.end());

    // Lock each stripe, and process all data points in it.  Do not recalculate
    // hash code (hence keeping the hash value).
    int curStripeIdx;
    for (int j = 0; j < dataSize;) {
        curStripeIdx = sortIdxs[j].d_stripeIdx;
        LockElement& lockElement = d_locks_p[curStripeIdx];
        lockElement.lockW();
        LEWGuard guard(&lockElement);
        for (; j < dataSize && sortIdxs[j].d_stripeIdx == curStripeIdx; ++j) {
            int          dataIdx   = sortIdxs[j].d_dataIdx;
            bsl::size_t  bucketIdx =
                                  bslalg::HashTableImpUtil::computeBucketIndex(
                                                         sortIdxs[j].d_hashVal,
                                                         d_numBuckets);

            StripedUnorderedContainerImpl_Bucket<KEY, VALUE> &bucket =
                                                          d_buckets[bucketIdx];

            const KEY& key  = first[dataIdx];

            Node **prevNodeAddress = bucket.headAddress();
            Node  *prevNode        = NULL;
            while (*prevNodeAddress) {
                Node *node = *prevNodeAddress;
                if (d_comparator(node->key(), key)) {
                    *prevNodeAddress = node->next();
                    if (bucket.tail() == node) {
                        bucket.setTail(prevNode);
                    }
                    d_allocator_p->deleteObject(node);
                    bucket.incrementSize(-1);
                    d_numElements.addRelaxed(-1);
                    ++count;
                    if (!eraseAll) {
                        break;
                    }
                }
                else {
                    prevNode        = node;
                    prevNodeAddress = node->nextAddress();
                }
            }
        }
    }

    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::insert(
                                                     const KEY&   key,
                                                     const VALUE& value,
                                                     Multiplicity multiplicity)
{
    bool insertAlways = multiplicity == e_INSERT_ALWAYS;

    bsl::size_t bucketIdx;
    LEWGuard    guard(lockWrite(&bucketIdx, key));

    bsl::size_t ret = 0;
    if (insertAlways) {
        // Insert, ignoring an existing value if any.  Use only in multimap.
        Node *node = new (*d_allocator_p)
                          StripedUnorderedContainerImpl_Node<KEY, VALUE>(key,
                                                                value,
                                                                NULL,
                                                                d_allocator_p);
        d_buckets[bucketIdx].addNode(node);
    }
    else {
        // Update only the first value if key exists.  Use only in hash map.
        ret = d_buckets[bucketIdx].setValue(
        key,
        d_comparator,
        value,
        StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::e_BUCKETSCOPE_FIRST);
    }
    if (ret == 1) {
        return 0;                                                     // RETURN
    }
    guard.release();
    d_numElements.addRelaxed(1);
    checkRehash();
    return 1;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::insert(
                                         const KEY&               key,
                                         bslmf::MovableRef<VALUE> value,
                                         Multiplicity             multiplicity)
{
    bool insertAlways = multiplicity == e_INSERT_ALWAYS;

    bsl::size_t bucketIdx;
    LEWGuard    guard(lockWrite(&bucketIdx, key));

    bsl::size_t ret = 0;
    if (insertAlways) {
        // Insert, ignoring an existing value if any.  Use only in multimap.
        Node *node = new (*d_allocator_p)
            Node(key, bslmf::MovableRefUtil::move(value), NULL, d_allocator_p);
        d_buckets[bucketIdx].addNode(node);
    }
    else {
        // Update only the first value if key exists.  Use only in hash map.
        ret = d_buckets[bucketIdx].setValue(
                                           key,
                                           d_comparator,
                                           bslmf::MovableRefUtil::move(value));
    }
    if (ret == 1) {
        return 0;                                                     // RETURN
    }
    guard.release();
    d_numElements.addRelaxed(1);
    checkRehash();
    return 1;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
template <class RANDOM_ITER>
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::insertBulk(
                                                     RANDOM_ITER  first,
                                                     RANDOM_ITER  last,
                                                     Multiplicity multiplicity)
{
    BSLS_ASSERT(first <= last);

    bool        insertAlways = multiplicity == e_INSERT_ALWAYS;
    bsl::size_t count        = 0;
    int         dataSize     = static_cast<int>(last - first);

    bsl::vector<StripedUnorderedContainerImpl_SortItem>
                        sortIdxs(dataSize, bslma::Default::defaultAllocator());

    // For each key, store in a vector its stripe, location, and hash value.
    for (int i = 0; i < dataSize; ++i) {
        sortIdxs[i].d_hashVal   = d_hasher(first[i].first);
        bsl::size_t bucketIdx =
           bslalg::HashTableImpUtil::computeBucketIndex(sortIdxs[i].d_hashVal,
                                                        d_numBuckets);
        sortIdxs[i].d_stripeIdx = static_cast<int>(bucketToStripe(bucketIdx));
        sortIdxs[i].d_dataIdx   = i;
    }
    // Sort it by stripe, and location
    bsl::sort(sortIdxs.begin(), sortIdxs.end());

    // Lock each stripe, and process all data points in it.  Do not recalculate
    // hash code (hence keeping the hash value).
    int curStripeIdx;
    for (int j = 0; j < dataSize;) {
        curStripeIdx = sortIdxs[j].d_stripeIdx;
        LockElement& lockElement = d_locks_p[curStripeIdx];
        lockElement.lockW();
        LEWGuard guard(&lockElement);
        for (; j < dataSize && sortIdxs[j].d_stripeIdx == curStripeIdx; ++j) {
            int          dataIdx   = sortIdxs[j].d_dataIdx;
            bsl::size_t  bucketIdx =
                bslalg::HashTableImpUtil::computeBucketIndex(
                                                         sortIdxs[j].d_hashVal,
                                                         d_numBuckets);
            const KEY&   key   = first[dataIdx].first;
            const VALUE& value = first[dataIdx].second;

            if (insertAlways) {
                // Insert, ignoring an existing value if any.  Use only in
                // multimap.
                StripedUnorderedContainerImpl_Node<KEY, VALUE> *node =
                                                           new (*d_allocator_p)
                                StripedUnorderedContainerImpl_Node<KEY, VALUE>(
                                                                key,
                                                                value,
                                                                NULL,
                                                                d_allocator_p);
                d_buckets[bucketIdx].addNode(node);
                ++count;
                d_numElements.addRelaxed(1);
            } else {
                bsl::size_t ret = d_buckets[bucketIdx].setValue(
                    key,
                    d_comparator,
                    value,
                    StripedUnorderedContainerImpl_Bucket<KEY, VALUE>::
                                                          e_BUCKETSCOPE_FIRST);
                if (ret == 0) {
                    ++count;
                    d_numElements.addRelaxed(1);
                }
            }
        }
    }
    checkRehash();
    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
int StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::setComputedValue(
                                                const KEY&             key,
                                                const VisitorFunction& visitor,
                                                Scope                  scope)
{
    typedef StripedUnorderedContainerImpl_Bucket<KEY, VALUE> BucketClass;
    typename BucketClass::BucketScope setAll = scope == e_SCOPE_ALL
                                            ? BucketClass::e_BUCKETSCOPE_ALL
                                            : BucketClass::e_BUCKETSCOPE_FIRST;

    bsl::size_t                bucketIdx;
    LEWGuard                   guard(lockWrite(&bucketIdx, key));

    StripedUnorderedContainerImpl_Bucket<KEY, VALUE>& bucket =
                                                          d_buckets[bucketIdx];
    // Loop on the elements in the list
    int                                             count = 0;
    StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode = bucket.head();
    for (; curNode != NULL; curNode = curNode->next()) {
        if (d_comparator(curNode->key(), key)) {
            bool ret = visitor(&curNode->value(), key);
            if (false == setAll) {
                return ret ? 1 : -1;                                  // RETURN
            }
            ++count;
            if (false == ret) {
                return -count;                                        // RETURN
            }
        }
    }
    if (count > 0) {
        return count;                                                 // RETURN
    }

    // Not found - process as false, and return 0.
    StripedUnorderedContainerImpl_Node<KEY, VALUE> *addNode =
                                                           new (*d_allocator_p)
                 StripedUnorderedContainerImpl_Node<KEY, VALUE>(key,
                                                                NULL,
                                                                d_allocator_p);
    {
        bslma::RawDeleterProctor<
            StripedUnorderedContainerImpl_Node<KEY, VALUE>,
            bslma::Allocator>
            proctor(addNode, d_allocator_p);

        visitor(&addNode->value(), key);
        proctor.release();
    }

    if (bucket.head() == NULL) {
        bucket.setHead(addNode);
        bucket.setTail(addNode);
    }
    else {
        bucket.tail()->setNext(addNode);
        bucket.setTail(addNode);
    }
    d_numElements.addRelaxed(1);
    bucket.incrementSize(1);
    guard.release();
    checkRehash();
    return 0;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::setValue(
                                                            const KEY&   key,
                                                            const VALUE& value,
                                                            Scope        scope)
{
    typedef StripedUnorderedContainerImpl_Bucket<KEY, VALUE> BucketClass;
    typename BucketClass::BucketScope setAll = scope == e_SCOPE_ALL
                                            ? BucketClass::e_BUCKETSCOPE_ALL
                                            : BucketClass::e_BUCKETSCOPE_FIRST;

    bsl::size_t                bucketIdx;
    LEWGuard                   guard(lockWrite(&bucketIdx, key));

    StripedUnorderedContainerImpl_Bucket<KEY, VALUE>& bucket =
                                                          d_buckets[bucketIdx];

    bsl::size_t count = bucket.setValue(key, d_comparator, value, setAll);
    if (count == 0) {
        guard.release();
        d_numElements.addRelaxed(1);
        checkRehash();
    }
    return count;
}

// PRIVATE ACCESSORS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::bucketIndex(
                                                  const KEY&  key,
                                                  bsl::size_t numBuckets) const
{
    bsl::size_t hashVal   = d_hasher(key);
    bsl::size_t bucketIdx =
             bslalg::HashTableImpUtil::computeBucketIndex(hashVal, numBuckets);
    return bucketIdx;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::bucketToStripe(
                                                 bsl::size_t bucketIndex) const
{
    return bucketIndex & d_hashMask;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
template <class VECTOR>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::getValueImpl(
                                                        VECTOR     *valuesPtr,
                                                        const KEY&   key) const
{
    static const bool isVector =
                           bsl::is_same<bsl::vector<VALUE>, VECTOR>::value
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
                        || bsl::is_same<std::pmr::vector<VALUE>, VECTOR>::value
#endif
                        || bsl::is_same<std::vector<VALUE>, VECTOR>::value;
    BSLMF_ASSERT(isVector);

    BSLS_ASSERT(NULL != valuesPtr);

    valuesPtr->clear();

    bsl::size_t bucketIdx;
    LERGuard    guard(lockRead(&bucketIdx, key));

    bsl::size_t                                             count  = 0;
    const StripedUnorderedContainerImpl_Bucket<KEY, VALUE>& bucket = d_buckets[
                                                                    bucketIdx];
    // Loop on the elements in the list
    StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode  = bucket.head();
    for (; curNode != NULL; curNode = curNode->next()) {
        if (d_comparator(curNode->key(), key)) {
            valuesPtr->push_back(curNode->value());
            ++count;
        }
    }
    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
StripedUnorderedContainerImpl_LockElement *
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::lockRead(
                                                       bsl::size_t  *bucketIdx,
                                                       const KEY&    key) const
{
    // From key, get hash value, and current number of buckets.
    bsl::size_t  hashVal    = d_hasher(key);
    bsl::size_t  numBuckets = d_numBuckets;
    bsl::size_t  bucketIndex  =
           bslalg::HashTableImpUtil::computeBucketIndex(hashVal, d_numBuckets);
    bsl::size_t  stripeIdx = bucketToStripe(bucketIndex);
    LockElement& lockElement = d_locks_p[stripeIdx];
    lockElement.lockR();
    // When we get the lock, did the number of buckets change?
    if (numBuckets != d_numBuckets) {
        bucketIndex =
           bslalg::HashTableImpUtil::computeBucketIndex(hashVal, d_numBuckets);
    }
    *bucketIdx = bucketIndex;
    return &lockElement;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
StripedUnorderedContainerImpl_LockElement *
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::lockWrite(
                                                       bsl::size_t  *bucketIdx,
                                                       const KEY&    key) const
{
    // From key, get hash value, and current number of buckets.
    bsl::size_t  hashVal      = d_hasher(key);
    bsl::size_t  numBuckets   = d_numBuckets;
    bsl::size_t  bucketIndex  =
           bslalg::HashTableImpUtil::computeBucketIndex(hashVal, d_numBuckets);
    bsl::size_t  stripeIdx   = bucketToStripe(bucketIndex);
    LockElement& lockElement = d_locks_p[stripeIdx];
    lockElement.lockW();

    // When we get the lock, did the number of buckets change?
    if (numBuckets != d_numBuckets) {
        bucketIndex =
           bslalg::HashTableImpUtil::computeBucketIndex(hashVal, d_numBuckets);
    }
    *bucketIdx = bucketIndex;
    return &lockElement;
}

// CREATORS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::
                                                 StripedUnorderedContainerImpl(
                                           bsl::size_t       numInitialBuckets,
                                           bsl::size_t       numStripes,
                                           bslma::Allocator *basicAllocator)
: d_numStripes(powerCeil(numStripes))
, d_numBuckets(adjustBuckets(numInitialBuckets, d_numStripes))
, d_hashMask(d_numStripes - 1)
, d_maxLoadFactor(1.0)
, d_hasher()
, d_comparator()
, d_statePad()
, d_numElementsPad()
, d_buckets(d_numBuckets, basicAllocator)
, d_allocator_p(bslma::Default::allocator(basicAllocator))
{
    d_state       = k_REHASH_ENABLED; // Rehash enabled, not in progress
    d_numElements = 0; // Hash empty

    // Allocate array of 'LockElement' objects, and construct them.
    d_locks_p = reinterpret_cast<LockElement*>(
                  d_allocator_p->allocate(d_numStripes * sizeof(LockElement)));
    for (bsl::size_t i = 0; i < d_numStripes; ++i) {
        bslma::ConstructionUtil::construct(&d_locks_p[i], d_allocator_p);
    }
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::
                                               ~StripedUnorderedContainerImpl()
{
    for (bsl::size_t i = 0; i < d_numStripes; ++i) {
        bslma::DestructionUtil::destroy(&d_locks_p[i]);
    }
    d_allocator_p->deallocate(d_locks_p);
}

// MANIPULATORS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::clear()
{
    // Locking all stripes will inherently block until a rehash will complete
    for (bsl::size_t i = 0; i < d_numStripes; ++i) {
        d_locks_p[i].lockW();
    }
    for (bsl::size_t j = 0; j < d_numBuckets; ++j) {
        d_buckets[j].clear();
    }
    d_numElements = 0;
    for (bsl::size_t i = 0; i < d_numStripes; ++i) {
        d_locks_p[i].unlockW();
    }
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::disableRehash()
{
    for (;;) {
        int oldState = d_state.load();
        int newState = oldState & ~k_REHASH_ENABLED;
        if (oldState == d_state.testAndSwap(oldState, newState)) {
            return;                                                   // RETURN
        }
    }
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::enableRehash()
{
    for (;;) {
        int oldState = d_state.load();
        int newState = oldState | k_REHASH_ENABLED;
        if (oldState == d_state.testAndSwap(oldState, newState)) {
            break;
        }
    }
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::eraseAll(
                                                                const KEY& key)
{
    return erase(key, e_SCOPE_ALL);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
template <class RANDOM_ITER>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::eraseBulkAll(
                                                             RANDOM_ITER first,
                                                             RANDOM_ITER last)
{
    BSLS_ASSERT(first <= last);

    return eraseBulk(first, last, e_SCOPE_ALL);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
template <class RANDOM_ITER>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::eraseBulkFirst(
                                                             RANDOM_ITER first,
                                                             RANDOM_ITER last)
{
    BSLS_ASSERT(first <= last);

    return eraseBulk(first, last, e_SCOPE_FIRST);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::eraseFirst(
                                                                const KEY& key)
{
    return erase(key, e_SCOPE_FIRST);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::insertAlways(
                                                            const KEY&   key,
                                                            const VALUE& value)
{
    insert(key, value, e_INSERT_ALWAYS);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::insertAlways(
                                                const KEY&               key,
                                                bslmf::MovableRef<VALUE> value)
{
    insert(key, bslmf::MovableRefUtil::move(value), e_INSERT_ALWAYS);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
template <class RANDOM_ITER>
inline
void StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::insertBulkAlways(
                                                             RANDOM_ITER first,
                                                             RANDOM_ITER last)
{
    BSLS_ASSERT(first <= last);

    insertBulk(first, last, e_INSERT_ALWAYS);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
template <class RANDOM_ITER>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::insertBulkUnique(
                                                             RANDOM_ITER first,
                                                             RANDOM_ITER last)
{
    BSLS_ASSERT(first <= last);

    return insertBulk(first, last, e_INSERT_UNIQUE);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::insertUnique(
                                                            const KEY&   key,
                                                            const VALUE& value)
{
    return insert(key, value, e_INSERT_UNIQUE);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::insertUnique(
                                                const KEY&               key,
                                                bslmf::MovableRef<VALUE> value)
{
    return insert(key, bslmf::MovableRefUtil::move(value), e_INSERT_UNIQUE);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::maxLoadFactor(
                                                        float newMaxLoadFactor)
{
    BSLS_ASSERT(0 < newMaxLoadFactor);

    d_maxLoadFactor = newMaxLoadFactor;
    checkRehash();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
void StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::rehash(
                                                        bsl::size_t numBuckets)
{
    // 'numBuckets' must not less than 2, and must be a power of 2.  To avoid
    // unused stripes, we also require numBuckets >= numStripes.
    if (numBuckets < 2) {
        numBuckets = 2;
    }
    if (numBuckets < d_numStripes) {
        numBuckets = d_numStripes;
    }
    numBuckets = powerCeil(numBuckets);

    if (numBuckets == d_numBuckets) { // Skip if no change in # of buckets
        return;                                                       // RETURN
    }
    if (!canRehash()) { // Skip if can't rehash
        return;                                                       // RETURN
    }

    // Set state to rehash
    int oldState = d_state.testAndSwap(
                                      k_REHASH_ENABLED,
                                      k_REHASH_ENABLED | k_REHASH_IN_PROGRESS);
    if (oldState != k_REHASH_ENABLED) { // State changed under our feet
        return;                                                       // RETURN
    }

    // Allocate a new data vector
    bsl::vector<StripedUnorderedContainerImpl_Bucket<KEY,VALUE> > newBuckets(
                                                                numBuckets,
                                                                d_allocator_p);

    // Main loop on stripes: lock a stripe and process all buckets in it
    for (bsl::size_t i = 0; i < d_numStripes; ++i) {
        d_locks_p[i].lockW();
        // Loop on the buckets of the current stripe.  This is simple, as the
        // stripe is the last bits in a bucket index.  We start with the
        // current stripe as the first bucket, and add 'd_numStripes' for the
        // next bucket, until 'd_numBuckets'.
        for (bsl::size_t j = i; j < d_numBuckets; j += d_numStripes) {
            StripedUnorderedContainerImpl_Bucket<KEY, VALUE> &bucket =
                                                                  d_buckets[j];
            // Process the nodes in the bucket.  Note that we do not need to
            // delete the old node and allocate a new one, but can simply move
            // it.
            for (StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode =
                                             bucket.head(); curNode != NULL;) {
                StripedUnorderedContainerImpl_Node<KEY, VALUE> *nextPtr =
                                                               curNode->next();

                bsl::size_t newBucketIdx = bucketIndex(curNode->key(),
                                                       numBuckets);
                curNode->setNext(NULL);
                newBuckets[newBucketIdx].addNode(curNode);
                curNode = nextPtr;
            }
            bucket.setHead(NULL);
            bucket.setTail(NULL);
            bucket.setSize(0);
        }
    }
    // Swap 'newBuckets' and 'd_buckets'.  This requires the same allocator.
    d_buckets.swap(newBuckets);

    // Update number of buckets.
    d_numBuckets = numBuckets;

    // Unlock all stripes.  This could not be done on the spot, as we store the
    /// rehashed data in a new set of buckets.
    for (bsl::size_t i = 0; i < d_numStripes; ++i) {
        d_locks_p[i].unlockW();
    }

    // Rehash no longer in progress
    d_state = d_state & ~k_REHASH_IN_PROGRESS;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
int
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::setComputedValueAll(
                                                const KEY&             key,
                                                const VisitorFunction& visitor)
{
    return setComputedValue(key, visitor, e_SCOPE_ALL);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
int
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::setComputedValueFirst(
                                                const KEY&             key,
                                                const VisitorFunction& visitor)
{
    return setComputedValue(key, visitor, e_SCOPE_FIRST);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::setValueAll(
                                                            const KEY&   key,
                                                            const VALUE& value)
{
    return setValue(key, value, e_SCOPE_ALL);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::setValueFirst(
                                                            const KEY&   key,
                                                            const VALUE& value)
{
    return setValue(key, value, e_SCOPE_FIRST);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::setValueFirst(
                                                const KEY&               key,
                                                bslmf::MovableRef<VALUE> value)
{
    bsl::size_t bucketIdx;
    LEWGuard    guard(lockWrite(&bucketIdx, key));

    StripedUnorderedContainerImpl_Bucket<KEY, VALUE>& bucket =
                                                          d_buckets[bucketIdx];

    bsl::size_t count = bucket.setValue(key,
                                        d_comparator,
                                        bslmf::MovableRefUtil::move(value));
    if (count == 0) {
        guard.release();
        d_numElements.addRelaxed(1);
        checkRehash();
    }
    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
int StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::update(
                                                const KEY&             key,
                                                const VisitorFunction& visitor)
{
    return visit(key, visitor);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
int StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::visit(
                                                const VisitorFunction& visitor)
{
    // Main loop on stripes: lock a stripe and process all buckets in it
    int count = 0;
    for (bsl::size_t i = 0; i < d_numStripes; ++i) {
        d_locks_p[i].lockW();
        // Loop on the buckets of the current stripe.  This is simple, as the
        // stripe is the last bits in a bucket index.  We start with the
        // current stripe as the first bucket, and add 'd_numStripes' for the
        // next bucket, until 'd_numBuckets'.
        for (bsl::size_t j = i; j < d_numBuckets; j += d_numStripes) {
            StripedUnorderedContainerImpl_Bucket<KEY, VALUE> &bucket =
                                                                  d_buckets[j];
            // Loop on the nodes in the bucket.
            for (StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode =
                                                bucket.head(); curNode != NULL;
                                                   curNode = curNode->next()) {
                ++count;
                bool ret = visitor(&(curNode->value()), curNode->key());
                if (!ret) {
                    d_locks_p[i].unlockW();
                    return -count;                                    // RETURN
                }
            }
        }
        d_locks_p[i].unlockW();
    }
    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
int StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::visit(
                                                const KEY&             key,
                                                const VisitorFunction& visitor)
{
    bsl::size_t bucketIdx;
    LEWGuard    guard(lockWrite(&bucketIdx, key));

    StripedUnorderedContainerImpl_Bucket<KEY, VALUE>& bucket =
                                                          d_buckets[bucketIdx];

    // Loop on the elements in the list
    int                                             count = 0;
    StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode = bucket.head();
    for (; curNode != NULL; curNode = curNode->next()) {
        if (d_comparator(curNode->key(), key)) {
            ++count;
            bool ret = visitor(&curNode->value(), key);
            if (ret == false) {
                return -count;                                        // RETURN
            }
        }
    }
    return count;
}

// ACCESSORS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::bucketCount() const
{
    return d_numBuckets;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::bucketIndex(
                                                          const KEY& key) const
{
    return bucketIndex(key, d_numBuckets);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::bucketSize(
                                                       bsl::size_t index) const
{
    BSLS_ASSERT(index < bucketCount());

    return d_buckets[index].size();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bool StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::canRehash() const
{
    return d_state == k_REHASH_ENABLED;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bool StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::empty() const
{
    for (bsl::size_t i = 0; i < d_numBuckets; ++i) {
        if (!d_buckets[i].empty()) {
            return false;                                             // RETURN
        }
    }
    return true;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
EQUAL
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::equalFunction() const
{
    return d_comparator;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::getValue(
                                                         VALUE      *value,
                                                         const KEY&  key) const
{
    BSLS_ASSERT(NULL != value);

    bsl::size_t bucketIdx;
    LERGuard    guard(lockRead(&bucketIdx, key));

    const StripedUnorderedContainerImpl_Bucket<KEY, VALUE>& bucket = d_buckets[
                                                                    bucketIdx];
    // Loop on the elements in the list
    StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode  = bucket.head();
    for (; curNode != NULL; curNode = curNode->next()) {
        if (d_comparator(curNode->key(), key)) {
            *value = curNode->value();
            return 1;                                                 // RETURN
        }
    }
    return 0;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::getValue(
                                                 bsl::vector<VALUE> *valuesPtr,
                                                 const KEY&          key) const
{
    return getValueImpl(valuesPtr, key);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::getValue(
                                                 std::vector<VALUE> *valuesPtr,
                                                 const KEY&          key) const
{
    return getValueImpl(valuesPtr, key);
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::getValue(
                                            std::pmr::vector<VALUE> *valuesPtr,
                                            const KEY&               key) const
{
    return getValueImpl(valuesPtr, key);
}
#endif

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
HASH
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::hashFunction() const
{
    return d_hasher;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bool
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::isRehashEnabled() const
{
    return d_state & k_REHASH_ENABLED;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
float
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::loadFactor() const
{
    return static_cast<float>(d_numElements.loadRelaxed()) /
           static_cast<float>(d_numBuckets);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
float
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::maxLoadFactor() const
{
    return d_maxLoadFactor;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::numStripes() const
{
    return static_cast<bsl::size_t>(d_numStripes);
}

template <class KEY, class VALUE, class HASH, class EQUAL>
int StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::visitReadOnly(
                                  const ReadOnlyVisitorFunction& visitor) const
{
    // Main loop on stripes: lock a stripe and process all buckets in it
    int count = 0;
    for (bsl::size_t i = 0; i < d_numStripes; ++i) {
        d_locks_p[i].lockR();
        // Loop on the buckets of the current stripe.  This is simple, as the
        // stripe is the last bits in a bucket index.  We start with the
        // current stripe as the first bucket, and add 'd_numStripes' for the
        // next bucket, until 'd_numBuckets'.
        for (bsl::size_t j = i; j < d_numBuckets; j += d_numStripes) {
            const StripedUnorderedContainerImpl_Bucket<KEY, VALUE> &bucket =
                                                                  d_buckets[j];
            // Loop on the nodes in the bucket.
            for (StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode =
                                                bucket.head(); curNode != NULL;
                                                   curNode = curNode->next()) {
                ++count;
                bool ret = visitor(curNode->value(), curNode->key());
                if (!ret) {
                    d_locks_p[i].unlockR();
                    return -count;                                    // RETURN
                }
            }
        }
        d_locks_p[i].unlockR();
    }
    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
int StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::visitReadOnly(
                                  const KEY&                     key,
                                  const ReadOnlyVisitorFunction& visitor) const
{
    bsl::size_t bucketIdx;
    LERGuard    guard(lockRead(&bucketIdx, key));

    const StripedUnorderedContainerImpl_Bucket<KEY, VALUE>& bucket =
                                                          d_buckets[bucketIdx];

    // Loop on the elements in the list
    int                                             count = 0;
    StripedUnorderedContainerImpl_Node<KEY, VALUE> *curNode = bucket.head();
    for (; curNode != NULL; curNode = curNode->next()) {
        if (d_comparator(curNode->key(), key)) {
            ++count;
            bool ret = visitor(curNode->value(), key);
            if (ret == false) {
                return -count;                                        // RETURN
            }
        }
    }
    return count;
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bsl::size_t
StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::size() const
{
    return d_numElements.loadRelaxed();
}

                               // Aspects

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
bslma::Allocator *
      StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>::allocator() const
{
    return d_allocator_p;
}

             // --------------------------------------------
             // class StripedUnorderedContainerImpl_TestUtil
             // --------------------------------------------

// CREATORS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
StripedUnorderedContainerImpl_TestUtil<KEY, VALUE, HASH, EQUAL>::
                                        StripedUnorderedContainerImpl_TestUtil(
                  StripedUnorderedContainerImpl<KEY, VALUE, HASH, EQUAL>& hash)
: d_hash(hash)
{
}

// MANIPULATORS
template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl_TestUtil<KEY, VALUE, HASH, EQUAL>::lockRead(
                                                                const KEY& key)
{
    bsl::size_t  bucketIdx = d_hash.bucketIndex(key);
    bsl::size_t  stripeIdx = d_hash.bucketToStripe(bucketIdx);
    LockElement& lockElement = d_hash.d_locks_p[stripeIdx];
    lockElement.lockR();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl_TestUtil<KEY, VALUE, HASH, EQUAL>::
                                                      lockWrite(const KEY& key)
{
    bsl::size_t  bucketIdx   = d_hash.bucketIndex(key);
    bsl::size_t  stripeIdx   = d_hash.bucketToStripe(bucketIdx);
    LockElement& lockElement = d_hash.d_locks_p[stripeIdx];
    lockElement.lockW();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl_TestUtil<KEY, VALUE, HASH, EQUAL>::
                                                     unlockRead(const KEY& key)
{
    bsl::size_t  bucketIdx   = d_hash.bucketIndex(key);
    bsl::size_t  stripeIdx   = d_hash.bucketToStripe(bucketIdx);
    LockElement& lockElement = d_hash.d_locks_p[stripeIdx];
    lockElement.unlockR();
}

template <class KEY, class VALUE, class HASH, class EQUAL>
inline
void StripedUnorderedContainerImpl_TestUtil<KEY, VALUE, HASH, EQUAL>::
                                                    unlockWrite(const KEY& key)
{
    bsl::size_t  bucketIdx   = d_hash.bucketIndex(key);
    bsl::size_t  stripeIdx   = d_hash.bucketToStripe(bucketIdx);
    LockElement& lockElement = d_hash.d_locks_p[stripeIdx];
    lockElement.unlockW();
}

}  // close package namespace

// FREE OPERATORS
inline
bool bdlcc::operator<(const StripedUnorderedContainerImpl_SortItem& lhs,
                      const StripedUnorderedContainerImpl_SortItem& rhs)
{
    if (lhs.d_stripeIdx < rhs.d_stripeIdx) {
        return true;                                                  // RETURN
    }
    if (lhs.d_stripeIdx > rhs.d_stripeIdx) {
        return false;                                                 // RETURN
    }
    if (lhs.d_dataIdx < rhs.d_dataIdx) {
        return true;                                                  // RETURN
    }
    return false;
}

namespace bslma {

template <class KEY, class VALUE, class HASH, class EQUAL>
struct UsesBslmaAllocator<bdlcc::StripedUnorderedContainerImpl<KEY,
                                                               VALUE,
                                                               HASH,
                                                               EQUAL> >
                                                             : bsl::true_type {
};

}  // close namespace bslma

}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2020 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations
// under the License.
// ----------------------------- END-OF-FILE ----------------------------------