A number of features of the C++ Standard library have been surpassed by
additions over the years, or we have learned do not serve their intended
purpose as well as originally expected. This paper proposed deprecating
features where better, simpler, or clearer options are available.

There is likely to be a reasonable amount of user code using all of the feature
below in production today, so it would be premature to remove them from the
standard. However, with best best practice advancing and superior or clearer
options available within the standard itself, now would be a good time to
deprecate these features, directing users to the preferred alternative instead.

As an aid to writing iterator classes, the original standard library supplied
the iterator class template to automate the declaration of the five
typedefs expected of every iterator by iterator_traits. This was then
used in the library itself, for instance in the specification of
std::ostream_iterator:

The long sequence of void arguments is much less clear to the reader
than simply providing the expected typedefs in the class definition itself,
which is the approach taken by the current working draft, following the pattern
set in C++14 where we deprecated the derivation throughout the library of
functors from unary_function and binary_function.

In addition to the reduced clarity, the iterator template also lays a
trap for the unwary, as in typical usage it will be a dependent base class,
which means it will not be looking into during name lookup from within the
class or its member functions. This leads to surprised users trying to
understand why the following simple usage does not work:

The reason of clarity alone was sufficient to persuade the LWG to update the
standard library specification to no longer mandate the standard iterator
adapators as deriving from std::iterator, so there is no further use
of this template within the standard itself. Therefore, it looks like a strong
candidate for deprecation.

Many members of std::allocator redundantly duplicate behavior that is
otherwise produced by std::allocator_traits<allocator<T>>,
and could safely be removed to simplify this class. Further,
addressof as a free function supersedes
std::allocator<T>::address which requires an allocator
object of the right type. Finally, the reference type aliases were
initially provided as an expected means for extension with other allocators,
but turned out to not serve a useful purpose when we specified the allocator
requirements (17.6.3.5 [allocator.requirements]).

While we cannot remove these members without breaking backwards compatibility
with code that explicitly used this allocator type, we should not be
recommending their continued use. If a type wants to support generic
allocators, it should access the allocator's functionality through
allocator_traits rather than directly accessing the allocator's
members - otherwise it will not properly support allocators that rely on the
traits to synthesize the default behaviors. Similarly, if a user does not
intend to support generic allocators, then it is much simpler to directly
invoke new, delete, and assume the other properties of
std::allocator such as pointer-types directly.

While the is_always_equal trait might be implicitly synthesized from
the trait is_empty_v<allocator<T>>, there appears to be no
specified guarantee that std::allocator specializations be empty
classes (although this is widely expected) so the trait remains explicitly
specialized to provide the necessary guarantee, even for implementations that
choose to add some data to std::allocator for debug, profiling, or
other purposes.

Similarly, std::allocator<void> is defined so that various
template rebinding tricks could work in the original C++98 library, but it is
not an actual allocator, as it lacks both allocate and
deallocate member functions, which cannot be synthesized by default
from allocator_traits. That need went away with C++11 and the
void_pointer and const_void_pointer type aliases in
allocator_traits. However, we continue to specify it in order to
avoid breaking old code that has not yet been upgraded to support generic
allocators, per C++11.

This paper recommends deprecating std::allocator<void> and the
redundant members of the std::allocator class template, and moving
their declarations and definitions to Annex D, just as the old iostreams
members were handled in the original 1998 standard.

One potential concern is whether removing these members in the future might
cause a compile-time performance regression, if the library is expected to
deduce the default behavior in each case. The proposed Zombie Names clause
from
P0090R0
and incorporated in
P0005R3
would allow vendors to retain these names indefinitely, if that were a concern.
However, the optimal (compile-time) solution for the library implementation is
to provide a partial specialization for
std::allocator_traits<std::allocator<T>>, and so entirely
avoid the cost of evaluating complex template machinery for the default
allocator in the standard library.

It was also observed, while writing this paper, that the allocator propagation
traits for std::allocator<void> do not match those for
std::allocator<T>. It was decided not to provide a drive-by
fix, as std::allocator<void> is not a true allocator, and so
should not end up in code relying on those traits. If the desision is made not
to deprecate this specialization though, it could be worth revisitting the
issue, just to address consistency and a lack of surprise.

