/*++

   Copyright    (c) 1998-2000    Microsoft Corporation

   Module  Name :
       LKR-hash.h

   Abstract:
       Declares LKRhash: a fast, scalable, cache- and
       multiprocessor-friendly hash table

   Authors:
       Paul (Per-Ake) Larson, PALarson@microsoft.com, July 1997
       Murali R. Krishnan    (MuraliK)
       George V. Reilly      (GeorgeRe)     06-Jan-1998

--*/


#ifndef __LKR_HASH_H__
#define __LKR_HASH_H__


/* Enable STL-style iterators */
#ifndef LKR_NO_STL_ITERATORS
# define LKR_STL_ITERATORS 1
#endif /* !LKR_NO_STL_ITERATORS */

/* Enable call-back, table visitor routines */
#ifndef LKR_NO_APPLY_IF
# define LKR_APPLY_IF
#endif /* !LKR_NO_APPLY_IF */

/* Expose the table's ReadLock and WriteLock routines */
#ifndef LKR_NO_EXPOSED_TABLE_LOCK
# define LKR_EXPOSED_TABLE_LOCK
#endif /* !LKR_NO_EXPOSED_TABLE_LOCK */


#ifndef __IRTLMISC_H__
# include <irtlmisc.h>
#endif /* !__IRTLMISC_H__ */



#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */


typedef struct LkrHashTable* PLkrHashTable;


/*--------------------------------------------------------------------
 * Possible return codes from LKR_functions and TypedLkrHashTable
 */
enum LK_RETCODE {
    /* severe errors < 0 */
    LK_UNUSABLE = -99,  /* Table corrupted: all bets are off */
    LK_ALLOC_FAIL,      /* ran out of memory */
    LK_BAD_ITERATOR,    /* invalid iterator; e.g., points to another table */
    LK_BAD_RECORD,      /* invalid record; e.g., NULL for LKR_InsertRecord */
    LK_BAD_PARAMETERS,  /* invalid parameters; e.g., NULL fnptrs to ctor */
    LK_NOT_INITIALIZED, /* LKRHashTableInit was not called */
    LK_BAD_TABLE,       /* Called with invalid PLkrHashTable */

    LK_SUCCESS = 0,     /* everything's okay */
    LK_KEY_EXISTS,      /* key already present for
                            LKR_InsertRecord(no-overwrite) */
    LK_NO_SUCH_KEY,     /* key not found */
    LK_NO_MORE_ELEMENTS,/* iterator exhausted */
};

#define LKR_SUCCEEDED(lkrc)  ((lkrc) >= LK_SUCCESS)


/*--------------------------------------------------------------------
 * Size parameter to LKR_CreateTable
 */

enum LK_TABLESIZE {
    LK_SMALL_TABLESIZE=  1,     /* < 200 elements */
    LK_MEDIUM_TABLESIZE= 2,     /* 200...10,000 elements */
    LK_LARGE_TABLESIZE=  3,     /* 10,000+ elements */
};


/*--------------------------------------------------------------------
 * Creation flag parameter to LKR_CreateTable
 */

enum {
    LK_CREATE_DEFAULT   =      0,   /* 0 is an acceptable default */
    LK_CREATE_MULTIKEYS = 0x0001,   /* Allow multiple identical keys? */
};


/*--------------------------------------------------------------------
 * Initialization flag parameters to LKR_Initialize
 */

enum {
    LK_INIT_DEFAULT    =      0, /* 0 is an acceptable default */
    LK_INIT_DEBUG_SPEW = 0x1000, /* Enable debug output: debug version only */
};



/*--------------------------------------------------------------------
 * Reference Counting and Lifetime Management
 *
 * Increment the reference count of a record before returning it from
 * LKR_FindKey. It's necessary to do it in LKR_FindKey itself while the
 * bucket is still locked, rather than one of the wrappers, to avoid race
 * conditions. Similarly, the reference count is incremented in
 * LKR_InsertRecord and decremented in LKR_DeleteKey. Finally, if an old
 * record is overwritten in LKR_InsertRecord, its reference count is
 * decremented.
 *
 * Summary of calls to AddRefRecord
 *   +1: add a new reference or owner
 *      - LKR_InsertRecord
 *      - LKR_FindKey
 *      - IncrementIterator
 *   -1: delete a reference => release an owner
 *      - LKR_InsertRecord (overwrite old record with same key)
 *      - LKR_DeleteKey, LKR_DeleteRecord
 *      - ApplyIf(LKP_DELETE), DeleteIf
 *      - IncrementIterator (previous record), CloseIterator, Erase(iter)
 *      - LKR_Clear, table destructor
 *   0: no change (not called)
 *      - LKR_FindRecord (by definition, you already have a ref to the record)
 *
 * It's up to you to decrement the reference count when you're finished
 * with it after retrieving it via LKR_FindKey (e.g., you could call
 * pht->AddRefRecord(pRec, LKAR_EXPLICIT_RELEASE)) and to determine the
 * semantics of what this means. The hashtable itself has no notion of
 * reference counts; this is merely to help with the lifetime management
 * of the record objects.
 */

