C++ Standard Library Issues Resolved Directly In Lenexa

Doc. no. N4525
Date:

Revised 2015-05-08 at 22:05:35 UTC

Project: Programming Language C++
Reply to: Marshall Clow <lwgchair@gmail.com>

Immediate Issues


2063. Contradictory requirements for string move assignment

Section: 21.4 [basic.string] Status: Immediate Submitter: Howard Hinnant Opened: 2011-05-29 Last modified: 2015-05-08

View other active issues in [basic.string].

View all other issues in [basic.string].

View all issues with Immediate status.

Discussion:

21.4.1 [string.require]/p4 says that basic_string is an "allocator-aware" container and behaves as described in 23.2.1 [container.requirements.general].

23.2.1 [container.requirements.general] describes move assignment in p7 and Table 99.

If allocator_traits<allocator_type>::propagate_on_container_move_assignment::value is false, and if the allocators stored in the lhs and rhs sides are not equal, then move assigning a string has the same semantics as copy assigning a string as far as resources are concerned (resources can not be transferred). And in this event, the lhs may have to acquire resources to gain sufficient capacity to store a copy of the rhs.

However 21.4.2 [string.cons]/p22 says:

basic_string<charT,traits,Allocator>&
operator=(basic_string<charT,traits,Allocator>&& str) noexcept;

Effects: If *this and str are not the same object, modifies *this as shown in Table 71. [Note: A valid implementation is swap(str). — end note ]

These two specifications for basic_string::operator=(basic_string&&) are in conflict with each other. It is not possible to implement a basic_string which satisfies both requirements.

Additionally assign from an rvalue basic_string is defined as:

basic_string& assign(basic_string&& str) noexcept;

Effects: The function replaces the string controlled by *this with a string of length str.size() whose elements are a copy of the string controlled by str. [ Note: A valid implementation is swap(str). — end note ]

It seems contradictory that this member can be sensitive to propagate_on_container_swap instead of propagate_on_container_move_assignment. Indeed, there is a very subtle chance for undefined behavior here: If the implementation implements this in terms of swap, and if propagate_on_container_swap is false, and if the two allocators are unequal, the behavior is undefined, and will likely lead to memory corruption. That's a lot to go wrong under a member named "assign".

[ 2011 Bloomington ]

Alisdair: Can this be conditional noexcept?

Pablo: We said we were not going to put in many conditional noexcepts. Problem is not allocator, but non-normative definition. It says swap is a valid operation which it is not.

Dave: Move assignment is not a critical method.

Alisdair: Was confusing assignment and construction.

Dave: Move construction is critical for efficiency.

Kyle: Is it possible to test for noexcept.

Alisdair: Yes, query the noexcept operator.

Alisdair: Agreed there is a problem that we cannot unconditionally mark these operations as noexcept.

Pablo: How come swap is not defined in alloc

Alisdair: It is in utility.

Pablo: Swap has a conditional noexcept. Is no throw move constructable, is no throw move assignable.

Pablo: Not critical for strings or containers.

Kyle: Why?

Pablo: They do not use the default swap.

Dave: Important for deduction in other types.

Alisdair: Would change the policy we adopted during FDIS mode.

Pablo: Keep it simple and get some vendor experience.

Alisdair: Is this wording correct? Concerned with bullet 2.

Pablo: Where does it reference containers section.

Alisdair: String is a container.

Alisdair: We should not remove redundancy piecemeal.

Pablo: I agree. This is a deviation from rest of string. Missing forward reference to containers section.

Pablo: To fix section 2. Only the note needs to be removed. The rest needs to be a forward reference to containers.

Alisdair: That is a new issue.

Pablo: Not really. Talking about adding one sentence, saying that basic string is a container.

Dave: That is not just a forward reference, it is a semantic change.

PJ: We intended to make it look like a container, but it did not satisfy all the requirements.

Pablo: Clause 1 is correct. Clause 2 is removing note and noexcept (do not remove the rest). Clause 3 is correct.

