1. Changelog
-
R0:
-
Wording drafted by Arthur O’Dwyer.
-
Added discussion of freestanding,
, and preconditions.noexcept
-
2. Motivation and proposal
[P0843] introduced
to the C++26 working draft. [P3160R1] proposes that
should support an allocator parameter, thus allowing it
to use that allocator’s
and
members, even though it never needs to
.
(1) This permits
to interoperate with allocator-extended client code, thus
making it a better drop-in replacement for
. For example:
| |
|
|
(2) This permits using
with a PMR allocator. The vector’s elements are still
allocated directly in-place inside the memory footprint of the
object, but
the elements in turn are constructed with allocator-extended construction.
Without allocator-awareness, the code is cumbersome and error-prone. Notice that neither
snippet below ever allocates from the global heap.
| |
|
|
(3) This permits using
with a Boost.Interprocess allocator,
with the same benefits as the PMR example above: less code to write, and
fewer chances to write bugs by accident.
See "Boost.Interprocess, and
" (2024-08-23).
| |
|
|
|
|
(4) This gives the programmer control over the
and
members
of
, permitting a predictably smaller (or larger) memory footprint.
Now, STL vendors could implement memory-footprint optimizations to store the
member
in fewer bits when
was small; but we have seen from our experience with
that vendors do not do such optimizations in practice, and then get locked out of them
forever by ABI. Giving control to the programmer is more general and reduces the
STL vendor’s own workload. (Godbolt.)
|
|
Note: Mital Ashok’s draft implementation of
for libc++ ([Mital]) does store the
in a reduced footprint,
based on the absolute value of
. That is, we have "reduced the STL vendor’s workload" in an area where libc++
has already done that work! libc++ also uses a reduced footprint for
’s index in its "unstable ABI" mode.
This proposal doesn’t forbid an implementation like Mital’s; libc++ is free to continue using a reduced footprint even after this proposal.
But certainly argument (4) in favor is weak: the space-saving is superfluous (in libc++'s case) and non-portable
(since different STL vendors may continue to choose different types for the
data member regardless of
). From the user-programmer’s point of view, it matters only that there is some way to
store the
member in fewer than eight bytes: either every STL vendor commits to store
in the minimum possible number of bytes,
or this proposal is adopted and every STL vendor commits to store
in a type no larger than
.
2.1. Notes on freestanding
is freestanding. We don’t propose to change that.
But now it’s a synonym for
.
So,
needs to become freestanding.
Freestanding implementations lack a heap, so
becomes
a freestanding-deleted member function ([freestanding.item]/3),
which means that it may be
’d if not provided by a freestanding implementation.
This is fine because
doesn’t use
or
.
is already freestanding in C++23, thanks to Ben Craig’s [P1642].
But it is UB to instantiate
unless
meets
the allocator requirements ([allocator.requirements]/3),
and the allocator requirements require that
must be present!
We could solve this conundrum in any of the following ways:
-
Declare by fiat: "All specializations of
meet the allocator requirements, regardless of whether they provideallocator
andallocate
member functions." (But what about program-defined types that derive fromdeallocate
?)allocator -
Change the effects of
to permit it to Do Something Else on freestanding implementations. (But this makes it untrustworthy. This is a bad idea.)allocator < T >:: allocate -
Change the allocator requirements to permit
andallocate
to be missing on freestanding implementations (only).deallocate
Arthur drafted wording for the third option, but Pablo suggests taking a variation on
that approach that basically anticipates Ben Craig’s [P3295] — marking
and
with a new "freestanding-consteval" category — except that instead of copying large
portions of [P3295] into this paper, we simply achieve the same effect with a Note.
If [P3295] is adopted, we can easily update our Note to use the freestanding-consteval markup instead.
2.2. Notes on feature-test macros
For the non-freestanding case, it’s easy: vendors just bump
and are done.
This subsection exists only because the freestanding situation is messier than that,
because we are making
partially freestanding.
This situation falls under [P2198]'s subsection labeled "Detect C++20 (or older) feature that is now partially required in freestanding." Ben Craig, in email, suggests this four-macro design.
Macro | Meaning |
---|---|
(exists)
| If , P0843 is fully implemented; if , allocator-aware is fully implemented
|
(new)
| At least the freestanding subset of is available (but e.g. the freestanding-deleted iterator-pair constructor might be d)
|
(new)
| is fully implemented (including and )
|
(new)
| At least the freestanding subset of is available (but e.g. might be d)
|
Note: The freestanding-deleted members, such as the iterator-pair constructor, are freestanding-deleted not because they allocate (they don’t) but because they throw.
The existing feature-test macros related to allocator support on freestanding are C++23’s
(the last major
change to
/
), C++26’s
, and C++26’s
(effectively indicates whether heap allocation is available).
2.3. Notes on allocator propagation
The STL uses allocators both for allocation/deallocation and for construction/destruction. So the happy path for STL containers is when the allocator sticks alongside both the value of the data pointer (which must be deallocated) and the lifetimes of the element objects (which must be destroyed).
Containers that store their elements on the heap want POCCA/POCMA/POCS to be true
: they’ll
simply swap the allocator alongside the data pointer.
When POCCA/POCMA/POCS is false
, those containers are unhappy: their constant-time move operations
become linear-time and throwing, and their
(which refuses to become throwing) requires that the two
allocators be equal, on pain of UB.
wants POCCA/POCMA/POCS to be false
!
Each element of
is itself an object that was constructed via
and must eventually be destroyed by
. Therefore, as long as
has
any elements,
must never forget about
. When POCCA/POCMA/POCS is false
, this is easy and natural.
When POCCA/POCMA/POCS is true
,
is unhappy: its copy-assignment, move-assignment, and
require for correctness that the two allocators be equal, on pain of UB.
For an example involving
, see this Godbolt.
Operation | Heap-storage
|
|
---|---|---|
Move construction | Non-allocating and O(1) | Non-allocating and O(n) |
Copy construction | O(n) | non-allocating and O(n) |
Move assignment | Non-allocating and O(1) by default.
If and , it becomes allocating and O(n)
| Non-allocating and O(n).
If and , it may have UB
|
Copy assignment | O(n) | Non-allocating and O(n).
If and , it may have UB
|
Swap | Non-allocating, , and O(1).
If and , it may have UB
| Non-allocating and O(n).
If and , it may have UB
|
We follow the Lakos rule and make each special member function
whenever
it may have UB.
Theoretically, we could special-case the preconditions to make it not UB to swap/assign two vectors
with unequal propagating allocators if either vector is empty — because then
there’s no insurmountable physical difficulty with
.
However, that idea would complicate the spec, make more work for vendors, and make the UB condition
less legible to users. So we don’t do that.
2.3.1. Notes on preconditions
For
in particular, even if we assume the "construct/destroy" model instead of the "uses-allocator" model,
Arthur and Pablo disagree as to what would be the appropriate precondition w.r.t. allocator equality.
Arthur’s proposed wording is as follows.
The first part of the Precondition element is modeled on [alg.swap]/2 (as used by
), and the second part (the part
dealing with allocators) is modeled
on [container.reqmts]/65.
constexpr void swap ( inplace_vector & x ) noexcept ( see below ); x․ Preconditions: Let M be
. For each non-negative integer n < M,
min ( size (), x . size ()) is swappable with ([swappable.requirements])
( * this )[ n ] . If
x [ n ] is
allocator_traits < allocator_type >:: propagate_on_container_swap :: value true
, thenmeets the
allocator_type requirements and
Cpp17Swappable .
get_allocator () == x . get_allocator () x․ Effects: [...]
Pablo would prefer something more like this:
constexpr void swap ( inplace_vector & x ) noexcept ( see below ); x․ Preconditions: Let
and
a1 be
a2 and
get_allocator () , respectively;
x . get_allocator () and
p1 be pointers of type
p2 to storage suitable to hold an object of type
T * ; and
T and
args1 be function parameter packs such that
args2 and
T ( args1 ...) are well-formed and well-defined. The following shall be well-formed and well-defined:
T ( args2 ...) allocator_traits < allocator_type >:: construct ( a1 , p1 , args1 ...); allocator_traits < allocator_type >:: construct ( a2 , p2 , args2 ...); using std :: swap ; swap ( * p1 , * p2 ); if constexpr ( allocator_traits < allocator_type >:: propagate_on_container_swap :: value ) swap ( a1 , a2 ); allocator_traits < allocator_type >:: destroy ( a1 , p1 ); allocator_traits < allocator_type >:: destroy ( a2 , p2 ); [Note: In other words, when
is
propagate_on_container_swap :: value false
, it must be valid to swap the individual elements even when the allocators do not compare equal. Conversely, whenis
propagate_on_container_swap :: value true
, it must be valid to destroy the swapped elements using the swapped allocators, even though the call touses a different allocator than the corresponding call to
destroy . —end note]
construct x․ Effects: [...]
We encourage discussion as to which direction LEWG prefers.
Arthur’s precondition has the benefit of being checkable at runtime,
because it involves the values of computable expressions like
.
An implementation would be allowed to assert-fail if the precondition was violated, using something like
SG14’s
macro or libc++'s
macro.
Pablo’s precondition is not checkable at runtime, but has the benefit of being looser: it permits the user to swap
s with allocators that are "unequal, yet functionally interchangeable." The
implementation would be forbidden to assert-fail inside
or
.
It is not possible to violate either precondition with
(which is always equal) nor with
(which doesn’t propagate).
[LWG4151] is related:
is already missing a precondition today.
Both of the above-proposed preconditions are supersets of LWG4151’s proposed resolution.
2.4. Notes on noexceptness
We expect that in practice, STL vendors will take approximately the same approach to noexceptness
which
has taken. This approach is:
static constexpr bool CopyCtorIsNoexcept = (( std :: is_nothrow_copy_constructible_v < T > && sg14 :: aaipv :: has_trivial_construct < Alloc , T , const T &>:: value ) || ( N == 0 )) && sg14 :: aaipv :: propagate_on_container_copy_construction < Alloc >:: value ; static constexpr bool MoveCtorIsNoexcept = (( std :: is_nothrow_move_constructible_v < T > && sg14 :: aaipv :: has_trivial_construct < Alloc , T , T &&>:: value ) || ( N == 0 )); static constexpr bool CopyAssignIsNoexcept = (( std :: is_nothrow_copy_constructible_v < T > && std :: is_nothrow_copy_assignable_v < T > && std :: is_nothrow_destructible_v < T > && sg14 :: aaipv :: has_trivial_construct < Alloc , T , const T &>:: value && sg14 :: aaipv :: has_trivial_destroy < Alloc , T >:: value ) || ( N == 0 )) && ( std :: allocator_traits < Alloc >:: is_always_equal :: value || ! std :: allocator_traits < Alloc >:: propagate_on_container_copy_assignment :: value ); static constexpr bool MoveAssignIsNoexcept = (( std :: is_nothrow_move_constructible_v < T > && std :: is_nothrow_move_assignable_v < T > && std :: is_nothrow_destructible_v < T > && sg14 :: aaipv :: has_trivial_construct < Alloc , T , T &&>:: value && sg14 :: aaipv :: has_trivial_destroy < Alloc , T >:: value ) || ( N == 0 )) && ( std :: allocator_traits < Alloc >:: is_always_equal :: value || ! std :: allocator_traits < Alloc >:: propagate_on_container_move_assignment :: value ); static constexpr bool SwapIsNoexcept = (( std :: is_nothrow_swappable_v < T > && std :: is_nothrow_move_constructible_v < T > && sg14 :: aaipv :: has_trivial_construct < Alloc , T , T &&>:: value && sg14 :: aaipv :: has_trivial_destroy < Alloc , T >:: value ) || ( N == 0 )) && ( std :: allocator_traits < Alloc >:: is_always_equal :: value || ! std :: allocator_traits < Alloc >:: propagate_on_container_swap :: value );
Here
is true
iff
is defaulted to simply copy the allocator (which cannot throw);
is true
iff
is defaulted
to simply call
; and
is true
iff
is
defaulted to simply call
. STL vendors might reasonably choose to further specialize these
helpers for
and
.
However, for wording purposes, we can’t use these helpers because they aren’t standardized: we don’t have any
easy way to invoke them in the wording. So our proposed wording is written as if each helper trait always yields false
,
but in a way that (we intend) permits the STL vendor to strengthen the noexcept guarantee — and we hope that they will do so!
Note: [res.on.exception.handling]/5 permits STL vendors
only to add
to function signatures that completely lack it; it doesn’t permit vendors to strengthen
into
. This is unfortunate.
Our exception-specifications don’t mention the copy/move/equality operations on
itself,
because [allocator.requirements] forbids these
operations to throw (on pain of UB).
3. Implementation experience
Arthur has implemented § 5 Proposed wording in [QuuxplusoneSG14] (see Godbolt). This includes extensive unit tests verifying that:
-
The proposed
is an exact drop-in replacement for the originalinplace_vector < T , N , std :: allocator < T >>
, with the same sizeof, alignof, triviality, and noexcept.inplace_vector < T , N > -
The proposed
works correctly withinplace_vector < T , N , Alloc >
.std :: pmr -
The proposed
works correctly with Boost.Interprocess.inplace_vector < T , N , Alloc > -
The proposed
works correctly with theinplace_vector < T , N , Alloc >
depicted above.Tiny :: Alloc < T >
4. Acknowledgments
Thanks to Pablo Halpern for [P3160R1] and [P3160R2].
Thanks to Ben Craig for his design of the feature-test macros and for discussion of the approach
to "freestanding
." Thanks to Gonzalo Brito and Mital Ashok for their review of this design and wording.
Thanks to Casey Carter for his review of this wording.
5. Proposed wording
In this section, wording in
strikethrough
is to be deleted; wording in
green
is to be added
no matter which alternative we go with;
wording in
5.1. [version.syn]
Modify [version.syn] as follows, inserting these macros into the list in the proper alphabetical positions:
#define __cpp_lib_allocator YYYYMML // also in <memory> #define __cpp_lib_freestanding_allocator YYYYMML // freestanding, also in <memory> #define __cpp_lib_inplace_vector 202406L // also in <inplace_vector> #define __cpp_lib_inplace_vector YYYYMML // also in <inplace_vector> #define __cpp_lib_freestanding_inplace_vector YYYYMML // freestanding, also in <inplace_vector>
5.2. [memory.syn]
Modify [memory.syn] as follows:
// [default.allocator], the default allocator template < class T > class allocator ; // partially freestanding template < class T , class U > constexpr bool operator == ( const allocator < T >& , const allocator < U >& ) noexcept ; // freestanding
5.3. [default.allocator]
Modify [default.allocator] as follows:
namespace std { template < class T > class allocator { public : using value_type = T ; using size_type = size_t ; using difference_type = ptrdiff_t ; using propagate_on_container_move_assignment = true_type ; constexpr allocator () noexcept ; constexpr allocator ( const allocator & ) noexcept ; template < class U > constexpr allocator ( const allocator < U >& ) noexcept ; constexpr ~ allocator (); constexpr allocator & operator = ( const allocator & ) = default ; constexpr T * allocate ( size_t n ); // Note 1 constexpr allocation_result < T *> allocate_at_least ( size_t n ); // Note 1 constexpr void deallocate ( T * p , size_t n ); // Note 1 }; } x․ [Note: For a freestanding implementation, it is implementation-defined whether
,
allocate , and
allocate_at_least are
deallocate rather than
consteval . —end note]
constexpr 2․
is
allocator_traits < allocator < T >>:: is_always_equal :: value true
for any.
T
5.4. [container.alloc.reqmts]
Modify [container.alloc.reqmts] as follows:
1․ Except for
array and, all of the containers defined in [containers], [stacktrace.basic], [basic.string], and [re.results] meet the additional requirements of an allocator-aware container, as described below.
inplace_vector 2․ Given an allocator type
and given a container type
A having a
X identical to
value_type and an
T identical to
allocator_type and given an lvalue
allocator_traits < A >:: rebind_alloc < T > of type
m , a pointer
A of type
p , an expression
T * that denotes an lvalue of type
v or
T or an rvalue of type
const T , and an rvalue
const T of type
rv , the following terms are defined.
T If Ifis a specialization of
X , the terms below are defined as if
inplace_vector were
A .
scoped_allocator_adaptor < allocator < T > , allocator_type > is not allocator-aware or is a specialization of
X , the terms below are defined as if
basic_string were
A — no allocator object needs to be created and user specializations of
allocator < T > are not instantiated:
allocator < T > (2.1) —
is Cpp17DefaultInsertable into
T means that the following expression is well-formed:
X allocator_traits < A >:: construct ( m , p ) (2.2) — An element of
is default-inserted if it is initialized by evaluation of the expression
X whereallocator_traits < A >:: construct ( m , p ) is the address of the uninitialized storage for the element allocated within
p .
X (2.3) —
is Cpp17MoveInsertable into
T means that the following expression is well-formed:
X and its evaluation causes the following postcondition to hold: The value ofallocator_traits < A >:: construct ( m , p , rv ) is equivalent to the value of
* p before the evaluation. [Note:
rv remains a valid object. Its state is unspecified. —end note]
rv (2.4) —
is Cpp17CopyInsertable into
T means that, in addition to
X being Cpp17MoveInsertable into
T , the following expression is well-formed:
X and its evaluation causes the following postcondition to hold: The value ofallocator_traits < A >:: construct ( m , p , v ) is unchanged and is equivalent to
v .
* p (2.5) —
is Cpp17EmplaceConstructible into
T from
X , for zero or more arguments
args , means that the following expression is well-formed:
args allocator_traits < A >:: construct ( m , p , args ) (2.6) —
is Cpp17Erasable from
T means that the following expression is well-formed:
X [Note: A container callsallocator_traits < A >:: destroy ( m , p ) to construct an element at
allocator_traits < A >:: construct ( m , p , args ) using
p , with
args
m == get_allocator () (or . The defaultin the case of
m == A ( allocator < T > (), get_allocator ()) )
inplace_vector in
construct will call
allocator , but specialized allocators can choose a different definition. —end note]
:: new (( void * ) p ) T ( args ) [...]
A type
meets the allocator-aware container requirements if
X meets the container requirements and the following types, statements, and expressions are well-formed and have the specified semantics.
X typename X :: allocator_type 4․ Result:
A 5․ Mandates:
is the same as
allocator_type :: value_type .
X :: value_type c . get_allocator () 6․ Result:
A 7․ Complexity: Constant.
X u ; X u = X (); 8․ Preconditions:
meets the Cpp17DefaultConstructible requirements.
A 9․ Postconditions:
returns
u . empty () true
,.
u . get_allocator () == A () 10․ Complexity: Constant.
X u ( m ); 11․ Postconditions:
returns
u . empty () true
,.
u . get_allocator () == m 12․ Complexity: Constant.
X u ( t , m ); 13․ Preconditions:
is Cpp17CopyInsertable into
T .
X 14․ Postconditions:
,
u == t
u . get_allocator () == m 15․ Complexity: Linear.
X u ( rv ); 16․ Postconditions:
has the same elements as
u had before this construction; the value of
rv is the same as the value of
u . get_allocator () before this construction.
rv . get_allocator () 17․ Complexity: Constant.
X u ( rv , m ); 18․ Preconditions:
is Cpp17MoveInsertable into
T .
X 19․ Postconditions:
has the same elements, or copies of the elements, that
u had before this construction,
rv .
u . get_allocator () == m 20․ Complexity: Constant if
, otherwise linear.
m == rv . get_allocator () a = t 21․ Result:
.
X & 22․ Preconditions:
is Cpp17CopyInsertable into
T and Cpp17CopyAssignable.
X 23․ Postconditions:
is true.
a == t 24․ Complexity: Linear.
a = rv 25․ Result: X&.
26․ Preconditions: If
is
allocator_traits < allocator_type >:: propagate_on_container_move_assignment :: value false
,is Cpp17MoveInsertable into
T and Cpp17MoveAssignable.
X 27․ Effects: All existing elements of
are either move assigned to or destroyed.
a 28․ Postconditions: If
and
a do not refer to the same object,
rv is equal to the value that
a had before this assignment.
rv 29․ Complexity: Linear.
a . swap ( b ) 30․ Result:
void 31․ Effects: Exchanges the contents of
and
a .
b 32․ Complexity: Constant.
5.5. [inplace.vector.syn]
Modify [inplace.vector.syn] as follows:
24.3.7 Header
synopsis [inplace.vector.syn]
< inplace_vector > // mostly freestanding #include <compare>// see [compare.syn] #include <initializer_list>// see [initializer.list.syn] namespace std { // exposition-only type traits template < class T , class A , class ... X > constexpr bool is - nothrow - ua - constructible - v = see below ; // exposition only
// [inplace.vector], class template inplace_vector template < class T , size_t N , class Allocator > class inplace_vector ; // partially freestanding // [inplace.vector.erasure], erasure template < class T , size_t N , class Allocator , class U = T > constexpr typename inplace_vector < T , N , Allocator >:: size_type erase ( inplace_vector < T , N , Allocator >& c , const U & value ); template < class T , size_t N , class Allocator , class Predicate > constexpr typename inplace_vector < T , N , Allocator >:: size_type erase_if ( inplace_vector < T , N , Allocator >& c , Predicate pred ); namespace pmr { template < class T , size_t N > using inplace_vector = std :: inplace_vector < T , N , polymorphic_allocator < T >> ; } }
5.6. [inplace.vector]
Modify [inplace.vector] as follows:
24.3.14 Class template
[inplace.vector]
inplace_vector 24.3.14.1 Overview [inplace.vector.overview]
1․ An
is a contiguous container. Its capacity is fixed and its elements are stored within the
inplace_vector object itself. [Note: Although it is an allocator-aware container,
inplace_vector uses its allocator only to construct and destroy
inplace_vector allocator-aware elements; it never directly instantiates theor
allocate member functions of its
deallocate .— end note]
allocator_type 2․ An
meets all of the requirements of a container ([container.reqmts]), of a reversible container ([container.rev.reqmts]), of an allocator-aware container ([container.alloc.reqmts]), of a contiguous container, and of a sequence container, including most of the optional sequence container requirements ([sequence.reqmts]). The exceptions are the
inplace_vector ,
push_front ,
prepend_range , and
pop_front member functions, which are not provided. Descriptions are provided here only for operations on
emplace_front that are not described in one of these tables or for operations where there is additional semantic information.
inplace_vector 3․ For any
,
N and
inplace_vector < T , N , allocator < T > >:: iterator meet the constexpr iterator requirements.
inplace_vector < T , N , allocator < T > >:: const_iterator 4․ For any
> 0 and any
N , if
Allocator is
is_trivial_v < T > false
, then nomember functions are usable in constant expressions.
inplace_vector < T , N , Allocator > 5․ Any member function of
that would cause the size to exceed
inplace_vector < T , N , Allocator > throws an exception of type
N .
bad_alloc 6․ Let
denote a specialization
IV of
inplace_vector < T , N >
inplace_vector < T , N , allocator < T >> inplace_vector<T, N, A> . Ifis zero, then
N is
IV empty. If both trivial and empty. Otherwise:is zero and either
N is
is_trivial_v < A > true
oris a specialization of
A , then
allocator is
IV
- (6.1) If
is
is_trivially_copy_constructible_v < T > && is_trivially_copy_constructible_v < A > true
and , thendoes not exist
A :: select_on_container_copy_construction has a trivial copy constructor.
IV - (6.2) If
is
is_trivially_move_constructible_v < T > && is_trivially_move_constructible_v < A > true
, thenhas a trivial move constructor.
IV - (6.3) If
is
is_trivially_destructible_v < T > true
, then:
- (6.3.1)
If is
is_trivially_destructible_v < A > true
, thenhas a trivial destructor.
IV - (6.3.2) If
is
is_trivially_copy_constructible_v < T > && is_trivially_copy_assignable_v < T > && is_trivially_copy_assignable_v < A > && ( allocator_traits < A >:: propagate_on_container_copy_assignment :: value || allocator_traits < A >:: is_always_equal :: value ) true
, thenhas a trivial copy assignment operator.
IV - (6.3.3) If
is
is_trivially_move_constructible_v < T > && is_trivially_move_assignable_v < T > && is_trivially_move_assignable_v < A > && ( allocator_traits < A >:: propagate_on_container_move_assignment :: value || allocator_traits < A >:: is_always_equal :: value ) true
, thenhas a trivial move assignment operator.
IV
7․ The exposition-only trait is true if uses-allocator construction with an allocator of type
is - nothrow - ua - constructible - v < T , A , X ... > and constructor arguments of types specified by
A ([allocator.uses.construction]) is known to be a non-throwing operation. [Note: This trait can be implemented by instantiating
X ... , where
is_nothrow_constructible_v < T , Y ... > is the set of tuple arguments deduced by
Y ... . —end note]
uses_allocator_construction_args namespace std { template < class T , size_t N , class Allocator = allocator < T > > class inplace_vector { public : // types: using value_type = T ; using allocator_type = Allocator ; using pointer = T * typename allocator_traits < Allocator >:: pointer ; using const_pointer = const T * typename allocator_traits < Allocator >:: const_pointer ; using reference = value_type & ; using const_reference = const value_type & ; using size_type = size_t implementation - defined ; // see [container.requirements] using difference_type = ptrdiff_t implementation - defined ; // see [container.requirements] using iterator = implementation - defined ; // see [container.requirements] using const_iterator = implementation - defined ; // see [container.requirements] using reverse_iterator = std :: reverse_iterator < iterator > ; using const_reverse_iterator = std :: reverse_iterator < const_iterator > ; // [inplace.vector.cons], construct/copy/destroy constexpr inplace_vector () noexcept ( noexcept ( Allocator ())) : inplace_vector ( Allocator ()) { } ; constexpr explicit inplace_vector ( const Allocator & ) noexcept ; constexpr explicit inplace_vector ( size_type n , const Allocator & = Allocator () ); // freestanding-deleted constexpr inplace_vector ( size_type n , const T & value , const Allocator & = Allocator () ); // freestanding-deleted template < class InputIterator > constexpr inplace_vector ( InputIterator first , InputIterator last , const Allocator & = Allocator () ); // freestanding-deleted template < container - compatible - range < T > R > constexpr inplace_vector ( from_range_t , R && rg , const Allocator & = Allocator () ); // freestanding-deleted constexpr inplace_vector ( const inplace_vector & ); constexpr inplace_vector ( inplace_vector && ) noexcept ( N == 0 || is_nothrow_move_constructible_v < T > ) noexcept ( N == 0 || is - nothrow - ua - constructible - v < T , Allocator , T &&> ) noexcept ( see below ) ; constexpr inplace_vector ( const inplace_vector & rhs , const type_identity_t < Allocator >& ); constexpr inplace_vector ( inplace_vector && rhs , const type_identity_t < Allocator >& ); constexpr inplace_vector ( initializer_list < T > il , const Allocator & = Allocator () ); // freestanding-deleted constexpr ~ inplace_vector (); constexpr inplace_vector & operator = ( const inplace_vector & other rhs ); constexpr inplace_vector & operator = ( inplace_vector && other rhs ) noexcept ( N == 0 || ( is_nothrow_move_assignable_v < T > && is_nothrow_move_constructible_v < T > )) noexcept ( N == 0 || ( is_nothrow_move_assignable_v < T > && is - nothrow - ua - constructible - v < T , Allocator , T &&> )) noexcept ( see below ) ; constexpr inplace_vector & operator = ( initializer_list < T > ); // freestanding-deleted template < class InputIterator > constexpr void assign ( InputIterator first , InputIterator last ); // freestanding-deleted template < container - compatible - range < T > R > constexpr void assign_range ( R && rg ); // freestanding-deleted constexpr void assign ( size_type n , const T & u ); // freestanding-deleted constexpr void assign ( initializer_list < T > il ); // freestanding-deleted constexpr allocator_type get_allocator () const noexcept ; // iterators constexpr iterator begin () noexcept ; constexpr const_iterator begin () const noexcept ; constexpr iterator end () noexcept ; constexpr const_iterator end () const noexcept ; constexpr reverse_iterator rbegin () noexcept ; constexpr const_reverse_iterator rbegin () const noexcept ; constexpr reverse_iterator rend () noexcept ; constexpr const_reverse_iterator rend () const noexcept ; constexpr const_iterator cbegin () const noexcept ; constexpr const_iterator cend () const noexcept ; constexpr const_reverse_iterator crbegin () const noexcept ; constexpr const_reverse_iterator crend () const noexcept ; // [inplace.vector.capacity] size/capacity constexpr bool empty () const noexcept ; constexpr size_type size () const noexcept ; static constexpr size_type max_size () noexcept ; static constexpr size_type capacity () noexcept ; constexpr void resize ( size_type sz ); // freestanding-deleted constexpr void resize ( size_type sz , const T & c ); // freestanding-deleted static constexpr void reserve ( size_type n ); // freestanding-deleted static constexpr void shrink_to_fit () noexcept ; // element access constexpr reference operator []( size_type n ); constexpr const_reference operator []( size_type n ) const ; constexpr reference at ( size_type n ); // freestanding-deleted constexpr const_reference at ( size_type n ) const ; // freestanding-deleted constexpr reference front (); constexpr const_reference front () const ; constexpr reference back (); constexpr const_reference back () const ; // [inplace.vector.data], data access constexpr T * data () noexcept ; constexpr const T * data () const noexcept ; // [inplace.vector.modifiers], modifiers template < class ... Args > constexpr reference emplace_back ( Args && ... args ); // freestanding-deleted constexpr reference push_back ( const T & x ); // freestanding-deleted constexpr reference push_back ( T && x ); // freestanding-deleted template < container - compatible - range < T > R > constexpr void append_range ( R && rg ); // freestanding-deleted constexpr void pop_back (); template < class ... Args > constexpr pointer T * try_emplace_back ( Args && ... args ); constexpr pointer T * try_push_back ( const T & x ); constexpr pointer T * try_push_back ( T && x ); template < container - compatible - range < T > R > constexpr ranges :: borrowed_iterator_t < R > try_append_range ( R && rg ); template < class ... Args > constexpr reference unchecked_emplace_back ( Args && ... args ); constexpr reference unchecked_push_back ( const T & x ); constexpr reference unchecked_push_back ( T && x ); template < class ... Args > constexpr iterator emplace ( const_iterator position , Args && ... args ); // freestanding-deleted constexpr iterator insert ( const_iterator position , const T & x ); // freestanding-deleted constexpr iterator insert ( const_iterator position , T && x ); // freestanding-deleted constexpr iterator insert ( const_iterator position , size_type n , // freestanding-deleted const T & x ); template < class InputIterator > constexpr iterator insert ( const_iterator position , // freestanding-deleted InputIterator first , InputIterator last ); template < container - compatible - range < T > R > constexpr iterator insert_range ( const_iterator position , R && rg ); // freestanding-deleted constexpr iterator insert ( const_iterator position , // freestanding-deleted initializer_list < T > il ); constexpr iterator erase ( const_iterator position ); constexpr iterator erase ( const_iterator first , const_iterator last ); constexpr void swap ( inplace_vector & x ) noexcept ( N == 0 || ( is_nothrow_swappable_v < T > && is_nothrow_move_constructible_v < T > )) noexcept ( N == 0 || ( is_nothrow_swappable_v < T > && is - nothrow - ua - constructible - v < T , Allocator , T &&> )) noexcept ( see below ) ; constexpr void clear () noexcept ; constexpr friend bool operator == ( const inplace_vector & x , const inplace_vector & y ); constexpr friend synth - three - way - result < T > operator <=> ( const inplace_vector & x , const inplace_vector & y ); constexpr friend void swap ( inplace_vector & x , inplace_vector & y ) noexcept ( N == 0 || ( is_nothrow_swappable_v < T > && is_nothrow_move_constructible_v < T > )) noexcept ( noexcept ( x . swap ( y ))) { x . swap ( y ); } }; }; 24.3.14.2. Constructors [inplace.vector.cons]
DRAFTING NOTE: The use of
on
explicit is consistent with the other STL containers (e.g.
inplace_vector ( n , alloc ) and
vector ).
list constexpr explicit vector ( const Allocator & ); x․ Effects: Constructs an empty
, using the specified allocator.
inplace_vector x․ Complexity: Constant.
constexpr explicit inplace_vector ( size_type n , const Allocator & = Allocator () ); 1․ Preconditions:
is
T into
Cpp17DefaultInsertable .
inplace_vector 2․ Effects: Constructs an
with
inplace_vector default-inserted elements, using the specified allocator.
n 3․ Complexity: Linear in
.
n constexpr inplace_vector ( size_type n , const T & value , const Allocator & = Allocator () ); 4․ Preconditions:
is
T into
Cpp17CopyInsertable .
inplace_vector 5․ Effects: Constructs an
with
inplace_vector copies of
n , using the specified allocator.
value 6․ Complexity: Linear in
.
n template < class InputIterator > constexpr inplace_vector ( InputIterator first , InputIterator last , const Allocator & = Allocator () ); 7․ Effects: Constructs an
equal to the range
inplace_vector , using the specified allocator.
[ first , last ) 8․ Complexity: Linear in
.
distance ( first , last ) template < container - compatible - range < T > R > constexpr inplace_vector ( from_range_t , R && rg , const Allocator & = Allocator () ); 9․ Effects: Constructs an
object with the elements of the range
inplace_vector , using the specified allocator.
rg 10․ Complexity: Linear in
.
ranges :: distance ( rg ) constexpr inplace_vector ( inplace_vector && ) noexcept ( N == 0 || is - nothrow - ua - constructible - v < T , Allocator , T &&> ); noexcept ( see below ); 11․ Preconditions:
is Cpp17MoveInsertable into
T .
inplace_vector 12․ Complexity: Linear.
13․ Remarks: When is
allocator_type , the exception specification is equivalent to:
allocator < value_type > is_nothrow_move_constructible_v < value_type > Otherwise, the exception specification is unspecified. constexpr inplace_vector & operator = ( const inplace_vector & rhs );
14․ Preconditions: is
allocator_traits < allocator_type >:: propagate_on_container_copy_assignment :: value false
or.
rhs . get_allocator () == this -> get_allocator () is Cpp17CopyInsertable into
T and Cpp17CopyAssignable.
inplace_vector constexpr inplace_vector & operator = ( inplace_vector && rhs ) noexcept ( N == 0 || ( is_nothrow_move_assignable_v < T > && is - nothrow - ua - constructible - v < T , Allocator , T &&> )); noexcept ( see below );
15․ Preconditions: is
allocator_traits < allocator_type >:: propagate_on_container_move_assignment :: value false
or.
rhs . get_allocator () == this -> get_allocator () is Cpp17MoveInsertable into
T and Cpp17MoveAssignable.
inplace_vector 16․ Complexity: Linear.
17․ Remarks: When is
allocator_type , the exception specification is equivalent to:
allocator < value_type > is_nothrow_move_assignable_v < value_type > && is_nothrow_move_constructible_v < value_type > Otherwise, the exception specification is unspecified. DRAFTING NOTE:
below is consistent with the other STL containers. CTAD needs it in order to handle this snippet:
type_identity_t
std :: pmr :: inplace_vector < int , 10 > v ; auto w = inplace_vector ( v , & mr ); constexpr inplace_vector ( const inplace_vector & rhs , const type_identity_t < Allocator >& ); 1․ Effects: Constructs an
with the elements of
inplace_vector , using the specified allocator.
rhs 2․ Complexity: Linear in
.
rhs . size () constexpr inplace_vector ( inplace_vector && rhs , const type_identity_t < Allocator >& ); 3․ Effects: Constructs an
with the elements that
inplace_vector had before the construction, using the specified allocator.
rhs 4․ Complexity: Linear in
.
rhs . size () 24.3.14.3 Size and capacity [inplace.vector.capacity]
static constexpr size_type capacity () noexcept ; static constexpr size_type max_size () noexcept ; 1․ Returns:
.
N constexpr void resize ( size_type sz ); 2․ Preconditions:
is
T into
Cpp17DefaultInsertable .
inplace_vector 3․ Effects: If
, erases the last
sz < size () elements from the sequence. Otherwise, appends
size () - sz default-inserted elements to the sequence.
sz - size () 4․ Remarks: If an exception is thrown, there are no effects on
.
* this constexpr void resize ( size_type sz , const T & c ); 5․ Preconditions:
is
T into
Cpp17CopyInsertable .
inplace_vector 6․ Effects: If
, erases the last
sz < size () elements from the sequence. Otherwise, appends
size () - sz copies of
sz - size () to the sequence.
c 7․ Remarks: If an exception is thrown, there are no effects on
.
* this 24.3.14.4 Data [inplace.vector.data]
constexpr T * data () noexcept ; constexpr const T * data () const noexcept ; 1․ Returns: A pointer such that [
,
data () ) is a valid range. For a non-empty
data () + size () ,
inplace_vector is
data () == addressof ( front ()) true
.2․ Complexity: Constant time.
24.3.14.5 Modifiers [inplace.vector.modifiers]
constexpr iterator insert ( const_iterator position , const T & x ); constexpr iterator insert ( const_iterator position , T && x ); constexpr iterator insert ( const_iterator position , size_type n , const T & x ); template < class InputIterator > constexpr iterator insert ( const_iterator position , InputIterator first , InputIterator last ); template < container - compatible - range < T > R > constexpr iterator insert_range ( const_iterator position , R && rg ); constexpr iterator insert ( const_iterator position , initializer_list < T > il ); template < class ... Args > constexpr iterator emplace ( const_iterator position , Args && ... args ); template < container - compatible - range < T > R > constexpr void append_range ( R && rg ); 1․ Let
be the value of
n before this call for the
size () overload, and
append_range otherwise.
distance ( begin , position ) 2․ Complexity: Linear in the number of elements inserted plus the distance to the end of the vector.
3․ Remarks: If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of
or by any
T operation, there are no effects. Otherwise, if an exception is thrown, then
InputIterator ≥
size () and elements in the range
n + [
begin () ,
0 ) are not modified.
n constexpr reference push_back ( const T & x ); constexpr reference push_back ( T && x ); template < class ... Args > constexpr reference emplace_back ( Args && ... args ); 4․ Returns:
.
back () 5․ Throws:
or any exception thrown by the initialization of the inserted element.
bad_alloc 6․ Complexity: Constant.
7․ Remarks: If an exception is thrown, there are no effects on
.
* this template < class ... Args > constexpr pointer T * try_emplace_back ( Args && ... args ); constexpr pointer T * try_push_back ( const T & x ); constexpr pointer T * try_push_back ( T && x ); 8․ Let
denote a pack:
vals
- (8.1)
for the first overload,
std :: forward < Args > ( args )... - (8.2)
for the second overload,
x - (8.3)
for the third overload.
std :: move ( x ) 9․ Preconditions:
is
value_type into
Cpp17EmplaceConstructible from
inplace_vector .
vals ... 10․ Effects: If
is
size () < capacity () true
, appends an object of typedirect-non-list-initialized with
T . Otherwise, there are no effects.
vals ... 11․ Returns:
if
nullptr is
size () == capacity () true
, otherwise.
addressof ( back ()) 12․ Throws: Nothing unless an exception is thrown by the initialization of the inserted element.
13․ Complexity: Constant.
14․ Remarks: If an exception is thrown, there are no effects on
.
* this template < container - compatible - range < T > R > constexpr ranges :: borrowed_iterator_t < R > try_append_range ( R && rg ); 15․ Preconditions:
is
value_type into
Cpp17EmplaceConstructible from
inplace_vector .
* ranges :: begin ( rg ) 16․ Effects: Appends copies of initial elements in
before
rg , until all elements are inserted or
end () . Each iterator in the range
size () == capacity () is trueis dereferenced at most once.
rg 17․ Returns: An iterator pointing to the first element of
that was not inserted into
rg , or
* this if no such element exists.
ranges :: end ( rg ) 18․ Complexity: Linear in the number of elements inserted.
19․ Remarks: Let
be the value of
n prior to this call. If an exception is thrown after the insertion of
size () elements, then
k equals
size () , elements in the range
n + k + [
begin () ,
0 ) are not modified, and elements in the range
n + [
begin () ,
n ) correspond to the inserted elements.
n + k template < class ... Args > constexpr reference unchecked_emplace_back ( Args && ... args ); 20․ Preconditions:
is
size () < capacity () true
.21․ Effects: Equivalent to:
return * try_emplace_back ( std :: forward < Args > ( args )...); constexpr reference unchecked_push_back ( const T & x ); constexpr reference unchecked_push_back ( T && x ); 22․ Preconditions:
is
size () < capacity () true
.23․ Effects: Equivalent to:
return * try_push_back ( std :: forward < decltype ( x ) > ( x )); static constexpr void reserve ( size_type n ); 24․ Effects: None.
25․ Throws:
if
bad_alloc is
n > capacity () true
.static constexpr void shrink_to_fit () noexcept ; 26․ Effects: None.
constexpr iterator erase ( const_iterator position ); constexpr iterator erase ( const_iterator first , const_iterator last ); constexpr void pop_back (); 27․ Effects: Invalidates iterators and references at or after the point of the erase.
28․ Throws: Nothing unless an exception is thrown by the assignment operator or move assignment operator of
.
T 29․ Complexity: The destructor of
is called the number of times equal to the number of the elements erased, but the assignment operator of
T is called the number of times equal to the number of elements after the erased elements.
T constexpr void swap ( inplace_vector & x ) noexcept ( N == 0 || ( is_nothrow_swappable_v < T > && is - nothrow - ua - constructible - v < T , Allocator , T &&> ));
30․ Effects: If is
allocator_traits < A >:: propagate_on_container_swap :: value && ! allocator_traits < A >:: always_compare_equal :: value true
, swap the allocators ofand
* this . Then, swap the first
x elements of
m and
* this , where
x is the smaller of
m and
this -> size () . Then, move-insert the remaining elements from the longer
x . size () to the end of the shorter one, then erase those elements from the longer
inplace_vector .
inplace_vector
32․ Complexity: Linear. constexpr void swap ( inplace_vector & x ) noexcept ( see below );
30․ Preconditions: Let M be . For each non-negative integer n < M,
min ( size (), x . size ()) is swappable with ([swappable.requirements])
( * this )[ n ] . If
x [ n ] is
allocator_traits < allocator_type >:: propagate_on_container_swap :: value true
, thenmeets the
allocator_type requirements and
Cpp17Swappable .
get_allocator () == x . get_allocator ()
31․ Effects: Exchanges the contents of and
* this .
x
32․ Complexity: Linear.
33․ Remarks: When is
allocator_type , the exception specification is equivalent to:
allocator < value_type > is_nothrow_swappable_v < value_type > && is_nothrow_move_constructible_v < value_type > Otherwise, the exception specification is unspecified. DRAFTING NOTE: The exception specification above is modeled on [optional.swap]/5.
24.3.14.6 Erasure [inplace.vector.erasure]
template < class T , size_t N , class Allocator , class U = T > constexpr size_t typename inplace_vector < T , N , Allocator >:: size_type erase ( inplace_vector < T , N , Allocator >& c , const U & value ); 1․ Effects: Equivalent to:
auto it = remove ( c . begin (), c . end (), value ); auto r = distance ( it , c . end ()); c . erase ( it , c . end ()); return r ; template < class T , size_t N , class Allocator , class Predicate > constexpr size_t typename inplace_vector < T , N , Allocator >:: size_type erase_if ( inplace_vector < T , N , Allocator >& c , Predicate pred ); 2․ Effects: Equivalent to:
auto it = remove_if ( c . begin (), c . end (), pred ); auto r = distance ( it , c . end ()); c . erase ( it , c . end ()); return r ;