/* These reason codes help in debugging refcount leaks */
enum LK_ADDREF_REASON {

/* negative reasons => decrement refcount => release ownership */
    LKAR_EXPLICIT_RELEASE    = -29, /* user calls ht.AddRefRecord to */
                                        /* explicitly release a record */
    LKAR_DELETE_KEY          = -28, /* DeleteKey() */
    LKAR_DELETE_RECORD       = -27, /* DeleteRecord() */
    LKAR_INSERT_RELEASE      = -26, /* InsertRecord overwrites prev record */
    LKAR_CLEAR               = -25, /* Clear() */
    LKAR_DTOR                = -24, /* hash table destructor */
    LKAR_APPLY_DELETE        = -23, /* Apply[If] LKP_(PERFORM|_DELETE) */
    LKAR_DELETE_IF_DELETE    = -22, /* DeleteIf  LKP_(PERFORM|_DELETE) */
    LKAR_ITER_RELEASE        = -21, /* ++iter releases previous record */
    LKAR_ITER_ASSIGN_RELEASE = -20, /* iter.operator= releases prev rec */
    LKAR_ITER_DTOR           = -19, /* ~iter */
    LKAR_ITER_ERASE          = -18, /* Erase(iter): iter releases record */
    LKAR_ITER_ERASE_TABLE    = -17, /* Erase(iter); table releases record */
    LKAR_ITER_CLOSE          = -16, /* CloseIterator (obsolete) */

/* positive reasons => increment refcount => add an owner */
    LKAR_INSERT_RECORD       = +11, /* InsertRecord() */
    LKAR_FIND_KEY            = +12, /* FindKey() */
    LKAR_ITER_ACQUIRE        = +13, /* ++iter acquires next record */
    LKAR_ITER_COPY_CTOR      = +14, /* iter copy constructor acquires rec */
    LKAR_ITER_ASSIGN_ACQUIRE = +15, /* iter.operator= acquires new rec */
    LKAR_ITER_INSERT         = +16, /* Insert(iter) */
    LKAR_ITER_FIND           = +17, /* Find(iter) */
    LKAR_EXPLICIT_ACQUIRE    = +18, /* user calls ht.AddRefRecord to */
                                        /* explicitly acquire a ref to a rec */
};


/* Convert an LK_ADDREF_REASON to a string representation.
 * Useful for debugging.
 */
IRTL_DLLEXP
const char*
LKR_AddRefReasonAsString(
    LK_ADDREF_REASON lkar);



/*--------------------------------------------------------------------
 * Callback functions needed by table:
 *     ExtractKey, CalcKeyHash, EqualKeys, AddRefRecord
 * Internally, records are handled as `const void*' and
 * keys are handled as `const DWORD_PTR'. The latter allows for
 * keys to be numbers as well as pointers (polymorphism).
 */


/* Use types defined in recent versions of the Platform SDK in <basetsd.h>.
 */
#ifndef _W64
typedef DWORD DWORD_PTR;    /* integral type big enough to hold a pointer */
#endif


/* Given a record, return its key. Assumes that the key is embedded in
 * the record, or at least somehow derivable from the record. For
 * completely unrelated keys & values, a wrapper class should use
 * something like STL's pair<key,value> template to aggregate them
 * into a record.
 */
typedef
const DWORD_PTR
(WINAPI *LKR_PFnExtractKey)  (
    const void* pvRecord);

/* Given a key, return its hash signature. The hashing functions in
 * hashfn.h (or something that builds upon them) are suggested.
 */
typedef
DWORD
(WINAPI *LKR_PFnCalcKeyHash) (
    const DWORD_PTR pnKey);

/* Compare two keys for equality; e.g., _stricmp, memcmp, operator==
 */
typedef
BOOL
(WINAPI *LKR_PFnEqualKeys) (
    const DWORD_PTR pnKey1,
    const DWORD_PTR pnKey2);


/* Adjust the reference count of a record. See the earlier discussion
 * of reference counting and lifetime management.
 */
typedef
void
(WINAPI *LKR_PFnAddRefRecord)(
    const void*      pvRecord,
    LK_ADDREF_REASON lkar);



#ifdef LKR_APPLY_IF
/*--------------------------------------------------------------------
 * Apply, ApplyIf, and DeleteIf provide one way to visit (enumerate) all
 * records in a table.
 */

/*--------------------------------------------------------------------
 * Return codes from PFnRecordPred.
 */

enum LK_PREDICATE {
    LKP_ABORT = 1,           /* Stop walking the table immediately */
    LKP_NO_ACTION = 2,       /* No action, just keep walking */
    LKP_PERFORM = 3,         /* Perform action and continue walking */
    LKP_PERFORM_STOP = 4,    /* Perform action, then stop */
    LKP_DELETE = 5,          /* Delete record and keep walking */
    LKP_DELETE_STOP = 6,     /* Delete record, then stop */
};