Alisdair: Not sure data() is correct (in clause 2).

Conclusion: Move to open, Alisdair and Pablo volunteered to provide wording

[ originally proposed wording: ]

This wording is relative to the FDIS.

  1. Modify the class template basic_string synopsis in 21.4 [basic.string]:

    namespace std {
      template<class charT, class traits = char_traits<charT>,
        class Allocator = allocator<charT> >
      class basic_string {
      public:
        […]
        basic_string& operator=(basic_string&& str) noexcept;
        […]
        basic_string& assign(basic_string&& str) noexcept;
        […]
      };
    }
    
  2. Remove the definition of the basic_string move assignment operator from 21.4.2 [string.cons] entirely, including Table 71 — operator=(const basic_string<charT, traits, Allocator>&&). This is consistent with how we define move assignment for the containers in Clause 23:

    basic_string<charT,traits,Allocator>&
    operator=(basic_string<charT,traits,Allocator>&& str) noexcept;
    

    -22- Effects: If *this and str are not the same object, modifies *this as shown in Table 71. [ Note: A valid implementation is swap(str). — end note ]

    -23- If *this and str are the same object, the member has no effect.

    -24- Returns: *this

    Table 71 — operator=(const basic_string<charT, traits, Allocator>&&)
    Element Value
    data() points at the array whose first element was pointed at by str.data()
    size() previous value of str.size()
    capacity() a value at least as large as size()
  3. Modify the paragraphs prior to 21.4.6.3 [string::assign] p.3 as indicated (The first insertion recommends a separate paragraph number for the indicated paragraph):

    basic_string& assign(basic_string&& str) noexcept;
    

    -?- Effects: Equivalent to *this = std::move(str). The function replaces the string controlled by *this with a string of length str.size() whose elements are a copy of the string controlled by str. [ Note: A valid implementation is swap(str). — end note ]

    -3- Returns: *this

[ 2012-08-11 Joe Gottman observes: ]

One of the effects of basic_string's move-assignment operator (21.4.2 [string.cons], Table 71) is

Element Value
data() points at the array whose first element was pointed at by str.data()

If a string implementation uses the small-string optimization and the input string str is small enough to make use of it, this effect is impossible to achieve. To use the small string optimization, a string has to be implemented using something like

union
{
   char buffer[SMALL_STRING_SIZE];
   char *pdata;
};

When the string is small enough to fit inside buffer, the data() member function returns static_cast<const char *>(buffer), and since buffer is an array variable, there is no way to implement move so that the moved-to string's buffer member variable is equal to this->buffer.

Resolution proposal:

Change Table 71 to read:

Element Value
data() points at the array whose first element was pointed at by str.data() that contains the same characters in the same order as str.data() contained before operator=() was called

[2015-05-07, Lenexa]

Howard suggests improved wording

Move to Immediate

Proposed resolution:

This wording is relative to N4431.

  1. Modify the class template basic_string synopsis in 21.4 [basic.string]:

    namespace std {
      template<class charT, class traits = char_traits<charT>,
        class Allocator = allocator<charT> >
      class basic_string {
      public:
        […]
        basic_string& assign(basic_string&& str) noexcept(
             allocator_traits<Allocator>::propagate_on_container_move_assignment::value ||
               allocator_traits<Allocator>::is_always_equal::value);
        […]
      };
    }
    
  2. Change 21.4.2 [string.cons]/p21-23:

    basic_string&
    operator=(basic_string&& str) noexcept(
             allocator_traits<Allocator>::propagate_on_container_move_assignment::value ||
               allocator_traits<Allocator>::is_always_equal::value);
    

    -21- Effects: If *this and str are not the same object, modifies *this as shown in Table 71. [ Note: A valid implementation is swap(str). — end note ] Move assigns as a sequence container ([container.requirements]), except that iterators, pointers and references may be invalidated.

    -22- If *this and str are the same object, the member has no effect.

    -23- Returns: *this

    Table 71 — operator=(basic_string&&) effects
    Element Value
    data() points at the array whose first element was pointed at by str.data()
    size() previous value of str.size()
    capacity() a value at least as large as size()
  3. Modify the paragraphs prior to 21.4.6.3 [string::assign] p.3 as indicated

    basic_string& assign(basic_string&& str) noexcept(
             allocator_traits<Allocator>::propagate_on_container_move_assignment::value ||
               allocator_traits<Allocator>::is_always_equal::value);
    

    -3- Effects: Equivalent to *this = std::move(str). The function replaces the string controlled by *this with a string of length str.size() whose elements are a copy of the string controlled by str. [ Note: A valid implementation is swap(str). — end note ]

    -4- Returns: *this


