1. Motivation
[N3657] and [P0919R0] introduced heterogeneous lookup support for ordered and unordered
associative containers in C++ Standard Library. [P2077R2] proposed heterogeneous erasure overloads.
As a result, a temporary key object is not created when a different (but comparable) type is provided as a key to the member function.
But there are still no heterogeneous overloads for methods such as 
Consider the following example:
void foo ( std :: map < std :: string , int , std :: less < void >>& m ) { const char * lookup_str = "Lookup_str" ; auto it = m . find ( lookup_str ); // matches to template overload // ... auto result = m . insert_or_assign ( lookup_str , 1 ); // no heterogeneous alternative } 
Function 
In the example above, the 
All the proposed APIs in this paper allow to avoid mentioned performance penalty.
2. Prior work
Heterogeneous lookup overloads for ordered and unordered associative containers were added into C++ standard. For example:
template < typename K > iterator find ( const K & x ); 
The corresponding overloads were added for 
[P2077R2] proposed heterogeneous erasure overloads for ordered and unordered associative containers:
template < typename K > size_type erase ( K && x ); template < typename K > node_type extract ( K && x ); 
Constraints:
- 
     qualified-id Compare :: is_transparent 
- 
     qualified-ids Hash :: is_transparent Pred :: is_transparent 
where 
3. Proposal overview
We propose to add heterogeneous overloads for the following member functions:
- 
     insert std :: set std :: unordered_set 
- 
     insert_or_assign try_emplace operator [] at std :: map std :: unordered_map 
- 
     bucket std :: unordered_set std :: unordered_map std :: unordered_multiset std :: unordered_multimap 
3.1. try_emplace 
   We propose the following API (
template < typename K , typename ... Args > std :: pair < iterator , bool > try_emplace ( K && k , Args && ... args ); template < typename K , typename ... Args > iterator try_emplace ( const_iterator hint , K && k , Args && ... args ); 
Effects: If the map already contains an element whose key compares equivalent with 
Constraints:
- 
     For ordered associative containers: qualified-id Compare :: is_transparent Hash :: is_transparent Pred :: is_transparent 
- 
     std :: is_convertible_v < K && , const_iterator > std :: is_convertible_v < K && , iterator > false.
Constraint 2 is introduced to maintain backward compatibility and makes homogeneous overload to be called when 
3.2. insert_or_assign 
   We propose the following API (
template < typename K , typename M > std :: pair < iterator , bool > insert_or_assign ( K && k , M && obj ); template < typename K , typename M > iterator insert_or_assign ( const_iterator hint , K && k , M && obj ); 
Effects: If the map already contains an element 
Constraints:
- 
     For ordered associative containers: qualified-id Compare :: is_transparent Hash :: is_transparent Pred :: is_transparent 
Backward compatibility problem in case of 
3.3. operator [] 
   We propose the following API (
template < typename K > mapped_type & operator []( K && k ); 
Effects: Equivalent to: 
Constraints:
- 
     For ordered associative containers: qualified-id Compare :: is_transparent Hash :: is_transparent Pred :: is_transparent 
3.4. at 
   We propose the following API (
template < typename K > mapped_type & at ( const K & k ); template < typename K > const mapped_type & at ( const K & k ) const ; 
Returns: A reference to the 
Throws: An exception object of type 
Constraints:
- 
     For ordered associative containers: qualified-id Compare :: is_transparent Hash :: is_transparent Pred :: is_transparent 
3.5. bucket 
   We propose the following API (
template < typename K > size_type bucket ( const K & k ) const ; 
Returns: The index of the bucket in which elements with keys compared equivalent with 
Constraints:
- 
     For ordered associative containers: qualified-id Compare :: is_transparent Hash :: is_transparent Pred :: is_transparent 
3.6. insert 
   We considered to add heterogeneous overloads of 
template < typename K > std :: pair < iterator , bool > insert ( K && k ); template < typename K > iterator insert ( const_iterator hint , K && k ); 
Effects: If the set already contains an element which compares equivalent with 
Constraints:
- 
     For ordered associative containers: qualified-id Compare :: is_transparent Hash :: is_transparent Pred :: is_transparent 
- 
     std :: is_convertible_v < K && , const_iterator > std :: is_convertible_v < K && , iterator > false.
Adding heterogeneous 
For 
3.7. Design decisions
3.7.1. Constructibility constraints
We do not introduce the following constraint: 
The only use-case when the homogeneous overload of the corresponding member function should be selected is the case when 
The condition 
3.7.2. Insertion of implicitly convertible types
The proposed heterogeneous overloads for the insertion (such as 
Consider the following example:
std :: set < std :: shared_ptr < int > , std :: owner_less <>> s ; std :: weak_ptr < int > w = /*...*/ ; s . insert ( w ); 
The constructor of 
We investigated adding constraints for heterogeneous overloads of the insertion methods so that they only participate in overload resolution if 
std :: set < std :: string , TransparentCharCompare > s ; std :: string_view sv = /*...*/ ; s . insert ( sv ); 
The constructor of 
Moreover, we found that the library already contains such precedences.
First, we can compare the behavior of 
std :: shared_ptr < int > sptr = /*...*/ ; std :: weak_ptr < int > wptr = sptr ; std :: vector < std :: shared_ptr < int >> v ; v . push_back ( sptr ); // OK v . push_back ( wptr ); // Fail due to implicit conversion v . emplace_back ( wptr ); // OK, insertion via explicit conversion 
Another example is one of the overloads of 
template < class P > std :: pair < iterator , bool > insert ( P && x ); 
This overload is equivalent to 
std :: shared_ptr < int > sptr ; std :: weak_ptr < int > wptr ; std :: pair < std :: shared_ptr < int > , int > p1 = std :: make_pair ( sptr , 1 ); // OK std :: pair < std :: shared_ptr < int > , int > p2 = std :: make_pair ( wptr , 1 ); // Fail std :: map < std :: shared_ptr < int > , int , std :: owner_less <>> m ; m . insert ( std :: make_pair ( sptr , 1 )); // OK m . insert ( std :: make_pair ( wptr , 1 )); // Also OK, equivalent to emplace 
Based on the described scenarios we came to the following conclusions:
- 
     Do not introduce additional constraints to allow heterogeneous instertion for such types like std :: string_view std :: string 
- 
     The implicit insertion possiblity of explicitly convertible types(as for weak_ptr shared_ptr 
3.7.3. Conversion and comparison consistency
The conversion from the heterogeneous key to 
Consider the following example where inconsistency leads to unexpected result:
struct HeterogeneousKey { int value ; operator int () const { return - value ; } }; struct Compare { using is_transparent = void ; bool operator ()( int lhs , int rhs ) const { return lhs < rhs ; } bool operator ()( int lhs , HeterogeneousKey rhs ) const { return lhs < rhs . value ; } bool operator ()( HeterogeneousKey lhs , int rhs ) const { return lhs . value < rhs ; } }; int main () { std :: set < int , Compare > s = { 1 , -2 }; s . insert ( HeterogeneousKey { 2 }); } 
In this case, 
Based on that, we need a precondition for heterogeneous insertion methods that the conversion from the heterogeneous key into 
The example above also could result in breaking change. Currently the conversion from 
But we believe that the most common use-case for both heterogeneous lookup and insertion is the case when 
3.7.3.1. Inconsistency of comparison and conversion today
We would like to highlight weird behavior when heterogeneous comparison and conversion are inconsistent even without this proposal being accepted. Consider the following example:
struct HalfIs { int value ; operator int () const { return value * 2 ; } }; struct Compare { using is_transparent = void ; bool operator ()( int x , int y ) const { return x < y ; } bool operator ()( int x , HalfIs y ) const { return x < y / 2 ; } bool operator ()( HalfIs x , int y ) const { return x / 2 < y ; } }; int main () { std :: set < int , Compare > s = { 1 , 2 , 3 , 5 , 6 }; if ( s . contains ( HalfIs { 2 })) { bool result = s . insert ( HalfIs { 2 }). second ; assert ( result == false); // Fails assert ( s . size () == 5 ); // Fails } } 
In the example above heterogeneous lookup could potentially find two values (because of dividing by 2 in heterogeneous comparator overload)
while true because the element 
We think that such a behavior is questionable - why are we able to insert the element into the set if it already contains the equivalent one?
3.7.4. Use-cases with multiple matches
The design of heterogeneous lookup and erasure allows the unique-key associative containers to hold unique elements in terms of homogeneous comparisons, but potentially non-unique with heterogeneous comparisons:
struct Employee { // First component of the pair is the lastname, // the second is the firstname std :: pair < std :: string , std :: string > fullname ; Employee ( const std :: string & firstname , const std :: string & lastname ) : fullname ( lastname , firstname ) {} std :: string firstname () const { return fullname . second ; } std :: string lastname () const { return fullname . first ; } }; struct Compare { using is_transparent = void ; // Homogeneous comparison - compares both firstname and lastname bool operator ()( const Employee & lhs , const Employee & rhs ) const { return lhs . fullname < rhs . fullname ; } // Heterogeneous comparisons - compare lastname and Employees lastname bool operator ()( const Employee & lhs , const std :: string & rhs ) const { return lhs . lastname () < rhs ; } // Reversed heterogeneous comparison bool operator ()( const std :: string & lhs , const Employee & rhs ) const { return lhs < rhs . lastname (); } }; int main () { std :: set < Employee , Compare > s = {{ "John" , "Smith" }, { "Oliver" , "Twist" }, { "William" , "Smith" }}; // Below std::distance(r.first, r.second) == 2 auto r = s . equal_range ( "Smith" ); } 
This code creates a 
The reasonable question here is how should the heterogeneous insertion methods behave if multiple match occurred? In particular, 
In general case we cannot not imagine how to write consistent conversion operator with the heterogeneous comparator that produces
multiple match. If this scenario is possible then the answers could be that the returned iterator from the insertion should be defined
in a consistent way with heterogeneous 
Below we provide the ruminations why constistent converstion operator for heterogeneous key in the use-case with multiple match for unique-key containers is hardly possible.
To use heterogeneous insertion for the example above, we need to define the conversion from 
struct Employee { // ... Employee ( const std :: string & lname ); // ... }; 
It’s unclear how this constructor with one string (e.g., representing last name) would restore both first and last names. Thus, such construction does not seem meaningful. We think that for the use-case similar to above, such a conversion is considered as an attepts to restore the element using one of its properties with no attention to other properties. Similar examples are restoring the information about the car knowing that its color is red or restoring the integer using its hashcode.
One can argue that such a conversion can use a single string representing both first and last names, e.g. 
To conclude. We believe that there are three possible scenarios on the user side:
- 
     There is heterogeneous comparison that could result in multiple match and there is no converion from heterogeneous key to key_type 
- 
     There is heterogeneous comparison and a converion from heterogeneous key to key_type 
- 
     There is heterogeneous comparison and a converion from heterogeneous key to key_type 
3.7.5. at 
   Despite that 
In case of multiple match this method should return the reference to the same element
as heterogeneous 
3.7.6. Convertibility constraints for insert 
   For 
std :: set < int > s1 = { ... }; std :: set < int > s2 = { ... }; // Insert elements from s2 to s1 s1 . insert ( s2 . cbegin (), s2 . cend ()); 
Call to insert with two 
template < typename InputIt > void insert ( InputIt first , InputIt last ); // Existing overload (1) 
and
template < typename K > iterator insert ( const_iterator hint , K && x ); // Proposed overload (2) 
To resolve the problem described above we introduce 
The behavior remains untouсhed when arguments are 
3.8. Proposed API summary
The proposed heterogeneous overloads for various methos are summarized in the table below:
Member function std::set std::multiset std::map std::multimap std::unordered_set std::unordered_multiset std::unordered_map std::unordered_multimap try_emplace Not available Not available K&& Not available Not available Not available K&& Not available insert_or_assign Not available Not available K&& Not available Not available Not available K&& Not available operator[] Not available Not available K&& Not available Not available Not available K&& Not available at Not available Not available const K& Not available Not available Not available const K& Not available bucket Not available Not available Not available Not available const K& const K& const K& const K& insert K&& Not applicable Not applicable Not applicable K&& Not applicable Not applicable Not applicable 
3.9. Performance evaluation
Our case study with the open-source pmemkv project confirms the importance of the case with 
pmemkv is an embedded key/value data storage for persistent memory that provides several
storage engines optimized for different use cases.
We analyzed 
using storage_type = std :: basic_string < char , std :: char_traits < char > , libmemkind :: pmem :: allocator < char >> ; using key_type = storage_type ; using mapped_type = storage_type ; using map_allocator_type = libmemkind :: pmem :: allocator < std :: pair < const key_type , mapped_type >> ; using map_type = std :: map < key_type , mapped_type , std :: less < void > , std :: scoped_allocator_adaptor < map_allocator_type >> ; 
Initial implementation of the 
status vsmap::put ( std :: string_view key , std :: string_view value ) { auto res = pmem_kv_container . try_emplace ( key_type ( key , kv_allocator ), value ); if ( ! res . second ) { auto it = res . first ; it -> second . assign ( value . data (), value . size ()); } return status :: OK ; } 
It explicitly creates a temporary object of 
For performance evaluation, we extended LLVM Standard Library with the heterogeneous overload for the 
status vsmap::put ( std :: string_view key , std :: string_view value ) { auto res = pmem_kv_container . try_emplace ( key , value ); if ( ! res . second ) { auto it = res . first ; it -> second . assign ( value . data (), value . size ()); } return status :: OK ; } 
For benchmarking we used the pmemkv utility and run the fillrandom benchmark on a prefilled storage
(it means that the 
The chart below shows performance increases relative to the initial implementation:
4. Formal wording
Below, substitute the � character with a number the editor finds appropriate for the table, paragraph, section or sub-section.
4.1. Modify class template map synopsis [map.overview]
[...]// [map.access], element access mapped_type & operator []( const key_type & x ); mapped_type & operator []( key_type && x ); template < class K > mapped_type & operator []( K && x ); mapped_type & at ( const key_type & x ); const mapped_type & at ( const key_type & x ) const ; template < class K > mapped_type & at ( const K & x ); template < class K > const mapped_type & at ( const K & x ) const ; [...]
template < class ... Args > pair < iterator , bool > try_emplace ( const key_type & k , Args && ... args ); template < class ... Args > pair < iterator , bool > try_emplace ( key_type && k , Args && .. args ); template < class K , class ... Args > pair < iterator , bool > try_emplace ( K && k , Args && ... args ); template < class ... Args > iterator try_emplace ( const_iterator hint , const key_type & k , Args && ... args ); template < class ... Args > iterator try_emplace ( const_iterator hint , key_type && k , Args && ... args ); template < class K , class ... Args > iterator try_emplace ( const_iterator hint , K && k , Args && ... args ); template < class M > pair < iterator , bool > insert_or_assign ( const key_type & k , M && obj ); template < class M > pair < iterator , bool > insert_or_assign ( key_type && k , M && obj ); template < class K , class M > pair < iterator , bool > insert_or_assign ( K && k , M && obj ); template < class M > iterator insert_or_assign ( const_iterator hint , const key_type & k , M && obj ); template < class M > iterator insert_or_assign ( const_iterator hint , key_type && k , M && obj ); template < class K , class M > iterator insert_or_assign ( const_iterator hint , K && k , M && obj ); 
4.2. Modify [map.access]
[...]mapped_type & operator []( key_type && x ); �Effects : Equivalent to : return try_emplace ( move ( x )). first -> second ; template < class K > mapped_type & operator []( K && x ); �Constraints : Qualified - id Compare :: is_transparent is valid and denotes a type . �Effects : Equivalent to : return try_emplace ( forward < K > ( x )). first -> second ; mapped_type & at ( const key_type & x ); const mapped_type & at ( const key_type & x ) const ; [...] �Complexity : Logarithmic . template < class K > mapped_type & at ( const K & x ); template < class K > const mapped_type & at ( const K & x ) const ; �Constraints : Qualified - id Compare :: is_transparent is valid and denotes a type . �Preconditions : The expression find ( x ) is well - formed and has well - defined behavior . �Returns : A reference to find ( x ) -> second . �Throws : An exception object of type out_of_range if find ( x ) == end () is true. �Complexity : Logarithmic . 
4.3. Modify [map.modifiers]
template < class ... Args > pair < iterator , bool > try_emplace ( key_type && k , Args && ... args ); template < class ... Args > iterator try_emplace ( const_iterator hint , key_type && k , Args && ... args ); [...] �Complexity : The same as emplace and emplace_hint , respectively . template < class K , class ... Args > pair < iterator , bool > try_emplace ( K && k , Args && ... args ); template < class K , class ... Args > iterator try_emplace ( const_iterator hint , K && k , Args && ... args ); �Constraints : Qualified - id Compare :: is_transparent is valid and denotes a type . For the first overload , is_convertible_v < K && , const_iterator > and is_convertible_v < K && , iterator > are both false. �Preconditions : value_type is Cpp17EmplaceConstructible into map from piecewise_construct , forward_as_tuple ( forward < K > ( k )), forward_as_tuple ( forward < Args > ( args )...) . The conversion from k into key_type constructs an object u , for which find ( k ) == find ( u ) is true. �Effects : If the map already contains an element whose key is equivalent to k , there is no effect . Otherwise inserts an object of type value_type constructed with piecewise_construct , forward_as_tuple ( std :: forward < K > ( k )), forward_as_tuple ( std :: forward < Args > ( args )...) . �Returns : In the first overload , the bool component of the returned pair is trueif and only if the insertion took place . The returned iterator points to the map element whose key is equivalent to k . �Complexity : The same as emplace and emplace_hint , respectively . [...] template < class M > pair < iterator , bool > insert_or_assign ( key_type && k , M && obj ); template < class M > iterator insert_or_assign ( const_iterator hint , key_type && k , M && obj ); [...] �Complexity : The same as emplace and emplace_hint , respectively . template < class K , class M > pair < iterator , bool > insert_or_assign ( K && k , M && obj ); template < class K , class M > iterator insert_or_assign ( const_iterator hint , K && k , M && obj ); �Constraints : Qualified - id Compare :: is_transparent is valid and denotes a type . �Mandates : is_assignable_v < mapped_type & , M &&> is true. �Preconditions : value_type is Cpp17EmplaceConstructible into map from forward < K > ( k ), forward < M > ( obj ) . The conversion from k into key_type constructs an object u , for which find ( k ) == find ( u ) is true. �Effects : If the map already contains an element e whose key is equivalent to k , assigns std :: forward < M > ( obj ) to e . second . Otherwise inserts an object of type value_type constructed with std :: forward < K > ( k ), std :: forward < M > ( obj ) . �Returns : In the first overload , the bool component of the returned pair is trueif and only if the insertion took place . The returned iterator points to the map element whose key is equivalent to k . �Complexity : The same as emplace and emplace_hint , respectively . 
4.4. Modify class template set synopsis [set.overview]
[...]pair < iterator , bool > insert ( const value_type & x ); pair < iterator , bool > insert ( value_type && x ); template < class K > pair < iterator , bool > insert ( K && x ); iterator insert ( const_iterator hint , const value_type & x ); iterator insert ( const_iterator hint , value_type && x ); template < class K > iterator insert ( const_iterator hint , K && x ); 
4.5. Add paragraph Modifiers into [set.overview]
�Erasure [ set . erasure ] [...]
�Modifiers [ set . modifiers ] template < class K > pair < iterator , bool > insert ( K && x ); template < class K > iterator insert ( const_iterator hint , K && x ); �Constraints : Qualified - id Compare :: is_transparent is valid and denotes a type , is_convertible_v < K && , const_iterator > and is_convertible_v < K && , iterator > are both false. �Preconditions : value_type is Cpp17EmplaceConstructible into set from x . The conversion from x into key_type constructs an object u , for which find ( x ) == find ( u ) is true. �Effects : Inserts a value_type object t constructed with std :: forward < K > ( x ) if and only if there is no element in the container that is equivalent to t . �Returns : In the first overload , the bool component of the returned pair is trueif and only if the insertion took place . The returned iterator points to the set element that is equivalent to x . �Complexity : Logarithmic . 
4.6. Modify [tab.container.hash.req]
Table �: Unordered associative container requirements (in addition to container) [tab:container.hash.req]
Expression Return type Assertion/ note 
pre- / post-conditionComplexity [...] b.bucket(k) size_type Preconditions: b.bucket_- 
count() > 0.
Returns: The index of the
bucket in which elements
with keys equivalent to k
would be found, if any such
element existed.
Postconditions: The return
value shall be in the range
[0, b.bucket_count()).Constant a_tran.bucket(ke) size_type Preconditions: a_tran.bucket- 
count() > 0.
Returns: The index of the
bucket in which elements
with keys equivalent to ke
would be found, if any such
element existed.
Postconditions: The return
value shall be in the range
[0, a_tran.bucket_count()).Constant [...] 
4.7. Modify paragraph � in [unord.req.general]
[...]The member function templates find, count, equal_range,andcontains and bucket shall not participate in overload resolution unless the qualified-ids Pred::is_transparent and Hash::is_transparent are both valid and denote types (�).
4.8. Modify class template unordered_map synopsis [unord.map.overview]
[...]template < class ... Args > pair < iterator , bool > try_emplace ( const key_type & k , Args && ... args ); template < class ... Args > pair < iterator , bool > try_emplace ( key_type && k , Args && ... args ); template < class K , class ... Args > pair < iterator , bool > try_emplace ( K && k , Args && ... args ); template < class ... Args > iterator try_emplace ( const_iterator hint , const key_type & k , Args && ... args ); template < class ... Args > iterator try_emplace ( const_iterator hint , key_type && k , Args && ... args ); template < class K , class ... Args > iterator try_emplace ( const_iterator hint , K && k , Args && ... args ); template < class M > pair < iterator , bool > insert_or_assign ( const key_type & k , M && obj ); template < class M > pair < iterator , bool > insert_or_assign ( key_type && k , M && obj ); template < class K , class M > pair < iterator , bool > insert_or_assign ( K && k , M && obj ); template < class M > iterator insert_or_assign ( const_iterator hint , const key_type & k , M && obj ); template < class M > iterator insert_or_assign ( const_iterator hint , key_type && k , M && obj ); template < class K , class M > iterator insert_or_assign ( const_iterator hint , K && k , M && obj ); [...]
// [unord.map.elem], element access mapped_type & operator []( const key_type & k ); mapped_type & operator []( key_type && k ); template < class K > mapped_type & operator []( K && k ); mapped_type & at ( const key_type & k ); const mapped_type & at ( const key_type & k ) const ; template < class K > mapped_type & at ( const K & k ); template < class K > const mapped_type & at ( const K & k ) const ; [...]
size_type bucket_size ( size_type n ) const ; size_type bucket ( const key_type & k ) const ; template < class K > size_type bucket ( const K & k ) const ; 
4.9. Modify [unord.map.access]
[...]mapped_type & operator []( key_type && k ); �Effects : Equivalent to : return try_emplace ( move ( k )). first -> second ; template < class K > mapped_type & operator []( K && k ); �Constraints : Qualified - ids Hash :: is_transparent and Pred :: is_transparent are valid and denote types . �Effects : Equivalent to : return try_emplace ( forward < K > ( k )). first -> second ; mapped_type & at ( const key_type & k ); const mapped_type & at ( const key_type & k ) const ; [...] �Throws : An exception object of type out_of_range if no such element is present . template < class K > mapped_type & at ( const K & k ); template < class K > const mapped_type & at ( const K & k ) const ; �Constraints : Qualified - ids Hash :: is_transparent and Pred :: is_transparent are valid and denote types . �Preconditions : The expression find ( k ) is well - formed and has well - defined behavior . �Returns : A reference to find ( k ) -> second . �Throws : An exception object of type out_of_range if find ( k ) == end () is true. 
4.10. Modify [unord.map.modifiers]
template < class ... Args > pair < iterator , bool > try_emplace ( key_type && k , Args && ... args ); template < class ... Args > iterator try_emplace ( const_iterator hint , key_type && k , Args && ... args ); [...] �Complexity : The same as emplace and emplace_hint , respectively . template < class K , class ... Args > pair < iterator , bool > try_emplace ( K && k , Args && ... args ); template < class K , class ... Args > iterator try_emplace ( const_iterator hint , K && k , Args && ... args ); �Constraints : Qualified - ids Hash :: is_transparent and Pred :: is_transparent are valid and denote types . For the first overload , is_convertible_v < K && , const_iterator > and is_convertible_v < K && , iterator > are both false. �Preconditions : value_type is Cpp17EmplaceConstructible into unordered_map from piecewise_construct , forward_as_tuple ( forward < K > ( k )), forward_as_tuple ( forward < Args > ( args )...) . The conversion from k into key_type constructs an object u , for which find ( k ) == find ( u ) is true. �Effects : If the map already contains an element whose key is equivalent to k , there is no effect . Otherwise inserts an object of type value_type constructed with piecewise_construct , forward_as_tuple ( std :: forward < K > ( k )), forward_as_tuple ( std :: forward < Args > ( args )...) . �Returns : In the first overload , the bool component of the returned pair is trueif and only if the insertion took place . The returned iterator points to the map element whose key is equivalent to k . �Complexity : The same as emplace and emplace_hint , respectively . [...] template < class M > pair < iterator , bool > insert_or_assign ( key_type && k , M && obj ); template < class M > iterator insert_or_assign ( const_iterator hint , key_type && k , M && obj ); [...] �Complexity : The same as emplace and emplace_hint , respectively . template < class K , class M > pair < iterator , bool > insert_or_assign ( K && k , M && obj ); template < class K , class M > iterator insert_or_assign ( const_iterator hint , K && k , M && obj ); �Constraints : Qualified - ids Hash :: is_transparent and Pred :: is_transparent are valid and denote types . �Mandates : is_assignable_v < mapped_type & , M &&> is true. �Preconditions : value_type is Cpp17EmplaceConstructible into unordered_map from forward < K > ( k ), forward < M > ( obj ) . The conversion from k into key_type constructs an object u , for which find ( k ) == find ( u ) is true. �Effects : If the map already contains an element e whose key is equivalent to k , assigns std :: forward < M > ( obj ) to e . second . Otherwise inserts an object of type value_type constructed with std :: forward < K > ( k ), std :: forward < M > ( obj ) . �Returns : In the first overload , the bool component of the returned pair is trueif and only if the insertion took place . The returned iterator points to the map element whose key is equivalent to k . �Complexity : The same as emplace and emplace_hint , respectively . 
4.11. Modify class template unordered_multimap synopsis [unord.multimap.overview]
[...]size_type bucket_size ( size_type n ) const ; size_type bucket ( const key_type & k ) const ; template < class K > size_type bucket ( const K & k ) const ; 
4.12. Modify class template unordered_set synopsis [unord.set.overview]
[...]pair < iterator , bool > insert ( const value_type & obj ); pair < iterator , bool > insert ( value_type && obj ); template < class K > pair < iterator , bool > insert ( K && obj ); iterator insert ( const_iterator hint , const value_type & obj ); iterator insert ( const_iterator hint , value_type && obj ); template < class K > iterator insert ( const_iterator hint , K && obj ); [...]
size_type bucket_size ( size_type n ) const ; size_type bucket ( const key_type & k ) const ; template < class K > size_type bucket ( const K & k ) const ; 
4.13. Add paragraph Modifiers into [unord.set.overview]
�Erasure [ unord . set . erasure ] [...]
�Modifiers [ unord . set . modifiers ] template < class K > pair < iterator , bool > insert ( K && obj ); template < class K > iterator insert ( const_iterator hint , K && obj ); �Constraints : Qualified - ids Hash :: is_transparent and Pred :: is_transparent are valid and denote types , is_convertible_v < K && , const_iterator > and is_convertible_v < K && , iterator > are both false. �Preconditions : value_type is Cpp17EmplaceConstructible into unordered_set from x . The conversion from obj into key_type constructs an object u , for which find ( obj ) == find ( u ) is true. �Effects : Inserts a value_type object t constructed with std :: forward < K > ( x ) if and only if there is no element in the container that is equivalent to t . �Returns : In the first overload , the bool component of the returned pair is trueif and only if the insertion took place . The returned iterator points to the set element that is equivalent to x . �Complexity : Average case - constant , worst case - linear . 
4.14. Modify class template unordered_multiset synopsis [unord.multiset.overview]
[...]size_type bucket_size ( size_type n ) const ; size_type bucket ( const key_type & k ) const ; template < class K > size_type bucket ( const K & k ) const ; 
5. Revision history
5.1. R3 ➡ R4
- 
     Added missing constraints for std :: set :: insert std :: unordered_set :: insert 
5.2. R2 ➡ R3
- 
     Added Preconditions for at 
5.3. R1 ➡ R2
- 
     Extended § 3.7 Design decisions to apply the feedback from LEWG. 
- 
     Added § 3.9 Performance evaluation. 
- 
     Updated § 4 Formal wording with the precondition about convertion and comparison consistency. 
5.4. R0 ➡ R1
- 
     Removed std :: is_constructible_v 
- 
     Added § 4 Formal wording. 
6. Acknowledgments
Special thanks to Tim Song for many findings during the paper review that eventually improved the quality of the proposal.
Thanks to Christian Mazakas for finding the ambiguity issue for existing and proposed