/*--------------------------------------------------------------------
 * Return codes from PFnRecordAction.
 */

enum LK_ACTION {
    LKA_ABORT = 1,          /* Stop walking the table immediately */
    LKA_FAILED = 2,         /* Action failed; continue walking the table */
    LKA_SUCCEEDED = 3,      /* Action succeeded; continue walking the table */
};


/*--------------------------------------------------------------------
 * Parameter to Apply and ApplyIf.
 */

enum LK_LOCKTYPE {
    LKL_READLOCK = 1,       /* Lock the table for reading (for constness) */
    LKL_WRITELOCK = 2,      /* Lock the table for writing */
};



/* LKR_ApplyIf() and LKR_DeleteIf(): Does the record match the predicate?
 */
typedef
LK_PREDICATE
(WINAPI *LKR_PFnRecordPred) (
    const void* pvRecord,
    void* pvState);

/* LKR_Apply() et al: Perform action on record.
 */
typedef
LK_ACTION
(WINAPI *LKR_PFnRecordAction)(
    const void* pvRecord,
    void* pvState);

#endif /* LKR_APPLY_IF */



/* Initialize the global variables needed by other LKR routines.
 */
IRTL_DLLEXP
BOOL
LKR_Initialize(
    DWORD dwInitFlags);

/* Clean up the global variables needed by other LKR routines.
 */
IRTL_DLLEXP
void
LKR_Terminate();

/* Create a new LkrHashTable
 * Returns pointer to new table if successful. NULL, otherwise.
 * The table must be destroyed with LKR_DeleteTable.
 */
IRTL_DLLEXP
PLkrHashTable
LKR_CreateTable(
    LPCSTR              pszName,        /* Identify the table for debugging */
    LKR_PFnExtractKey   pfnExtractKey,  /* Extract key from record */
    LKR_PFnCalcKeyHash  pfnCalcKeyHash, /* Calculate hash signature of key */
    LKR_PFnEqualKeys    pfnEqualKeys,   /* Compare two keys */
    LKR_PFnAddRefRecord pfnAddRefRecord,/* AddRef in LKR_FindKey, etc */
    LK_TABLESIZE        nTableSize,     /* Small/Med/Large number of elements*/
    DWORD               fCreateFlags    /* Mixture of LK_CREATE_* flags. */
    );

/* Destroy an LkrHashTable created by LKR_CreateTable.
 */
IRTL_DLLEXP
void
LKR_DeleteTable(
    PLkrHashTable plkr);

/* Insert a new record into hash table.
 * Returns LKR_SUCCESS if all OK, LKR_KEY_EXISTS if same key already
 * exists (unless fOverwrite), LKR_ALLOC_FAIL if out of space,
 * or LKR_BAD_RECORD for a bad record.
 * If fOverwrite is set and a record with this key is already present,
 * it will be overwritten. If there are multiple records with this key,
 * only the first will be overwritten.
 */
IRTL_DLLEXP
LK_RETCODE
LKR_InsertRecord(
    PLkrHashTable   plkr,
    const void*     pvRecord,
    BOOL            fOverwrite);

/* Delete record with the given key from the table. Does not actually delete
 * record from memory, just calls AddRefRecord(LKAR_DELETE_KEY);
 * Returns LKR_SUCCESS if all OK, or LKR_NO_SUCH_KEY if not found
 * If fDeleteAllSame is set, all records that match pnKey will be deleted
 * from the table; otherwise, only the first matching record is deleted.
 */
IRTL_DLLEXP
LK_RETCODE
LKR_DeleteKey(
    PLkrHashTable   plkr,
    const DWORD_PTR pnKey,
    BOOL            fDeleteAllSame);

/* Delete a record from the table, if present.
 * Returns LKR_SUCCESS if all OK, or LKR_NO_SUCH_KEY if not found
 */
IRTL_DLLEXP
LK_RETCODE
LKR_DeleteRecord(
    PLkrHashTable   plkr,
    const void*     pvRecord);

/* Find record with given key.
 * Returns:  LKR_SUCCESS, if record found (record is returned in *ppvRecord)
 *           LKR_NO_SUCH_KEY, if no record with given key value was found
 *           LKR_BAD_RECORD, if ppvRecord is invalid
 *           LKR_UNUSABLE, if hash table not in usable state
 * Note: the record is AddRef'd. You must decrement the reference
 * count when you are finished with the record (if you're implementing
 * refcounting semantics).
 */
IRTL_DLLEXP
LK_RETCODE
LKR_FindKey(
    PLkrHashTable   plkr,
    const DWORD_PTR pnKey,
    const void**    ppvRecord);

/* Sees if the record is contained in the table
 * Returns:  LKR_SUCCESS, if record found
 *           LKR_NO_SUCH_KEY, if record is not in the table
 *           LKR_BAD_RECORD, if pvRecord is invalid
 *           LKR_UNUSABLE, if hash table not in usable state
 * Note: the record is *not* AddRef'd. By definition, the caller
 * already has a reference to it.
 */
