2464. try_emplace and insert_or_assign misspecified

Section: 23.4.3.4 [map.modifiers], 23.5.3.4 [unord.map.modifiers] Status: C++17 Submitter: Thomas Koeppe Opened: 2014-12-17 Last modified: 2017-07-30

Priority: 2

View all other issues in [map.modifiers].

View all issues with C++17 status.

Discussion:

The specification of the try_emplace and insert_or_assign member functions in N4279 contains the following errors and omissions:

  1. In insert_or_assign, each occurrence of std::forward<Args>(args)... should be std::forward<M>(obj); this is was a mistake introduced in editing.

  2. In try_emplace, the construction of the value_type is misspecified, which is a mistake that was introduced during the evolution from a one-parameter to a variadic form. As written, value_type(k, std::forward<Args>(args)...) does not do the right thing; it can only be used with a single argument, which moreover must be convertible to a mapped_type. The intention is to allow direct-initialization from an argument pack, and the correct constructor should be value_type(piecewise_construct, forward_as_tuple(k), forward_as_tuple(std::forward<Args>(args)...).

  3. Both try_emplace and insert_or_assign are missing requirements on the argument types. Since the semantics of these functions are specified independent of other functions, they need to include their requirements.

[2015-02, Cologne]

This issue is related to 2469.

AM: The repeated references to "first and third forms" and "second and fourth forms" is a bit cumbersome. Maybe split the four functions?
GR: We don't have precendent for "EmplaceConstructible from a, b, c". I don't like the ambiguity between code commas and text commas.
TK: What's the danger?
GR: It's difficult to follow standardese.
AM: It seems fine with code commas. What's the problem?
GR: It will lead to difficulties when we use a similar construction that's not at the end of a sentence.
AM: That's premature generalization. DK: When that happens, let's look at this again.
AM: Clean up "if the map does contain"
TK: Can we call both containers "map"? DK/GR: yes.
TK will send updated wording to DK.

Conclusion: Update wording, then poll for tentatively ready

[2015-03-26, Thomas provides improved wording]

The approach is to split the descriptions of the various blocks of four functions into two blocks each so as to make the wording easier to follow.

Previous resolution [SUPERSEDED]:

This wording is relative to N4296.

  1. Apply the following changes to section 23.4.3.4 [map.modifiers] p3:

    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... 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);
    

    -?- Requires: For the first and third forms, value_type shall be EmplaceConstructible into map from piecewise_construct, forward_as_tuple(k), forward_as_tuple(forward<Args>(args)...). For the second and fourth forms, value_type shall be EmplaceConstructible into map from piecewise_construct, forward_as_tuple(move(k)), forward_as_tuple(forward<Args>(args)...).

    -3- Effects: If the key k already exists in the map, there is no effect. Otherwise, inserts an element into the map. In the first and third forms, the element is constructed from the arguments as value_type(k, std::forward<Args>(args)...). In the second and fourth forms, the element is constructed from the arguments as value_type(std::move(k), std::forward<Args>(args)...). In the first two overloads, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the element of the map whose key is equivalent to k If the map does already contain an element whose key is equivalent to k, there is no effect. Otherwise for the first and third forms inserts a value_type object t constructed with piecewise_construct, forward_as_tuple(k), forward_as_tuple(forward<Args>(args)...), for the second and fourth forms inserts a value_type object t constructed with piecewise_construct, forward_as_tuple(move(k)), forward_as_tuple(forward<Args>(args)...).

    -?- Returns: In the first two overloads, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the map element whose key is equivalent to k.

  2. Apply the following changes to section 23.4.3.4 [map.modifiers] p5:

    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 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);
    

    -?- Requires: is_assignable<mapped_type&, M&&>::value shall be true. For the first and third forms, value_type shall be EmplaceConstructible into map from k, forward<M>(obj). For the second and fourth forms, value_type shall be EmplaceConstructible into map from move(k), forward<M>(obj).

    -5- Effects: If the key k does not exist in the map, inserts an element into the map. In the first and third forms, the element is constructed from the arguments as value_type(k, std::forward<Args>(args)...). In the second and fourth forms, the element is constructed from the arguments as value_type(std::move(k), std::forward<Args>(args)...). If the key already exists, std::forward<M>(obj) is assigned to the mapped_type corresponding to the key. In the first two overloads, the bool component of the returned value is true if and only if the insertion took place. The returned iterator points to the element that was inserted or updated If the map does already contain an element whose key is equivalent to k, forward<M>(obj) is assigned to the mapped_type corresponding to the key. Otherwise the first and third forms inserts a value_type object t constructed with k, forward<M>(obj), the second and fourth forms inserts a value_type object t constructed with move(k), forward<M>(obj).

    -?- Returns: In the first two overloads, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the element of the map whose key is equivalent to k.

  3. Apply the following changes to section 23.5.3.4 [unord.map.modifiers] p5:

    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... 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);
    

    -?- Requires: For the first and third forms, value_type shall be EmplaceConstructible into unordered_map from piecewise_construct, forward_as_tuple(k), forward_as_tuple(forward<Args>(args)...). For the second and fourth forms, value_type shall be EmplaceConstructible into unordered_map from piecewise_construct, forward_as_tuple(move(k)), forward_as_tuple(forward<Args>(args)...).

    -5- Effects: If the key k already exists in the map, there is no effect. Otherwise, inserts an element into the map. In the first and third forms, the element is constructed from the arguments as value_type(k, std::forward<Args>(args)...). In the second and fourth forms, the element is constructed from the arguments as value_type(std::move(k), std::forward<Args>(args)...). In the first two overloads, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the element of the map whose key is equivalent to k If the unordered_map does already contain an element whose key is equivalent to k, there is no effect. Otherwise for the first and third forms inserts a value_type object t constructed with piecewise_construct, forward_as_tuple(k), forward_as_tuple(forward<Args>(args)...), for the second and fourth forms inserts a value_type object t constructed with piecewise_construct, forward_as_tuple(move(k)), forward_as_tuple(forward<Args>(args)...).

    -?- Returns: In the first two overloads, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the element of the unordered_map whose key is equivalent to k.

  4. Apply the following changes to section 23.5.3.4 [unord.map.modifiers] p7:

    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 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);
    

    -?- Requires: is_assignable<mapped_type&, M&&>::value shall be true. For the first and third forms, value_type shall be EmplaceConstructible into unordered_map from k, forward<M>(obj). For the second and fourth forms, value_type shall be EmplaceConstructible into unordered_map from move(k), forward<M>(obj).

    -7- Effects: If the key k does not exist in the map, inserts an element into the map. In the first and third forms, the element is constructed from the arguments as value_type(k, std::forward<Args>(args)...). In the second and fourth forms, the element is constructed from the arguments as value_type(std::move(k), std::forward<Args>(args)...). If the key already exists, std::forward<M>(obj) is assigned to the mapped_type corresponding to the key. In the first two overloads, the bool component of the returned value is true if and only if the insertion took place. The returned iterator points to the element that was inserted or updated If the unordered_map does already contain an element whose key is equivalent to k, forward<M>(obj) is assigned to the mapped_type corresponding to the key. Otherwise the first and third forms inserts a value_type object t constructed with k, forward<M>(obj), the second and fourth forms inserts a value_type object t constructed with move(k), forward<M>(obj).

    -?- Returns: In the first two overloads, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the element of the unordered_map whose key is equivalent to k.