2407. packaged_task(allocator_arg_t, const Allocator&, F&&) should neither be constrained nor explicit

Section: 30.6.9.1 [futures.task.members] Status: Immediate Submitter: Stephan T. Lavavej Opened: 2014-06-14 Last modified: 2015-05-08

View other active issues in [futures.task.members].

View all other issues in [futures.task.members].

View all issues with Immediate status.

Discussion:

LWG 2097's resolution was slightly too aggressive. It constrained packaged_task(allocator_arg_t, const Allocator&, F&&), but that's unnecessary because packaged_task doesn't have any other three-argument constructors. Additionally, it's marked as explicit (going back to WP N2798 when packaged_task first appeared) which is unnecessary.

[2015-02 Cologne]

Handed over to SG1.

[2015-05 Lenexa, SG1 response]

Back to LWG; not an SG1 issue.

[2015-05 Lenexa]

STL improves proposed wording by restoring the constraint again.

Proposed resolution:

This wording is relative to N3936.

  1. Change 30.6.9 [futures.task] p2, class template packaged_task as indicated:

    template <class F>
    explicit packaged_task(F&& f);
    template <class F, class Allocator>
    explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f);
    
  2. Change 30.6.9.1 [futures.task.members] as indicated:

    template <class F>
    packaged_task(F&& f);
    template <class F, class Allocator>
    explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f);
    

    […]

    -3- Remarks: These constructors shall not participate in overload resolution if decay_t<F> is the same type as std::packaged_task<R(ArgTypes...)>.


2420. function<void(ArgTypes...)> does not discard the return value of the target object

Section: 20.9.12.2 [func.wrap.func] Status: Immediate Submitter: Agustín Bergé Opened: 2014-07-12 Last modified: 2015-05-08

View other active issues in [func.wrap.func].

View all other issues in [func.wrap.func].

View all issues with Immediate status.

Discussion:

function<void(ArgTypes...)> should discard the return value of the target object. This behavior was in the original proposal, and it was removed (accidentally?) by the resolution of LWG 870.