IRTL_DLLEXP
LK_RETCODE
LKR_FindRecord(
    PLkrHashTable   plkr,
    const void*     pvRecord);


#ifdef LKR_APPLY_IF

/* Walk the hash table, applying pfnAction to all records.
 * Locks one subtable after another with either a (possibly
 * shared) readlock or a writelock, according to lkl.
 * Loop is aborted if pfnAction ever returns LKA_ABORT.
 * Returns the number of successful applications.
 */
IRTL_DLLEXP
DWORD
LKR_Apply(
    PLkrHashTable       plkr,
    LKR_PFnRecordAction pfnAction,
    void*               pvState,
    LK_LOCKTYPE         lkl);

/* Walk the hash table, applying pfnAction to any records that match
 * pfnPredicate. Locks one subtable after another with either
 * a (possibly shared) readlock or a writelock, according to lkl.
 * Loop is aborted if pfnAction ever returns LKA_ABORT.
 * Returns the number of successful applications.
 */
IRTL_DLLEXP
DWORD
LKR_ApplyIf(
    PLkrHashTable       plkr,
    LKR_PFnRecordPred   pfnPredicate,
    LKR_PFnRecordAction pfnAction,
    void*               pvState,
    LK_LOCKTYPE         lkl);

/* Delete any records that match pfnPredicate.
 * Locks one subtable after another with a writelock.
 * Returns the number of deletions.
 *
 * Do *not* walk the hash table by hand with an iterator and call
 * LKR_DeleteKey. The iterator will end up pointing to garbage.
 */
IRTL_DLLEXP
DWORD
LKR_DeleteIf(
    PLkrHashTable       plkr,
    LKR_PFnRecordPred   pfnPredicate,
    void*               pvState);

#endif /* LKR_APPLY_IF */


/* Check table for consistency. Returns 0 if okay, or the number of
 * errors otherwise.
 */
IRTL_DLLEXP
int
LKR_CheckTable(
    PLkrHashTable plkr);

/* Remove all data from the table
 */
IRTL_DLLEXP
void
LKR_Clear(
    PLkrHashTable plkr);

/* Number of elements in the table
 */
IRTL_DLLEXP
DWORD
LKR_Size(
    PLkrHashTable plkr);

/* Maximum possible number of elements in the table
 */
IRTL_DLLEXP
DWORD
LKR_MaxSize(
    PLkrHashTable plkr);

/* Is the hash table usable?
 */
IRTL_DLLEXP
BOOL
LKR_IsUsable(
    PLkrHashTable plkr);

/* Is the hash table consistent and correct?
 */
IRTL_DLLEXP
BOOL
LKR_IsValid(
    PLkrHashTable plkr);

#ifdef LKR_EXPOSED_TABLE_LOCK

/* Lock the table (exclusively) for writing
 */
IRTL_DLLEXP
void
LKR_WriteLock(
    PLkrHashTable plkr);

/* Lock the table (possibly shared) for reading
 */
IRTL_DLLEXP
void
LKR_ReadLock(
    PLkrHashTable plkr);

/* Unlock the table for writing
 */
IRTL_DLLEXP
void
LKR_WriteUnlock(
    PLkrHashTable plkr);

/* Unlock the table for reading
 */
IRTL_DLLEXP
void
LKR_ReadUnlock(
    PLkrHashTable plkr);

/* Is the table already locked for writing?
 */
IRTL_DLLEXP
BOOL
LKR_IsWriteLocked(
    PLkrHashTable plkr);

/* Is the table already locked for reading?
 */
IRTL_DLLEXP
BOOL
LKR_IsReadLocked(
    PLkrHashTable plkr);

/* Is the table unlocked for writing?
 */
IRTL_DLLEXP
BOOL
LKR_IsWriteUnlocked(
    PLkrHashTable plkr);

/* Is the table unlocked for reading?
 */
IRTL_DLLEXP
BOOL
LKR_IsReadUnlocked(
    PLkrHashTable plkr);

/* Convert the read lock to a write lock. Note: another thread may acquire
 * exclusive access to the table before this routine returns.
 */
IRTL_DLLEXP
void
LKR_ConvertSharedToExclusive(
    PLkrHashTable plkr);

/* Convert the write lock to a read lock
 */
IRTL_DLLEXP
void
LKR_ConvertExclusiveToShared(
    PLkrHashTable plkr);

#endif /* LKR_EXPOSED_TABLE_LOCK */


#ifdef __cplusplus
} // extern "C"



// Only provide iterators in the C++ interface. It's too hard to
// provide the correct ownership semantics in a typesafe way in C,
// and C users can always use the LKR_ApplyIf family of callback
// enumerators if they really need to walk the hashtable.


#ifdef LKR_STL_ITERATORS

#pragma message("STL iterators")

// needed for std::forward_iterator_tag, etc
# include <utility>

#include <irtldbg.h>

#define LKR_ITER_TRACE IRTLTRACE