[2015-05, Lenexa]

STL: existing wording is horrible, this is Thomas' wording and his issue
STL: already implemented the piecewise part
MC: ok with changes
STL: changes are mechanical
STL: believe this is P1, it must be fixed, we have wording
PJP: functions are sensible
STL: has been implemented
MC: consensus is to move to ready

Proposed resolution:

This wording is relative to N4296.

  1. Apply the following changes to 23.4.3.4 [map.modifiers] p3+p4:

    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... 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);
    

    -?- Requires: value_type shall be EmplaceConstructible into map from piecewise_construct, forward_as_tuple(k), forward_as_tuple(forward<Args>(args)...).

    -3- Effects: If the key k already exists in the map, there is no effect. Otherwise, inserts an element into the map. In the first and third forms, the element is constructed from the arguments as value_type(k, std::forward<Args>(args)...). In the second and fourth forms, the element is constructed from the arguments as value_type(std::move(k), std::forward<Args>(args)...). In the first two overloads, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the element of the map whose key is equivalent to k 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(k), forward_as_tuple(forward<Args>(args)...).

    -?- Returns: In the first overload, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the map element whose key is equivalent to k.

    -4- Complexity: The same as emplace and emplace_hint, respectively.

    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);
    

    -?- Requires: value_type shall be EmplaceConstructible into map from piecewise_construct, forward_as_tuple(move(k)), forward_as_tuple(forward<Args>(args)...).

    -?- 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(move(k)), forward_as_tuple(forward<Args>(args)...).

    -?- Returns: In the first overload, the bool component of the returned pair is true if 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.

  2. Apply the following changes to 23.4.3.4 [map.modifiers] p5+p6:

    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 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);
    

    -?- Requires: is_assignable<mapped_type&, M&&>::value shall be true. value_type shall be EmplaceConstructible into map from k, forward<M>(obj).

    -5- Effects: If the key k does not exist in the map, inserts an element into the map. In the first and third forms, the element is constructed from the arguments as value_type(k, std::forward<Args>(args)...). In the second and fourth forms, the element is constructed from the arguments as value_type(std::move(k), std::forward<Args>(args)...). If the key already exists, std::forward<M>(obj) is assigned to the mapped_type corresponding to the key. In the first two overloads, the bool component of the returned value is true if and only if the insertion took place. The returned iterator points to the element that was inserted or updated If the map already contains an element e whose key is equivalent to k, assigns forward<M>(obj) to e.second. Otherwise inserts an object of type value_type constructed with k, forward<M>(obj).

    -?- Returns: In the first overload, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the map element whose key is equivalent to k.

    -6- 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);
    

    -?- Requires: is_assignable<mapped_type&, M&&>::value shall be true. value_type shall be EmplaceConstructible into map from move(k), forward<M>(obj).

    -?- Effects: If the map already contains an element e whose key is equivalent to k, assigns forward<M>(obj) to e.second. Otherwise inserts an object of type value_type constructed with move(k), forward<M>(obj).

    -?- Returns: In the first overload, the bool component of the returned pair is true if 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.

  3. Apply the following changes to 23.5.3.4 [unord.map.modifiers] p5+p6:

    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... 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);
    

    -?- Requires: value_type shall be EmplaceConstructible into unordered_map from piecewise_construct, forward_as_tuple(k), forward_as_tuple(forward<Args>(args)...).

    -5- Effects: If the key k already exists in the map, there is no effect. Otherwise, inserts an element into the map. In the first and third forms, the element is constructed from the arguments as value_type(k, std::forward<Args>(args)...). In the second and fourth forms, the element is constructed from the arguments as value_type(std::move(k), std::forward<Args>(args)...). In the first two overloads, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the element of the map whose key is equivalent to k 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(k), forward_as_tuple(forward<Args>(args)...).

    -?- Returns: In the first overload, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the map element whose key is equivalent to k.

    -6- Complexity: The same as emplace and emplace_hint, respectively.

    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);
    

    -?- Requires: value_type shall be EmplaceConstructible into unordered_map from piecewise_construct, forward_as_tuple(move(k)), forward_as_tuple(forward<Args>(args)...).

    -?- 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(move(k)), forward_as_tuple(forward<Args>(args)...).

    -?- Returns: In the first overload, the bool component of the returned pair is true if 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. Apply the following changes to 23.5.3.4 [unord.map.modifiers] p7+p8:

    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 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);
    

    -?- Requires: is_assignable<mapped_type&, M&&>::value shall be true. value_type shall be EmplaceConstructible into unordered_map from k, forward<M>(obj).

    -7- Effects: If the key k does not exist in the map, inserts an element into the map. In the first and third forms, the element is constructed from the arguments as value_type(k, std::forward<Args>(args)...). In the second and fourth forms, the element is constructed from the arguments as value_type(std::move(k), std::forward<Args>(args)...). If the key already exists, std::forward<M>(obj) is assigned to the mapped_type corresponding to the key. In the first two overloads, the bool component of the returned value is true if and only if the insertion took place. The returned iterator points to the element that was inserted or updated If the map already contains an element e whose key is equivalent to k, assigns forward<M>(obj) to e.second. Otherwise inserts an object of type value_type constructed with k, forward<M>(obj).

    -?- Returns: In the first overload, the bool component of the returned pair is true if and only if the insertion took place. The returned iterator points to the map element whose key is equivalent to k.

    -8- 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);
    

    -?- Requires: is_assignable<mapped_type&, M&&>::value shall be true. value_type shall be EmplaceConstructible into unordered_map from move(k), forward<M>(obj).

    -?- Effects: If the map already contains an element e whose key is equivalent to k, assigns forward<M>(obj) to e.second. Otherwise inserts an object of type value_type constructed with move(k), forward<M>(obj).

    -?- Returns: In the first overload, the bool component of the returned pair is true if 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.