2160. Unintended destruction ordering-specification of resize

Section: 23.3.11.3 [vector.capacity] Status: C++17 Submitter: Daniel Krügler Opened: 2012-06-07 Last modified: 2017-07-30

Priority: 1

View other active issues in [vector.capacity].

View all other issues in [vector.capacity].

View all issues with C++17 status.

Discussion:

As part of resolving LWG issue 2033 a wording change was done for resize() to respect the problem mentioned in the question:

Does a call to 'void resize(size_type sz)' of std::vector require the element type to be MoveAssignable because the call erase(begin() + sz, end()) mentioned in the Effects paragraph would require the element type to be MoveAssignable?

The wording change was to replace in 23.3.5.3 [deque.capacity] and 23.3.11.3 [vector.capacity]:

-1- Effects: If sz <= size(), equivalent to erase(begin() + sz, end()); […]

by:

-1- Effects: If sz <= size(), equivalent to calling pop_back() size() - sz times. […]

The overlooked side-effect of this wording change is that this implies a destruction order of the removed elements to be in reverse order of construction, but the previous version did not impose any specific destruction order due to the way how the semantics of erase is specified in Table 100.

Given the program:

#include <vector>
#include <iostream>

struct Probe {
  int value;
  Probe() : value(0) {}
  Probe(int value) : value(value) {}
  ~Probe() { std::cout << "~Probe() of " << value << std::endl; }
};

int main() {
  std::vector<Probe> v;
  v.push_back(Probe(1));
  v.push_back(Probe(2));
  v.push_back(Probe(3));
  std::cout << "---" << std::endl;
  v.resize(0);
}

the last three lines of the output for every compiler I tested was:

~Probe() of 1
~Probe() of 2
~Probe() of 3

but a conforming implementation would now need to change this order to

~Probe() of 3
~Probe() of 2
~Probe() of 1

This possible stringent interpretation makes sense, because one can argue that sequence containers (or at least std::vector) should have the same required destruction order of it's elements, as elements of a C array or controlled by memory deallocated with an array delete have. I also learned that libc++ does indeed implement std::vector::resize in a way that the second output form is observed.

While I agree that required reverse-destruction would better mimic the natural behaviour of std::vector this was not required in C++03 and this request may be too strong. My current suggestion would be to restore the effects of the previous wording in regard to the destruction order, because otherwise several currently existing implementations would be broken just because of this additional requirement.

[2013-03-15 Issues Teleconference]

Moved to Open.

Jonathan says that he believes this is a valid issue.

Walter wonders if this was intended when we made the previous change - if so, this would be NAD.

Jonathan said that Issue 2033 doesn't mention ordering.

Walter then asked if anyone is really unhappy that we're destroying items in reverse order of construction.

Jonathan points out that this conflicts with existing practice (libstc++, but not libc++).

Jonathan asked for clarification as to whether this change was intended by 2033.

[2014-06 Rapperswil]

Daniel points out that the ordering change was not intended.

General agreement that implementations should not be required to change.

[2014-06-28 Daniel provides alternative wording]

[Urbana 2014-11-07: Move to Ready]

Proposed resolution:

This wording is relative to N3936.

  1. Change 23.3.5.3 [deque.capacity] as indicated: [Drafting note: The chosen wording form is similar to that for forward_list. Note that the existing Requires element already specifies the necessary operational requirements on the value type. — end drafting note]

    void resize(size_type sz);
    

    -1- Effects: If sz <= size(), erases the last size() - sz elements from the sequenceequivalent to calling pop_back() size() - sz times. OtherwiseIf size() <= sz, appends sz - size() default-inserted elements to the sequence.

    […]

    void resize(size_type sz, const T& c);
    

    -3- Effects: If sz <= size(), erases the last size() - sz elements from the sequenceequivalent to calling pop_back() size() - sz times. OtherwiseIf size() < sz, appends sz - size() copies of c to the sequence.

    […]

  2. Change 23.3.11.3 [vector.capacity] as indicated: [Drafting note: See deque for the rationale of the used wording. — end drafting note]

    void resize(size_type sz);
    

    -12- Effects: If sz <= size(), erases the last size() - sz elements from the sequenceequivalent to calling pop_back() size() - sz times. OtherwiseIf size() < sz, appends sz - size() default-inserted elements to the sequence.

    […]

    void resize(size_type sz, const T& c);
    

    -15- Effects: If sz <= size(), erases the last size() - sz elements from the sequenceequivalent to calling pop_back() size() - sz times. OtherwiseIf size() < sz, appends sz - size() copies of c to the sequence.

    […]