class IRTL_DLLEXP LKR_Iterator
{
private:
    friend IRTL_DLLEXP LKR_Iterator LKR_Begin(PLkrHashTable plkr);
    friend IRTL_DLLEXP LKR_Iterator LKR_End(PLkrHashTable plkr);

    // private ctor
    LKR_Iterator(bool);

public:
    // default ctor
    LKR_Iterator();
    // copy ctor
    LKR_Iterator(const LKR_Iterator& rhs);
    // assignment operator
    LKR_Iterator& operator=(const LKR_Iterator& rhs);
    // dtor
    ~LKR_Iterator();

    // Increment the iterator to point to the next record, or to LKR_End()
    bool Increment();
    // Is the iterator valid?
    bool IsValid() const;

    // Returns the record that the iterator points to.
    // Must point to a valid record.
    const void* Record() const;
    // Returns the key of the record that the iterator points to.
    // Must point to a valid record.
    const DWORD_PTR Key() const;

    // Compare two iterators for equality
    bool operator==(const LKR_Iterator& rhs) const;
    // Compare two iterators for inequality
    bool operator!=(const LKR_Iterator& rhs) const;

    // pointer to implementation object
    void* pImpl;
}; // class LKR_Iterator


/* Return iterator pointing to first item in table
 */
IRTL_DLLEXP
LKR_Iterator
LKR_Begin(
    PLkrHashTable plkr);

/* Return a one-past-the-end iterator. Always empty.
 */
IRTL_DLLEXP
LKR_Iterator
LKR_End(
    PLkrHashTable plkr);

/* Insert a record
 * Returns `true' if successful; iterResult points to that record
 * Returns `false' otherwise; iterResult == End()
 */
IRTL_DLLEXP
bool
LKR_Insert(
              PLkrHashTable plkr,
    /* in */  const void*   pvRecord,
    /* out */ LKR_Iterator& riterResult,
    /* in */  bool          fOverwrite=false);

/* Erase the record pointed to by the iterator; adjust the iterator
 * to point to the next record. Returns `true' if successful.
 */
IRTL_DLLEXP
bool
LKR_Erase(
                 PLkrHashTable plkr,
    /* in,out */ LKR_Iterator& riter);

/* Erase the records in the range [riterFirst, riterLast).
 * Returns `true' if successful.
 */
IRTL_DLLEXP
bool
LKR_Erase(
           PLkrHashTable plkr,
    /*in*/ LKR_Iterator& riterFirst,
    /*in*/ LKR_Iterator& riterLast);

/* Find the (first) record that has its key == pnKey.
 * If successful, returns `true' and iterator points to (first) record.
 * If fails, returns `false' and iterator == End()
 */
IRTL_DLLEXP
bool
LKR_Find(
              PLkrHashTable plkr,
    /* in */  DWORD_PTR     pnKey,
    /* out */ LKR_Iterator& riterResult);

/* Find the range of records that have their keys == pnKey.
 * If successful, returns `true', iterFirst points to first record,
 *     and iterLast points to one-beyond-the last such record.
 * If fails, returns `false' and both iterators == End().
 * Primarily useful when fMultiKeys == TRUE
 */
IRTL_DLLEXP
bool
LKR_EqualRange(
              PLkrHashTable plkr,
    /* in */  DWORD_PTR     pnKey,
    /* out */ LKR_Iterator& riterFirst,     // inclusive
    /* out */ LKR_Iterator& riterLast);     // exclusive

#endif // LKR_STL_ITERATORS



//--------------------------------------------------------------------
// A typesafe wrapper for PLkrHashTable
//
// * _Derived must derive from TypedLkrHashTable and provide certain member
//   functions. It's needed for various downcasting operations.
// * _Record is the type of the record. PLkrHashTable will store
//   pointers to _Record, as const void*.
// * _Key is the type of the key. _Key is used directly; i.e., it is
//   not assumed to be a pointer type. PLkrHashTable assumes that
//   the key is stored in the associated record. See the comments
//   at the declaration of LKR_PFnExtractKey for more details.
//
// You may need to add the following line to your code to disable
// warning messages about truncating extremly long identifiers.
//   #pragma warning (disable : 4786)
//
// The _Derived class should look something like this:
//  class CDerived : public TypedLkrHashTable<CDerived, RecordType, KeyType>
//  {
//  public:
//      CDerived()
//          : TypedLkrHashTable<CDerived, RecordType, KeyType>("CDerived")
//      {/*other ctor actions*/}
//      static KeyType ExtractKey(const RecordType* pTest);
//      static DWORD   CalcKeyHash(const KeyType Key);
//      static bool    EqualKeys(const KeyType Key1, const KeyType Key2);
//      static void    AddRefRecord(RecordType* pRecord,LK_ADDREF_REASON lkar);
//      // optional: other functions
//  };
//
//--------------------------------------------------------------------

template <class _Derived, class _Record, class _Key>
class TypedLkrHashTable
{
public:
    // convenient aliases
    typedef _Derived        Derived;
    typedef _Record         Record;
    typedef _Key            Key;

