Document No: P0752r0
Date: 2017-10-15
Project: ISO SC22/WG21 C++ Standard
Author: Nathan Myers

# std::vector Destruction Order

C++ has always been a language intended for controlling computing 
machinery, leaving as little room as possible, underneath, requiring
code in a lower-level language. 

One expression of that intent has been its explicit stack-order
construction and destruction of program objects.  Struct members are
constructed in ascending order, from low address to high, and are
destroyed in the opposite, descending, order. Built-in arrays are
handled likewise, as is std::array.  Function-local variables and
function activation frames follow the same discipline.  I will call
this "natural order".  It is specified in the standard, and all
implementations observe it.

The natural, stack order is far from accidental.  Construction order
creates an implicit dependency graph in which later-constructed
objects may reasonably depend on objects constructed before them, but
clearly cannot depend on objects not yet constructed.  Respecting that
graph at destruction time, by not destroying objects that others are
most likely to still depend on, means the programmer will not need to
explicitly code a destruction order.  Respecting implicit dependency
may matter especially when the elements represent entities external
to, and not under control of, the program.

### The Problem

We have discovered an unfortunate divergence of std::vector element
destruction ordering in implementations of the standard library.
We find implementations that obey natural order, and others that
do not.

It appears that all these implementations conform, as they are,
to the existing wording, because the standard does not suggest any
ordering for either constructing or destroying elements of a vector.
Other conforming implementations could both construct and destroy
in descending natural order, or construct in descending order and
destroy in ascending order, although we do not know of any.

We find this lack of specification, and lack of conformance to
core language practice, unfortunate.  It appears that there is no
notable technical advantage to using any of the three possible
unnatural orderings; i.e., it appears to be a case of historical
accident.  (There is some evidence that machines do slightly favor
performing memory operations in ascending order, but it is weak.)
As accidental as the choice may have been in each case, it would
be easy for a program to depend, implicitly, on the implementation
order choice, and thus not be portable to a natural-order
implementation.

It seems likely that more programs would depend on the natural
order, and would not be portable to an unnatural-order
implementation.

Your author has not surveyed how consistently the various
implementations have observed their default order.  For example,
do they destroy elements in response to an exception during
construction in the same order as when they destroy the whole
vector?  How about when erasing a range of elements in the middle?
We would like to have consistent, reasoned behavior in all of
these cases.

### The Proposal

We propose to specify, in C++20, that std::vector performs its 
construction and destruction in the same order as native structs
and arrays, and as std::array.  In addition, we propose that,
where reasonable, std::vector observe the same order when 
constructing or destroying sub-ranges of elements.

It is clear that there must be exceptions to the general rule,
for cases where natural order would be impractical, such as the
order of moving elements in response to an insertion.  We are
confident these cases can all be identified, and specified with
equal clarity so that implementations will be uniform in these
details as well.  (We expect they probably all conform, already,
in these cases.)

Other aggregate types also impose an order.  We would like them
to be the same, wherever practical.  Clearly, destroying elements
of a singly-linked list should continue to be done in list order.
A defined order for tree and hash structures would be hard even to
express.  We propose that these be identified as explicitly not
required to conform to any specific rule.

### Backward Compatibility

Clearly, some existing programs must depend on the unnatural
destruction order performed by the implementation they were
originally tested on.  Even to determine whether a given program
is among those might be prohibitively difficult. Fixing it, if
it does, might be harder. We cannot expect implementations to
abandon support for these programs and their users.

Accommodate users dependent on unnatural destruction order could
become the most difficult part of this proposal. It seems certain
that, whatever appears in the working paper, each divergent
implementation must ship with some way for users who cannot afford
to fix their programs to build them as-is.  The worst outcome would
be for implementators to feel obliged to ignore the standard, and
ship a now non-conforming library. Almost as bad would be to
continue to have no specified destruction order.

It is far from clear, though, that the proposal must, or can,
specify the accommodations that currently divergent implementations
must make for their users.  Each implementation has had to adapt to
evolution of the standard, and will need to do so in the future;
this case is one of many.  The usual approach is to provide a
"backward-compatibility" command-line flag that restores the old
behavior when building the old program. That seems to the author
like it should suffice, but opinions may differ.  Where they do,
they seem to emphasize that the standard is not the place to list
alternative accommodations.

That said, a global symbol could be defined to allow a portable
library to discover whether it is running in an environment with 
unnatural vector destruction order.

### Votes

This proposal does not have Working Paper text yet.  If there is
interest, a forthcoming draft will have unsurprising text.