Previous resolution [SUPERSEDED]:

  1. Edit 20.9.12.2 [func.wrap.func] paragraph 2:

    A callable object f of type F is Callable for argument types ArgTypes and return type R if the expression INVOKE(f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well formed (20.9.2 [func.require]) and, if R is not void, implicitly convertible to R.

[2014-10-05 Daniel comments]

This side-effect was indeed not intended by 870.

[2015-05, Lenexa]

STL provides improved wording. It replaces the current PR, and intentionally leaves 20.9.12.2 [func.wrap.func] unchanged.

Due to 5 [expr]/6, static_cast<void> is correct even when R is const void.

Proposed resolution:

This wording is relative to N4431.

  1. Edit 20.9.2 [func.require] as depicted:

    -2- Define INVOKE(f, t1, t2, ..., tN, R) as static_cast<void>(INVOKE(f, t1, t2, ..., tN)) if R is cv void, otherwise INVOKE(f, t1, t2, ..., tN) implicitly converted to R.

  2. Change 20.9.12.2.4 [func.wrap.func.inv] as depicted:

    R operator()(ArgTypes... args) const;
    

    -1- EffectsReturns: INVOKE(f, std::forward<ArgTypes>(args)..., R) (20.9.2), where f is the target object (20.9.1) of *this.

    -2- Returns: Nothing if R is void, otherwise the return value of INVOKE(f, std::forward<ArgTypes>(args)..., R).


2442. call_once() shouldn't DECAY_COPY()

Section: 30.4.4.2 [thread.once.callonce] Status: Immediate Submitter: Stephan T. Lavavej Opened: 2014-10-01 Last modified: 2015-05-08

View all other issues in [thread.once.callonce].

View all issues with Immediate status.

Discussion:

When LWG 891 overhauled call_once()'s specification, it used decay_copy(), following LWG 929's overhaul of thread's constructor.

In thread's constructor, this is necessary and critically important. 30.3.1.2 [thread.thread.constr]/5 "The new thread of execution executes INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...) with the calls to DECAY_COPY being evaluated in the constructing thread." requires the parent thread to copy arguments for the child thread to access.

In call_once(), this is unnecessary and harmful. It's unnecessary because call_once() doesn't transfer arguments between threads. It's harmful because:

call_once() should use perfect forwarding without decay_copy(), in order to avoid interfering with the call like this.

[2015-02 Cologne]

Handed over to SG1.

[2015-05 Lenexa, SG1 response]

Looks good to us, but this is really an LWG issue.

Proposed resolution:

This wording is relative to N3936.

  1. Change 30.4.4.2 [thread.once.callonce] p1+p2 as depicted:

    template<class Callable, class ...Args>
      void call_once(once_flag& flag, Callable&& func, Args&&... args);
    

    -1- Requires: Callable and each Ti in Args shall satisfy the MoveConstructible requirements. INVOKE(DECAY_COPY(std::forward<Callable>(func)), DECAY_COPY(std::forward<Args>(args))...) (20.9.2) shall be a valid expression.

    -2- Effects; […] An active execution shall call INVOKE(DECAY_COPY(std::forward<Callable>(func)), DECAY_COPY(std::forward<Args>(args))...). […]


2464. try_emplace and insert_or_assign misspecified

Section: 23.4.4.4 [map.modifiers], 23.5.4.4 [unord.map.modifiers] Status: Immediate Submitter: Thomas Koeppe Opened: 2014-12-17 Last modified: 2015-05-08

View all other issues in [map.modifiers].

View all issues with Immediate 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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.


2488. Placeholders should be allowed and encouraged to be constexpr

Section: 20.9.10.4 [func.bind.place] Status: Immediate Submitter: Stephan T. Lavavej Opened: 2015-03-27 Last modified: 2015-05-08

View all issues with Immediate status.

Discussion:

piecewise_construct (20.3.5 [pair.piecewise]), allocator_arg (20.7.6 [allocator.tag]), and adopt_lock/defer_lock/try_to_lock (30.4.2 [thread.lock]) are all required to be constexpr with internal linkage. bind()'s placeholders should be allowed to follow this modern practice, for increased consistency and reduced implementer headaches (header-only is easier than separately-compiled).

Proposed resolution:

This wording is relative to N4296.

  1. Change 20.9 [function.objects] p2 "Header <functional> synopsis" as depicted:

    namespace placeholders {
      // M is the implementation-defined number of placeholders
      see belowextern unspecified _1;
      see belowextern unspecified _2;
      ...
      see belowextern unspecified _M;
    }
    
  2. Change 20.9.10.4 [func.bind.place] p2 as depicted:

    namespace std::placeholders {
      // M is the implementation-defined number of placeholders
      see belowextern unspecified _1;
      see belowextern unspecified _2;
                 .
                 .
                 .
      see belowextern unspecified _M;
    }
    

    -1- All placeholder types shall be DefaultConstructible and CopyConstructible, and their default constructors and copy/move constructors shall not throw exceptions. It is implementation-defined whether placeholder types are CopyAssignable. CopyAssignable placeholders' copy assignment operators shall not throw exceptions.

    -?- Placeholders should be defined as:

    constexpr unspecified _1{};
    

    If they are not, they shall be declared as:

    extern unspecified _1;