    typedef TypedLkrHashTable<_Derived, _Record, _Key> HashTable;

#ifdef LKR_APPLY_IF
    // LKR_ApplyIf() and LKR_DeleteIf(): Does the record match the predicate?
    // Note: takes a Record*, not a const Record*. You can modify the
    // record in Pred() or Action(), if you like, but if you do, you
    // should use LKL_WRITELOCK to lock the table.
    typedef LK_PREDICATE (WINAPI *PFnRecordPred) (Record* pRec, void* pvState);

    // Apply() et al: Perform action on record.
    typedef LK_ACTION   (WINAPI *PFnRecordAction)(Record* pRec, void* pvState);
#endif // LKR_APPLY_IF

protected:
    PLkrHashTable m_plkr;

    // Wrappers for the typesafe methods exposed by the derived class

    static const DWORD_PTR WINAPI
    _ExtractKey(const void* pvRecord)
    {
        const _Record* pRec = static_cast<const _Record*>(pvRecord);
        const _Key   key = static_cast<const _Key>(_Derived::ExtractKey(pRec));
        // I would prefer to use reinterpret_cast here and in _CalcKeyHash
        // and _EqualKeys, but the stupid Win64 compiler thinks it knows
        // better than I do.
        return (const DWORD_PTR) key;
    }

    static DWORD WINAPI
    _CalcKeyHash(const DWORD_PTR pnKey)
    {
        const _Key key = (const _Key) (DWORD_PTR) pnKey;
        return _Derived::CalcKeyHash(key);
    }

    static BOOL WINAPI
    _EqualKeys(const DWORD_PTR pnKey1, const DWORD_PTR pnKey2)
    {
        const _Key key1 = (const _Key) (DWORD_PTR) pnKey1;
        const _Key key2 = (const _Key) (DWORD_PTR) pnKey2;
        return _Derived::EqualKeys(key1, key2);
    }

    static void WINAPI
    _AddRefRecord(const void* pvRecord, LK_ADDREF_REASON lkar)
    {
        _Record* pRec = static_cast<_Record*>(const_cast<void*>(pvRecord));
        _Derived::AddRefRecord(pRec, lkar);
    }


#ifdef LKR_APPLY_IF
    // Typesafe wrappers for Apply, ApplyIf, and DeleteIf.

    class CState
    {
    public:
        PFnRecordPred   m_pfnPred;
        PFnRecordAction m_pfnAction;
        void*           m_pvState;

        CState(
            PFnRecordPred   pfnPred,
            PFnRecordAction pfnAction,
            void*           pvState)
            : m_pfnPred(pfnPred), m_pfnAction(pfnAction), m_pvState(pvState)
        {}
    };

    static LK_PREDICATE WINAPI
    _Pred(const void* pvRecord, void* pvState)
    {
        _Record* pRec = static_cast<_Record*>(const_cast<void*>(pvRecord));
        CState*  pState = static_cast<CState*>(pvState);

        return (*pState->m_pfnPred)(pRec, pState->m_pvState);
    }

    static LK_ACTION WINAPI
    _Action(const void* pvRecord, void* pvState)
    {
        _Record* pRec = static_cast<_Record*>(const_cast<void*>(pvRecord));
        CState*  pState = static_cast<CState*>(pvState);

        return (*pState->m_pfnAction)(pRec, pState->m_pvState);
    }
#endif // LKR_APPLY_IF

public:
    TypedLkrHashTable(
        LPCSTR        pszName,          // An identifier for debugging
        LK_TABLESIZE  nTableSize,       // Small/Med/Large number of elements
        bool          fMultiKeys=false  // Allow multiple identical keys?
        )
        : m_plkr(NULL)
    {
        m_plkr = LKR_CreateTable(pszName, _ExtractKey, _CalcKeyHash,
                                 _EqualKeys, _AddRefRecord,
                                 nTableSize, fMultiKeys);
    }

    ~TypedLkrHashTable()
    {
        LKR_DeleteTable(m_plkr);
    }

    LK_RETCODE   InsertRecord(const _Record* pRec, bool fOverwrite=false)
    { return LKR_InsertRecord(m_plkr, pRec, fOverwrite); }

    LK_RETCODE   DeleteKey(const _Key key, bool fDeleteAllSame=false)
    {
        const void* pvKey = reinterpret_cast<const void*>((DWORD_PTR)(key));
        DWORD_PTR   pnKey = reinterpret_cast<DWORD_PTR>(pvKey);
        return LKR_DeleteKey(m_plkr, pnKey, fDeleteAllSame);
    }

    LK_RETCODE   DeleteRecord(const _Record* pRec)
    { return LKR_DeleteRecord(m_plkr, pRec);}