The is_literal type trait offers negligible value to generic code, as
what is really needed is the ability to know that a specific construction would
produce constant initialization. The core term of a literal type having
at least one constexpr constructor is too weak to be used
meaningfully.

However, the (already implemented) trait does no harm, and allows compile-time
introspection for which core-language type-categories a given template
parameter might satisfy. Until the Core Working Group retire the notion of a
literal type, the corresponding library trait should be preserved.

The next step towards removal (after deprecation) would be to write a paper
proposing to remove the term from the core language while deprecating/removing
the type trait.

Several additional aspects of the library were looked at, but decided against a
recommendation for deprecation at this point. They are listed here, with
rationale, in case others are more motivated. With appropriate additions to
the standard, these may become stronger deprecation candidates for the next
standard.

If members of the Library Working Groups would like to see any of these
additional libraries deprecated, the author is happy to extend the proposal,
but feels that they go beyond the remit of this paper deprecating only features
where a preferable alternative is already available in the standard.

There has been a long history of the bool partial specialization
of std::vector not satisfying the container requirements, and in
particular, its iterators not satisfying the requirements of a random access
iterator. A previous attempt to deprecate this container was rejected for C++11,
N2204.

One of the reasons for rejection is that it is not clear what it would mean to
deprecate a particular specialization of a template. That could be addressed
with careful wording. The larger issue is that the (packed) specialization of
vector<bool> offers an important optimization that clients of
the standard library genuinely seek, but would not longer be available. It is
unlikely that we would be able to deprecate this part of the standard until a
replacement facility is proposed and accepted, such as
N2050.
Unfortunately, there are no such revised proposals currently being offered to
the Library Evolution Working Group.

Library clause 20.7.11 [temporary.buffer] provides an API intended to
efficiently create a small ammount of additional storage for local,
short-term use. The inspiration behind the API is to use some magic to create
a tiny buffer on the stack.

This API would be considered an incomplete thought were it proposed today. As
a functional API it lacks exception safety if the function allocating the
buffer leaks, yet we offer no RAII-like wrappers to promote safe use.

It has been suggested that all current implementation of this API actually do
not perform a more efficient allocation than the regular new operator,
and, if that is genuinely the case, we should seriously consider deprecating
this facility. Otherwise, we should probably complete the design with an
appropriate guard/wrapper class, and encourage vendors to deliver on missed
optimization opportunities.

It is possible the need for this API could be overtaken by delivering on a
genuine C++ API or data structure for allocating off the stack, such as was
proposed by std::dynarray in
N3662.
However, the Evolution Working Group has not been keen on further work in this
direction, and the Arrays TS appears to have stalled, if not abandoned
entirely.

Like the temporary buffer API, the raw_storage_iterator (20.7.10
[storage.iterator]) and uninitialized algorithms (20.7.12
[specialized.algorithms]) are incomplete thoughts, although they
appeared complete at the time of adoption.

One obvious shortcoming of the iterator adapter is that it could easily query
the type of element the adapated iterator is prepared to construct via
iterator_traits and provide a default template argument for the second
parameter, simplifying use. That no such proposal has come forward in almost
20 years suggests that this is not a widely used component. Similarly, a
type-deducing factory function would be a natural extension, especially with
the advent of auto in C++11, yet no such proposal has materialized.

A deeper concern arises from the completion of allocator support in C++11.
There is now a common requirement that construting an object in a region of
allocated memory should invoke the construct method of the
corresponding allocator. This is not possible with the current iterator, and
undermines its use as an implementation detail in users writing generic
containers. However, no-one seems motivated to invest the time in proposing an
allocator-aware raw storage iterator, nor adding allocator-awareness to the
uninitialized memory algorithms.

Without a clear alternative to replace them, there is no clear deprecation path
other than simply looking to drop support for the whole idea in some future
standard. That goes beyond the intent of this paper.

