1. Motivation and Scope
[P0920R2] extends the interface of unordered containers with member function overloads that have one additional argument taking a precalculated hash value for the value being queried.std :: array < std :: unordered_map < std :: string , int > , array_size > maps ; void update ( const std :: string & user ) { const auto hash = maps . front (). hash_function ()( user ); for ( auto & m : maps ) { auto it = m . find ( user , hash ); // ... } } 
This approach has a number of problems, including the lack of safety around hasher misuse and the lack of clarity around sharing hash values across multiple containers.
1.1. Hasher misuse
Instead of obtaining the hasher instance viamap . hash_function () Firstly, this is plain incorrect if the hasher for the container is stateful. Secondly, even if the user used a different hasher instance of the correct hasher type and the hasher is not stateful, it increases the chance of mismatch between the hash value and the key, especially when the hasher of the container gets changed. It would require auditing and potentially changing all call sites of this API.
1.2. Unclear semantics of sharing hash values across containers
The motivating example in §1 Motivation and Scope assumes the hasher typestd :: hash < std :: string > unordered_map 1.3. Use of raw hash values limits optimization opportunities
Some hash table implementations may choose to further process the hash values obtained fromHash std :: hash In addition, in a few use cases, sizable performance gains came mainly from prefetching the pertinent cache lines, as opposed to merely precalculating the hash value. This optimization is not possible with the API [P0920R2] proposed.
1.4. Extensibility considerations
In the future, we may wish to extend the mutation methods (emplace try_emplace insert mapped_type size_t try_emplace ( const key_type & k , size_t hash , Args && ... args ); try_emplace ( const key_type & k , Args && ... args ); 
For 
2. Design alternatives and respective concerns
2.1. Encapsulate hash values with hash_token 
    Some of the above issues can be addressed by encapsulating the raw hash value in
a hash_token token () hash_token 2.1.1. Possible mismatch between hash_token 
    The intention of lookup using precalculated hash is to improve performance in
specific use cases which need it. These use cases can presumably be trusted to
provide a matching hash_token hash_token end () Conceivably, the hash table implementation could check whether the supplied 
2.1.2. Whether to store reference / copy of key in hash_token
Storing a reference or a copy of the key in thehash_token hash_token 2.1.3. Sharing hash_token 
    This approach does not provide a satisfying solution to specifying sharing
semantics across different containers. 
   2.2. “Caching” hash values in keys
Another approach for using precalculating hash is to “cache” hash values alongsidekey_type struct KeyWithHash { std :: string key_ ; size_t hash_ ; }; std :: unordered_map < std :: string , int , MyHash , MyEqualTo > m ; m . emplace ( “foo ”, 42 ); KeyWithHash kh ; kh . key_ = "foo" ; kh . hash_ = m . hash_function ()( kh . key_ ); // precalculated m . find ( kh ); // heterogeneous lookup 
Note that this approach does not need the committee need to adopt any changes to the standard. This is an option available to users with or without [P0920R2]. This could also be a mitigation that would buy us time to explore the design space further.
3. Proposed Wording
3.1. Option 1: Revert [P0920R2]
Given the issues detailed above, as well as the lack of an obviously clean design or agreeable approach to use precalculated hash values for lookups, we recommend reverting the changes in [P0920R2], as the first option. This allows the LEWG to come to a safer and better-explored design in the near future.3.2. Option 2: Replace size_t 
    As a second option, we recommend using an opaque hash token to encapsulate the
generation and usage of precalculated hash, with the following wording changes.
This is a relatively small forward-fix to [P0920R2]. 
   The proposed changes are relative to the working draft of the standard as of [N4810].
Modify 22.2.7 [unord.req] paragraph 11.23 as follows:
andhk denote values of typehke size_t .X :: hash_token_type 
Add the following paragraph to 22.2.7 [unord.req]:
Member function templates,find ,count , andequal_range that take acontains shall not participate in overload resolution unlesshash_token_type is true.std :: is_empty_v < Hash > 
Modify Table 70 in 22.2.7 [unord.req] as follows:
Expression Return type Assertion/note pre-/post-condition Complexity X :: hash_token_type An implementation-defined type that can be used to look up its associated key. Expects: meets the Cpp17DefaultConstructible requirements.hash_token_type compile time b . token ( k ) X :: hash_token_type Returns: returns a value of typehk such thatX :: hash_token_type equalsb . find ( hk , k ) b . find ( k ) constant a_tran . token ( ke ) X :: hash_token_type Returns: returns a value of typehke such thatX :: hash_token_type equalsa_tran . find ( hke , ke ) a_tran . find ( ke ) constant b . find ( k , hk ) b . find ( hk , k ) ;iterator for constconst_iterator .b Expects: equalsb . hash_function ()( k ) ,hk equalsb . token ( k ) ,hk 
Returns: an iterator pointing to an element with key equivalent to, ork if no such element exists.b . end () Average case O(1), worst case O( ).b . size () a_tran . find ( ke , hke ) a_tran . find ( hke , ke ) ;iterator for constconst_iterator .a_tran Expects: equalsa_tran . hash_function ()( ke ) ,hke equalsa_tran . token ( ke ) ,hke 
Returns: an iterator pointing to an element with key equivalent to, orke if no such element exists.a_tran . end () Average case O(1), worst case O( ).a_tran . size () b . count ( k , hk ) b . count ( hk , k ) size_type Expects: equalsb . hash_function ()( k ) ,hk equalsb . token ( k ) ,hk 
Returns: the number of elements with key equivalent to.k Average case O( ), worst case O(b . count ( k ) ).b . size () a_tran . count ( ke , hke ) a_tran . count ( hke , ke ) size_type Expects: equalsa_tran . hash_function ()( ke ) ,hke equalsa_tran . token ( ke ) ,hke 
Returns: the number of elements with key equivalent to.ke Average case O( ), worst case O(a_tran . count ( ke ) ).a_tran . size () b . contains ( k , hk ) b . contains ( hk , k ) bool Expects: equalsb . hash_function ()( k ) ,hk equalsb . token ( k ) ,hk 
Effects: Equivalent tob . find ( k , hk ) != b . end () Average case O(1), worst case O( )b . size () a_tran . contains ( ke , hke ) a_tran . contains ( hke , ke ) bool Expects: equalsa_tran . hash_function ()( ke ) ,hke equalsa_tran . token ( ke ) ,hke 
Effects: Equivalent toa_tran . find ( ke , hke ) != a_tran . end () Average case O(1), worst case O( )a_tran . size () b . equal_range ( k , hk ) b . equal_range ( hk , k ) ;pair < iterator , iterator > for constpair < const_iterator , const_iterator > .b Expects: equalsb . hash_function ()( k ) ,hk equalsb . token ( k ) ,hk 
Returns: a range containing all elements with keys equivalent to. Returnsk if no such elements exist.make_pair ( b . end (), b . end ()) Average case O( ), worst case O(b . count ( k ) ).b . size () a_tran . equal_range ( ke , hke ) a_tran . equal_range ( hke , ke ) ;pair < iterator , iterator > for constpair < const_iterator , const_iterator > .a_tran Expects: equalsa_tran . hash_function ()( ke ) ,hke equalsa_tran . token ( ke ) ,hke 
Returns: a range containing all elements with keys equivalent to. Returnske if no such elements exist.make_pair ( a_tran . end (), a_tran . end ()) Average case O( ), worst case O(a_tran . count ( ke ) ).a_tran . size () 
Make the following changes to:
- 
     22.5.4.1 [unord.map.overview] 
- 
     22.5.5.1 [unord.multimap.overview] 
- 
     22.5.6.1 [unord.set.overview] 
- 
     22.5.7.1 [unord.multiset.overview] 
using const_local_iterator = implementation - defined ; // see 22.2 using node_type = unspecified ; using insert_return_type = insert - return - type < iterator , node_type > ; using hash_token_type = implementation - defined ; // see 22.2 ... hash_token_type token ( const key_type & k ); template < class K > hash_token_type token ( const K & k ); iterator find ( const key_type & k ); const_iterator find ( const key_type & k ) const ; iterator find ( const hash_token_type & hash_token , const key_type & k , size_t hash ); const_iterator find ( const hash_token_type & hash_token , const key_type & k , size_t hash ) const ; template < class K > iterator find ( const K & k ); template < class K > const_iterator find ( const K & k ) const ; template < class K > iterator find ( const hash_token_type & hash_token , const K & k , size_t hash ); template < class K > const_iterator find ( const hash_token_type & hash_token , const K & k , size_t hash ) const ; size_type count ( const key_type & k ) const ; size_type count ( const hash_token_type & hash_token , const key_type & k , size_t hash ) const ; template < class K > size_type count ( const K & k ) const ; template < class K > size_type count ( const hash_token_type & hash_token , const K & k , size_t hash ) const ; bool contains ( const key_type & k ) const ; bool contains ( const hash_token_type & hash_token , const key_type & k , size_t hash ) const ; template < class K > bool contains ( const K & k ) const ; template < class K > bool contains ( const hash_token_type & hash_token , const K & k , size_t hash ) const ; pair < iterator , iterator > equal_range ( const key_type & k ); pair < const_iterator , const_iterator > equal_range ( const key_type & k ) const ; pair < iterator , iterator > equal_range ( const hash_token_type & hash_token , const key_type & k , size_t hash ); pair < const_iterator , const_iterator > equal_range ( const hash_token_type & hash_token , const key_type & k , size_t hash ) const ; template < class K > pair < iterator , iterator > equal_range ( const K & k ); template < class K > pair < const_iterator , const_iterator > equal_range ( const K & k ) const ; template < class K > pair < iterator , iterator > equal_range ( const hash_token_type & hash_token , const K & k , size_t hash ); template < class K > pair < const_iterator , const_iterator > equal_range ( const hash_token_type & hash_token , const K & k , size_t hash ) const ; 
4. Implementation and Usage Experiences
Google’s widely used hash table implementation, [SwissTables], provides the exact same API as [P0929R0]. There are fewer than a dozen or so uses of the precalculated hash infind () Facebook’s [F14] hash tables used an opaque hash token approach. In our experiences, such an approach allowed several use cases to precalculate hash and prefetch the relevant cache lines, resulting in CPU and memory efficiency improvement.