    // Note: returns a _Record**, not a const Record**. Note that you
    // can use a const type for the template parameter to ensure constness.
    LK_RETCODE   FindKey(const _Key key, _Record** ppRec) const
    {
        if (ppRec == NULL)
            return LK_BAD_RECORD;
        *ppRec = NULL;
        const void* pvRec = NULL;
        const void* pvKey = reinterpret_cast<const void*>((DWORD_PTR)(key));
        DWORD_PTR pnKey = reinterpret_cast<DWORD_PTR>(pvKey);
        LK_RETCODE lkrc = LKR_FindKey(m_plkr, pnKey, &pvRec);
        *ppRec = static_cast<_Record*>(const_cast<void*>(pvRec));
        return lkrc;
    }

    LK_RETCODE   FindRecord(const _Record* pRec) const
    { return LKR_FindRecord(m_plkr, pRec);}

#ifdef LKR_APPLY_IF
    DWORD        Apply(PFnRecordAction pfnAction,
                       void*           pvState=NULL,
                       LK_LOCKTYPE     lkl=LKL_READLOCK)
    {
        IRTLASSERT(pfnAction != NULL);
        if (pfnAction == NULL)
            return 0;

        CState   state(NULL, pfnAction, pvState);
        return   LKR_Apply(m_plkr, _Action, &state, lkl);
    }

    DWORD        ApplyIf(PFnRecordPred   pfnPredicate,
                         PFnRecordAction pfnAction,
                         void*           pvState=NULL,
                         LK_LOCKTYPE     lkl=LKL_READLOCK)
    {
        IRTLASSERT(pfnPredicate != NULL  &&  pfnAction != NULL);
        if (pfnPredicate == NULL  ||  pfnAction == NULL)
            return 0;

        CState   state(pfnPredicate, pfnAction, pvState);
        return   LKR_ApplyIf(m_plkr, _Pred, _Action, &state, lkl);
    }

    DWORD        DeleteIf(PFnRecordPred pfnPredicate, void* pvState=NULL)
    {
        IRTLASSERT(pfnPredicate != NULL);
        if (pfnPredicate == NULL)
            return 0;

        CState   state(pfnPredicate, NULL, pvState);
        return   LKR_DeleteIf(m_plkr, _Pred, &state);
    }
#endif // LKR_APPLY_IF

    int          CheckTable() const
    { return LKR_CheckTable(m_plkr); }

    void          Clear()
    { return LKR_Clear(m_plkr); }

    DWORD         Size() const
    { return LKR_Size(m_plkr); }

    DWORD         MaxSize() const
    { return LKR_MaxSize(m_plkr); }

    BOOL          IsUsable() const
    { return LKR_IsUsable(m_plkr); }

    BOOL          IsValid() const
    { return LKR_IsValid(m_plkr); }

#ifdef LKR_EXPOSED_TABLE_LOCK
    void          WriteLock()
    { LKR_WriteLock(m_plkr); }

    void          ReadLock() const
    { LKR_ReadLock(m_plkr); }

    void          WriteUnlock()
    { LKR_WriteUnlock(m_plkr); }

    void          ReadUnlock() const
    { LKR_ReadUnlock(m_plkr); }

    BOOL          IsWriteLocked() const
    { return LKR_IsWriteLocked(m_plkr); }

    BOOL          IsReadLocked() const
    { return LKR_IsReadLocked(m_plkr); }

    BOOL          IsWriteUnlocked() const
    { return LKR_IsWriteUnlocked(m_plkr); }

    BOOL          IsReadUnlocked() const
    { return LKR_IsReadUnlocked(m_plkr); }

    void          ConvertSharedToExclusive() const
    { LKR_ConvertSharedToExclusive(m_plkr); }

    void          ConvertExclusiveToShared() const
    { LKR_ConvertExclusiveToShared(m_plkr); }
#endif // LKR_EXPOSED_TABLE_LOCK


#ifdef LKR_STL_ITERATORS
    friend class LKR_Iterator;

    // TODO: const_iterator

public:
    class iterator
    {
        friend class TypedLkrHashTable<_Derived, _Record, _Key>;

    protected:
        LKR_Iterator            m_iter;

        iterator(
            LKR_Iterator& rhs)
            : m_iter(rhs)
        {
            LKR_ITER_TRACE(_TEXT("Typed::prot ctor, this=%p, rhs=%p\n"),
                           this, &rhs);
        }

    public:
        typedef std::forward_iterator_tag   iterator_category;
        typedef _Record                     value_type;
        typedef ptrdiff_t                   difference_type;
        typedef size_t                      size_type;
        typedef value_type&                 reference;
        typedef value_type*                 pointer;

        iterator()
            : m_iter()
        {
            LKR_ITER_TRACE(_TEXT("Typed::default ctor, this=%p\n"), this);
        }

        iterator(
            const iterator& rhs)
            : m_iter(rhs.m_iter)
        {
            LKR_ITER_TRACE(_TEXT("Typed::copy ctor, this=%p, rhs=%p\n"),
                           this, &rhs);
        }

        iterator& operator=(
            const iterator& rhs)
        {
            LKR_ITER_TRACE(_TEXT("Typed::operator=, this=%p, rhs=%p\n"),
                           this, &rhs);
            m_iter = rhs.m_iter;
            return *this;
        }