The standard library has several algorithms that read from two ranges in order
to determine their result. In most cases, the original C++98 standard fully
specified the first range with a pair of iterators, and supplied only the first
iterator for the second range, with a narrow contract requirement that the
second range be at least as large as the first. For the C++14 standard, these
algorithms were overloaded with a second form where the second range is also
fully specified by a pair of iterators, partly so that the length of the second
sequence can be taken into account (when it is shorter), and partly because
this form is less liable to misuse leading to buffer overruns and other
security risks. See
N3671
Making non-modifying sequence operations more robust for more details.

Given the security risks associated with the original algorithms, this paper
recommends deprecating the form of any algorithm where a second input range is
specified by only one iterator. Note that algorithms using a single iterator
for an output range continue to be supported, as there is no (standard) way to
specify the end of an output iteration sequence, such as defined by an
std::ostream_iterator.

Update from Jacksonville, 2016:

Poll: Deprecate the listed algorithms with half-specified ranges?

SF

F

N

A

SA

3

6

3

5

1

There is no consensus to deprecate at this point, although interest remains
to revisit for a future standard, probably after the Ranges TS has landed.

The standard containers map and multimap both contain an
identical class template value_compare that is a functor which can be
used to determine the relative order of two elements in that container. These
are provided as elements in a map are ordered by only the key value, rather
than the whole value, so the predicate supplied to the container compares the
key type, not the actual element type.

In practice these functors are not used by the containers, as they are often
required to compare keys with values for elements that have not yet been
inserted into the map, and may not have been created as a pair yet.
Meanwhile, the cost of having a nested class type, rather than an alias to a
functor with the same behavior, means that every associative container with a
different allocator has an identical copy of this class, but with a different
name mangling so that the duplicate functionality cannot be elided or merged.
The class name itself will have a longer mangling than necessary, which would
add up if these functors were genuinely useful and saw much use in practice.

However, it is not clear that these functors do see much actual use. For
example, a quick search with Google Code search while preparing this paper
turned up 29 hits, which were all standard library implementations, or test
drivers for standard library implementations, and a similar result for the
value_comp() function that returns an appropriately constructed
functor from the container for the client to use.

This paper recommends deprecating the value_compare member classes of
the associative containers, and moving their declarations and definitions to
Annex D, just as the old iostreams members were handled in the original 1998
standard. It further recommends making it unspecified whether these members
are provided as member-classes, per the text of the standard, or as aliases to
a class with the same interface, allowing, but not requiring, vendors to choose
to provide these members in a less redundant (but ABI-breaking) manner.

Update from Jacksonville, 2016:

Poll: Replace value_compare by unspecified type with the same semantics
(for after C++17)?

Except for the destructor, member functions of the default allocator shall not
introduce data races (1.10) as a result of concurrent calls to those member
functions from different threads. Calls to these functions that allocate or
deallocate a particular unit of storage shall occur in a single total order,
and each such deallocation call shall happen before the next allocation (if
any) in this order.

pointer address(reference x) const noexcept;

Returns: The actual address of the object referenced by x, even
in the presence of an overloaded operator&.

const_pointer address(const_reference x) const noexcept;

Returns: The actual address of the object referenced by x, even
in the presence of an overloaded operator&.

pointerT* allocate(size_type n, allocator::const_pointer hint = 0);

[ Note: In a container member function, the address of an adjacent
element is often a good choice to pass for the hint argument. - end note
]

Returns: A pointer to the initial element of an array of storage of size
n * sizeof(T), aligned appropriately for objects of type T.
It is implementation-defined whether over-aligned types are supported (3.11).

Remark: the storage is obtained by calling ::operator
new(std::size_t) (18.6.1), but it is unspecified when or how often this
function is called. The use of hint is unspecified, but intended
as an aid to locality if an implementation so desires.

Throws:bad_alloc if the storage cannot be obtained.

void deallocate(pointerT* p, size_type n);

Requires:p shall be a pointer value obtained from
allocate(). n shall equal the value passed as the first
argument to the invocation of allocate which returned p.

Effects: Deallocates the storage referenced by p.

Remarks: Uses ::operator delete(void*, std::size_t) (18.6.1),
but it is unspecified when this function is called.

size_type max_size() const noexcept;

Returns: The largest value N for which the call
allocate(N,0) might succeed.

