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 ] . Ifx [ n ] isallocator_traits < allocator_type >:: propagate_on_container_swap :: value true, thenmeets theallocator_type requirements andCpp17Swappable .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
anda1 bea2 andget_allocator () , respectively;x . get_allocator () andp1 be pointers of typep2 to storage suitable to hold an object of typeT * ; andT andargs1 be function parameter packs such thatargs2 andT ( 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
ispropagate_on_container_swap :: value false, it must be valid to swap the individual elements even when the allocators do not compare equal. Conversely, whenispropagate_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 todestroy . —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 , andallocate_at_least aredeallocate rather thanconsteval . —end note]constexpr 2․
isallocator_traits < allocator < T >>:: is_always_equal :: value truefor 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 typeA having aX identical tovalue_type and anT identical toallocator_type and given an lvalueallocator_traits < A >:: rebind_alloc < T > of typem , a pointerA of typep , an expressionT * that denotes an lvalue of typev orT or an rvalue of typeconst T , and an rvalueconst T of typerv , the following terms are defined.T If Ifis a specialization ofX , the terms below are defined as ifinplace_vector wereA .scoped_allocator_adaptor < allocator < T > , allocator_type > is not allocator-aware or is a specialization ofX , the terms below are defined as ifbasic_string wereA — no allocator object needs to be created and user specializations ofallocator < T > are not instantiated:allocator < T > (2.1) —
is Cpp17DefaultInsertable intoT 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 expressionX whereallocator_traits < A >:: construct ( m , p ) is the address of the uninitialized storage for the element allocated withinp .X (2.3) —
is Cpp17MoveInsertable intoT 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 intoT means that, in addition toX being Cpp17MoveInsertable intoT , 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 tov .* p (2.5) —
is Cpp17EmplaceConstructible intoT fromX , for zero or more argumentsargs , means that the following expression is well-formed:args allocator_traits < A >:: construct ( m , p , args ) (2.6) —
is Cpp17Erasable fromT means that the following expression is well-formed:X [Note: A container callsallocator_traits < A >:: destroy ( m , p ) to construct an element atallocator_traits < A >:: construct ( m , p , args ) usingp , withargs m == get_allocator () (or . The defaultin the case ofm == A ( allocator < T > (), get_allocator ()) )inplace_vector inconstruct will callallocator , but specialized allocators can choose a different definition. —end note]:: new (( void * ) p ) T ( args ) [...]
A type
meets the allocator-aware container requirements ifX 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 asallocator_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:
returnsu . empty () true,.u . get_allocator () == A () 10․ Complexity: Constant.
X u ( m ); 11․ Postconditions:
returnsu . empty () true,.u . get_allocator () == m 12․ Complexity: Constant.
X u ( t , m ); 13․ Preconditions:
is Cpp17CopyInsertable intoT .X 14․ Postconditions:
,u == t u . get_allocator () == m 15․ Complexity: Linear.
X u ( rv ); 16․ Postconditions:
has the same elements asu had before this construction; the value ofrv is the same as the value ofu . get_allocator () before this construction.rv . get_allocator () 17․ Complexity: Constant.
X u ( rv , m ); 18․ Preconditions:
is Cpp17MoveInsertable intoT .X 19․ Postconditions:
has the same elements, or copies of the elements, thatu 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 intoT and Cpp17CopyAssignable.X 23․ Postconditions:
is true.a == t 24․ Complexity: Linear.
a = rv 25․ Result: X&.
26․ Preconditions: If
isallocator_traits < allocator_type >:: propagate_on_container_move_assignment :: value false,is Cpp17MoveInsertable intoT and Cpp17MoveAssignable.X 27․ Effects: All existing elements of
are either move assigned to or destroyed.a 28․ Postconditions: If
anda do not refer to the same object,rv is equal to the value thata had before this assignment.rv 29․ Complexity: Linear.
a . swap ( b ) 30․ Result:
void 31․ Effects: Exchanges the contents of
anda .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 theinplace_vector object itself. [Note: Although it is an allocator-aware container,inplace_vector uses its allocator only to construct and destroyinplace_vector allocator-aware elements; it never directly instantiates theorallocate member functions of itsdeallocate .— 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 theinplace_vector ,push_front ,prepend_range , andpop_front member functions, which are not provided. Descriptions are provided here only for operations onemplace_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 andinplace_vector < T , N , allocator < T > >:: iterator meet the constexpr iterator requirements.inplace_vector < T , N , allocator < T > >:: const_iterator 4․ For any
> 0 and anyN , ifAllocator isis_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 exceedinplace_vector < T , N , Allocator > throws an exception of typeN .bad_alloc 6․ Let
denote a specializationIV ofinplace_vector < T , N > inplace_vector < T , N , allocator < T >> inplace_vector<T, N, A> . Ifis zero, thenN isIV empty. If both trivial and empty. Otherwise:is zero and eitherN isis_trivial_v < A > trueoris a specialization ofA , thenallocator isIV
- (6.1) If
isis_trivially_copy_constructible_v < T > && is_trivially_copy_constructible_v < A > trueand , thendoes not existA :: select_on_container_copy_construction has a trivial copy constructor.IV - (6.2) If
isis_trivially_move_constructible_v < T > && is_trivially_move_constructible_v < A > true, thenhas a trivial move constructor.IV - (6.3) If
isis_trivially_destructible_v < T > true, then:
- (6.3.1)
If isis_trivially_destructible_v < A > true, thenhas a trivial destructor.IV - (6.3.2) If
isis_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
isis_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 typeis - nothrow - ua - constructible - v < T , A , X ... > and constructor arguments of types specified byA ([allocator.uses.construction]) is known to be a non-throwing operation. [Note: This trait can be implemented by instantiatingX ... , whereis_nothrow_constructible_v < T , Y ... > is the set of tuple arguments deduced byY ... . —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
onexplicit is consistent with the other STL containers (e.g.inplace_vector ( n , alloc ) andvector ).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:
isT intoCpp17DefaultInsertable .inplace_vector 2․ Effects: Constructs an
withinplace_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:
isT intoCpp17CopyInsertable .inplace_vector 5․ Effects: Constructs an
withinplace_vector copies ofn , 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 rangeinplace_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 rangeinplace_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 intoT .inplace_vector 12․ Complexity: Linear.
13․ Remarks: When isallocator_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: isallocator_traits < allocator_type >:: propagate_on_container_copy_assignment :: value falseor.rhs . get_allocator () == this -> get_allocator () is Cpp17CopyInsertable intoT 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: isallocator_traits < allocator_type >:: propagate_on_container_move_assignment :: value falseor.rhs . get_allocator () == this -> get_allocator () is Cpp17MoveInsertable intoT and Cpp17MoveAssignable.inplace_vector 16․ Complexity: Linear.
17․ Remarks: When isallocator_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 ofinplace_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 thatinplace_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:
isT intoCpp17DefaultInsertable .inplace_vector 3․ Effects: If
, erases the lastsz < size () elements from the sequence. Otherwise, appendssize () - 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:
isT intoCpp17CopyInsertable .inplace_vector 6․ Effects: If
, erases the lastsz < size () elements from the sequence. Otherwise, appendssize () - sz copies ofsz - 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-emptydata () + size () ,inplace_vector isdata () == 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 ofn before this call for thesize () overload, andappend_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 anyT operation, there are no effects. Otherwise, if an exception is thrown, thenInputIterator ≥size () and elements in the rangen + [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:
isvalue_type intoCpp17EmplaceConstructible frominplace_vector .vals ... 10․ Effects: If
issize () < capacity () true, appends an object of typedirect-non-list-initialized withT . Otherwise, there are no effects.vals ... 11․ Returns:
ifnullptr issize () == 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:
isvalue_type intoCpp17EmplaceConstructible frominplace_vector .* ranges :: begin ( rg ) 16․ Effects: Appends copies of initial elements in
beforerg , until all elements are inserted orend () . Each iterator in the rangesize () == capacity () is trueis dereferenced at most once.rg 17․ Returns: An iterator pointing to the first element of
that was not inserted intorg , 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 ofn prior to this call. If an exception is thrown after the insertion ofsize () elements, thenk equalssize () , elements in the rangen + k + [begin () ,0 ) are not modified, and elements in the rangen + [begin () ,n ) correspond to the inserted elements.n + k template < class ... Args > constexpr reference unchecked_emplace_back ( Args && ... args ); 20․ Preconditions:
issize () < 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:
issize () < 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:
ifbad_alloc isn > 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 ofT 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 isallocator_traits < A >:: propagate_on_container_swap :: value && ! allocator_traits < A >:: always_compare_equal :: value true, swap the allocators ofand* this . Then, swap the firstx elements ofm and* this , wherex is the smaller ofm andthis -> size () . Then, move-insert the remaining elements from the longerx . size () to the end of the shorter one, then erase those elements from the longerinplace_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 ] . Ifx [ n ] isallocator_traits < allocator_type >:: propagate_on_container_swap :: value true, thenmeets theallocator_type requirements andCpp17Swappable .get_allocator () == x . get_allocator ()
31․ Effects: Exchanges the contents of and* this .x
32․ Complexity: Linear.
33․ Remarks: When isallocator_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 ;