        ~iterator()
        {
            LKR_ITER_TRACE(_TEXT("Typed::dtor, this=%p\n"), this);
        }

        reference operator*() const
        {
            void* pvRecord = const_cast<void*>(m_iter.Record());
            return reinterpret_cast<reference>(pvRecord);
        }

        pointer   operator->() const  { return &(operator*()); }

        // pre-increment
        iterator& operator++()
        {
            LKR_ITER_TRACE(_TEXT("Typed::pre-increment, this=%p\n"), this);
            m_iter.Increment();
            return *this;
        }

        // post-increment
        iterator  operator++(int)
        {
            LKR_ITER_TRACE(_TEXT("Typed::post-increment, this=%p\n"), this);
            iterator iterPrev = *this;
            m_iter.Increment();
            return iterPrev;
        }

        bool operator==(
            const iterator& rhs) const
        {
            LKR_ITER_TRACE(_TEXT("Typed::operator==, this=%p, rhs=%p\n"),
                           this, &rhs);
            return m_iter == rhs.m_iter;
        }

        bool operator!=(
            const iterator& rhs) const
        {
            LKR_ITER_TRACE(_TEXT("Typed::operator!=, this=%p, rhs=%p\n"),
                           this, &rhs);
            return m_iter != rhs.m_iter;
        }

        _Record*  Record() const
        {
            LKR_ITER_TRACE(_TEXT("Typed::Record, this=%p\n"), this);
            return reinterpret_cast<_Record*>(
                        const_cast<void*>(m_iter.Record()));
        }

        _Key      Key() const
        {
            LKR_ITER_TRACE(_TEXT("Typed::Key, this=%p\n"), this);
            return reinterpret_cast<_Key>(
                        reinterpret_cast<void*>(m_iter.Key()));
        }
    }; // class iterator

    // Return iterator pointing to first item in table
    iterator begin()
    {
        LKR_ITER_TRACE(_TEXT("Typed::begin()\n"));
        return LKR_Begin(m_plkr);
    }

    // Return a one-past-the-end iterator. Always empty.
    iterator end() const
    {
        LKR_ITER_TRACE(_TEXT("Typed::end()\n"));
        return LKR_End(m_plkr);
    }

    template <class _InputIterator>
    TypedLkrHashTable(
        LPCSTR pszName,             // An identifier for debugging
        _InputIterator f,           // first element in range
        _InputIterator l,           // one-beyond-last element
        LK_TABLESIZE  nTableSize,   // Small/Med/Large number of elements
        bool   fMultiKeys=false     // Allow multiple identical keys?
        )
    {
        m_plkr = LKR_CreateTable(pszName, _ExtractKey, _CalcKeyHash,
                                 _EqualKeys, _AddRefRecord,
                                 nTableSize, fMultiKeys);
        insert(f, l);
    }

    template <class _InputIterator>
    void insert(_InputIterator f, _InputIterator l)
    {
        for ( ;  f != l;  ++f)
            InsertRecord(&(*f));
    }

    bool
    Insert(
        const _Record* pRecord,
        iterator& riterResult,
        bool fOverwrite=false)
    {
        LKR_ITER_TRACE(_TEXT("Typed::Insert\n"));
        return LKR_Insert(m_plkr, pRecord, riterResult.m_iter, fOverwrite);
    }

    bool
    Erase(
        iterator& riter)
    {
        LKR_ITER_TRACE(_TEXT("Typed::Erase\n"));
        return LKR_Erase(m_plkr, riter.m_iter);
    }

    bool
    Erase(
        iterator& riterFirst,
        iterator& riterLast)
    {
        LKR_ITER_TRACE(_TEXT("Typed::Erase2\n"));
        return LKR_Erase(m_plkr, riterFirst.m_iter, riterLast.m_iter);
    }

    bool
    Find(
        const _Key key,
        iterator& riterResult)
    {
        LKR_ITER_TRACE(_TEXT("Typed::Find\n"));
        const void* pvKey = reinterpret_cast<const void*>((DWORD_PTR)(key));
        DWORD_PTR   pnKey = reinterpret_cast<DWORD_PTR>(pvKey);
        return LKR_Find(m_plkr, pnKey, riterResult.m_iter);
    }

    bool
    EqualRange(
        const _Key key,
        iterator& riterFirst,
        iterator& riterLast)
    {
        LKR_ITER_TRACE(_TEXT("Typed::EqualRange\n"));
        const void* pvKey = reinterpret_cast<const void*>((DWORD_PTR)(key));
        DWORD_PTR   pnKey = reinterpret_cast<DWORD_PTR>(pvKey);
        return LKR_EqualRange(m_plkr, pnKey, riterFirst.m_iter,
                              riterLast.m_iter);
    }

#undef LKR_ITER_TRACE

#endif // LKR_STL_ITERATORS

}; // class TypedLkrHashTable

#endif /* __cplusplus */

#endif /* __LKR_HASH_H__ */