raw_storage_iterator is provided to enable algorithms to store their
results into uninitialized memory. The template parameter
OutputIterator is required to have its operator* return an
object for which operator& is defined and returns a pointer to
T, and is also required to satisfy the requirements of an output
iterator (24.2.4).

Effects: Obtains a pointer to uninitialized, contiguous storage for
N adjacent objects of type T, for some non-negative number
N. It is implementation-defined whether over-aligned types are supported
(3.11).

Remarks: Calling get_temporary_buffer with a positive number
n is a non-binding request to return storage for n objects of
type T. In this case, an implementation is permitted to return instead
storage for a non-negative number N of such objects, where
N!= n (including N == 0). [ Note: The
request is non-binding to allow latitude for implementation-specific
optimizations of its memory management. - end note ]

Returns: If n <= 0 or if no storage could be obtained,
returns a pair P such that P.first is a null pointer value
and P.second == 0; otherwise returns a pair P such that
P.first refers to the address of the uninitialized storage and
P.second refers to its capacity N (in the units of
sizeof(T)).

template <class T> void return_temporary_buffer(T* p);

Effects: Deallocates the storage referenced by p.

Requires:p shall be a pointer value returned by an earlier
call to get_temporary_buffer that has not been invalidated by an
intervening call to return_temporary_buffer(T*).

Returns: The actual address of the object referenced by x, even
in the presence of an overloaded operator&.

const T* address(const_reference x) const noexcept;

Returns: The actual address of the object referenced by x, even
in the presence of an overloaded operator&.

T* allocate(size_t, const void* hint);

Returns: A pointer to the initial element of an array of storage of size
n * sizeof(T), aligned appropriately for objects of type T.
It is implementation-defined whether over-aligned types are supported (3.11).

Remark: the storage is obtained by calling
::operator new(std::size_t) (18.6.1), but it is unspecified when or
how often this function is called.

raw_storage_iterator is provided to enable algorithms to store their
results into uninitialized memory. The template parameter
OutputIterator is required to have its operator* return an
object for which operator& is defined and returns a pointer to
T, and is also required to satisfy the requirements of an output
iterator (24.2.4).

explicit raw_storage_iterator(OutputIterator x);

Effects: Initializes the iterator to point to the same value to which
x points.

raw_storage_iterator& operator*();

Returns:*this

raw_storage_iterator& operator=(const T& element);

Requires:T shall be CopyConstructible.

Effects: Constructs a value from element at the location to
which the iterator points.

Returns: A reference to the iterator.

raw_storage_iterator& operator=(T&& element);

Requires:T shall be MoveConstructible.

Effects: Constructs a value from std::move(element) at the
location to which the iterator points.

Returns: A reference to the iterator.

raw_storage_iterator& operator++();

Effects: Pre-increment: advances the iterator and returns a reference to
the updated iterator.

raw_storage_iterator operator++(int);

Effects: Post-increment: advances the iterator and returns the old value
of the iterator.

OutputIterator base() const;

Returns: An iterator of type OutputIterator that points to the
same value as *this points to.

Effects: Obtains a pointer to uninitialized, contiguous storage for
N adjacent objects of type T, for some non-negative number
N. It is implementation-defined whether over-aligned types are supported
(3.11).

Remarks: Calling get_temporary_buffer with a positive number
n is a non-binding request to return storage for n objects of
type T. In this case, an implementation is permitted to return instead
storage for a non-negative number N of such objects, where
N!= n (including N == 0). [ Note: The
request is non-binding to allow latitude for implementation-specific
optimizations of its memory management. - end note ]

Returns: If n <= 0 or if no storage could be obtained,
returns a pair P such that P.first is a null pointer value
and P.second == 0; otherwise returns a pair P such that
P.first refers to the address of the uninitialized storage and
P.second refers to its capacity N (in the units of
sizeof(T)).

template <class T> void return_temporary_buffer(T* p);

Effects: Deallocates the storage referenced by p.

Requires:p shall be a pointer value returned by an earlier
call to get_temporary_buffer that has not been invalidated by an
intervening call to return_temporary_buffer(T*).