inplace_vector

A dynamically-resizable vector with fixed capacity and embedded storage

Document number: P0843R9.
Date: 2023-09-11.
Authors: Gonzalo Brito Gadeschi, Timur Doumler, Nevin Liber, David Sankel <dsankel _at_ adobe.com>.
Reply to: Timur Doumler <papers _at_ timur.audio>.
Audience: LEWG.

Table of Contents

Changelog

Introduction

This paper proposes inplace_vector, a dynamically-resizable array with capacity fixed at compile time and contiguous inplace storage, that is, the array elements are stored within the vector object itself. Its API closely resembles std::vector<T, A>, making it easy to teach and learn, and the inplace storage guarantee makes it useful in environments in which dynamic memory allocations are undesired.

This container is widely-used in the standard practice of C++, with prior art in, e.g., boost::static_vector<T, Capacity> [1] or the EASTL [2], and therefore we believe it will be very useful to expose it as part of the C++ standard library, which will enable it to be used as a vocabulary type.

Motivation and Scope

The inplace_vector container is useful when:

Existing practice

Three widely used implementations of inplace_vector are available: Boost.Container [1], EASTL [2], and Folly [3]. Boost.Container implements inplace_vector as a standalone type with its own guarantees. EASTL and Folly implement it via an extra template parameter in their small_vector types.

Custom allocators like Howard Hinnant’s stack_alloc [4] emulate inplace_vector on top of std::vector, but as discussed in the next sections, this emulation is not great.

Other prior art includes:

A reference implementation of this proposal is available here (godbolt).

Design

The design described below was approved at LEWG Varna '23:

Strongly Favor Weakly Favor Neutral Weakly Against Strongly Against
9 7 0 0 0
Strongly Favor Weakly Favor Neutral Weakly Against Strongly Against
11 5 0 0 0

Standalone or a special case another type?

The EASTL [2] and Folly [3] special case small_vector, e.g., using a 4th template parameter, to make it become a inplace_vector. P0639R0: Changing attack vector of the constexpr_vector [7] proposes improving the Allocator concepts to allow implementing inplace_vector as a special case of vector with a custom allocator. Both approaches produce specializations of small_vector or vector whose methods subtly differ in terms of effects, exception-safety, iterator invalidation, and complexity guarantees.

This proposal closely follows boost::container::static_vector<T,Capacity> [1] and proposes inplace_vector as a standalone type.

Where possible, this proposal defines the semantics of inplace_vector to match vector. Providing the same programming model makes this type easier to teach, use, and makes it easy to “just change” one type on a program to, e.g., perform a performance experiment without accidentally introducing undefined behavior.

Layout

inplace_vector models ContiguousContainer. Its elements are stored and properly aligned within the inplace_vector object itself. If the Capacity is zero the container has zero size:

static_assert(is_empty_v<inplace_vector<T, 0>>); // for all T

The offset of the first element within inplace_vector is unspecified, and Ts are not allowed to overlap.

The layout differs from vector, since inplace_vector does not store the capacity field (it’s known from the template parameter).

If T is trivially-copyable or N == 0, then inplace_vector<T, N> is also trivially copyable to support HPC (such as copying one between a CPU and a GPU), serialization/deserialization, which is important for use cases like sending a vector via MPI_Send, etc.

// for all C:
static_assert(!is_trivially_copyable_v<T> || is_trivially_copyable_v<inplace_vector<T, C>> || N == 0);

Move semantics

A moved-from inplace_vector is left in a valid but unspecified state (option 3 below) unless T is trivially-copyable, in which case the size of the inplace_vector does not change (array semantics, option 2 below). That is:

inplace_vector a(10); inplace_vector b(std::move(a)); assert(a.size() == 10); // MAY FAIL

moves a’s elements element-wise into b, and afterwards the size of the moved-from inplace_vector may have changed.

This prevents code from relying on the size staying the same (and therefore being incompatible with changing an inplace_vector type back to vector) without incuring the cost of having to clear the inplace_vector.

When T is trivially-copyable, array semantics are used to provide trivial move operations.

This is different from LEWG Kona '22 Polls (22 in person + 8 remote) and we’d like to poll on these semantics again:

Alternatives:

  1. vector semantics: guarantees that inplace_vector is left empty (this happens with move assignment when using std::allocator<T> and always with move construction).
    • Pro: same programming model as vector.
    • Pro: increases safety by requiring users to re-initialize vector elements.
    • Con: clearing an inplace_vector is not free.
    • Con: inplace_vector<T, N> can no longer be made trivially copyable for a trivially copyable T, as the move operations can no longer be trivial.
  2. array semantics: guarantees that size() of inplace_vector does not change, and that elements are left in their moved-from state.
    • Pro: no additional run-time cost incurred.
    • Con: different programming model than vector.
  3. “valid but unspecified state”
    • Con: different programming model than vector and array, requires calling size()
    • Pro: code calling size() is correct for both vector and inplace_vector, enabling changing the type back and forth.

Exception Safety

When using the inplace_vector APIs, the following types of failures are expected:

  1. The value_type’s constructors/assignment/destructors/swap can potentially throw (depends on noexcept),
  2. Mutating operations exceeding the capacity (push_back, insert, , inplace_vector(value_type, size), inplace_vector(begin, end)…), and
  3. Out-of-bounds unchecked access: front/back/pop_back when empty, operator[].

For inplace_vector, we choose the same semantics as for vector:

  1. When value_type’s operations are invoked, inplace_vector exception-safety guarantees depend on whether these operations throw, which is detected with noexcept if the API has a narrow contract.
  2. Mutating operations that exceed the capacity ask “allocator embedded within the inplace_vector”, which has run out of space, for more memory, and therefore throw bad_alloc like vector APIs do (e.g. a pmr “stack allocator” throws bad_alloc as well). This preserves the programming model from vector. bad_alloc also has the property that it never does an allocation itself.
  3. Out-of-bounds unchecked access is a precondition violation.

Alternatives:

  1. Throw bad_alloc when the inplace_vector is out-of-memory:
    • Pros: same programming model as vector.
  2. Throw “some other exception” when the inplace_vector is out-of-memory:
    • Pros: to be determined.
    • Cons: different programming model as vector.
  3. Abort the process
    • Pros: portability to embedded platforms without exception support
    • Cons: different programming model than vector
  4. Precondition violation
    • Cons: different proramming model than vector, users responsible for checking before modifying vector size, etc.

Fallible APIs

We add the following new fallible APIs which, when the vector size equal its capacity, return nullptr (do not throw bad_alloc) without moving from the inputs, enabling them to be re-used:

constexpr T* inplace_vector<T, C>::try_push_back(const T& value);
constexpr T* inplace_vector<T, C>::try_push_back(T&& value);

template<class... Args>
  constexpr T* try_emplace_back(Args&&... args);

They can be used as follows

T value = T(); if (!v.try_push_back(value)) { std::cerr << "Failed to insert " << value << std::endl; // value not moved-from std::terminate(); }

Fallible Unchecked APIs

We add the following new fallible unchecked APIs for which exceeding the capacity is a precondition violation:

constexpr T& inplace_vector<T, C>::unchecked_push_back(const T& value);
constexpr T& inplace_vector<T, C>::unchecked_push_back(T&& value);

template<class... Args>
  constexpr T& unchecked_emplace_back(Args&&... args);

These APIs were requested in LEWG Kona '22 (22 in person + 8 remote):

This was confirmed at LEWG Varna '23 after a discussion on safety:

Strongly Favor Weakly Favor Neutral Weakly Against Strongly Against
1 5 3 3 7

The name unchecked_push_back was polled in LEWG Varna '23:

The potential impact of the three APIs on code size and performance is shown here, where the main difference between try_push_back and unchecked_push_back is the presenece of an extra branch in try_push_back.

Iterator invalidation

inplace_vector iterator invalidation guarantees differ from std::vector:

inplace_vector APIs that potentially invalidate iterators are: resize(n), resize(n, v), pop_back, erase, and swap.

Freestanding

The inplace_vector APIs are not freestanding because all the insertion APIs (constructors, push back, insert, …) may throw bad_alloc.

If we were to only make the non-throwing APIs available in free standing, then this type would be useless in free standing.

We intend to propose making this freestanding in a different paper where we figure out how to do that.

We’d need to add it to: [library.requirements.organization.compliance]

When we fix this we’d need to add <inplace_vector> to [tab:headers.cpp.fs]:

Subclause Headers
[containers] containers <inplace_vector>

Which header does it belong to?

We propose that this container goes into its own header <inplace_vector> rather than in header <vector>.

LWG asked for inplace_vector to be part of the <vector> header. LEWG Varna '23 took the following poll:

That is, consensus against change.

Return type of push_back

In C++20, both push_back and emplace_back were slated to return a reference (they used to both return void). Even with plenary approval, changing push_back turned out to be an ABI break that was backed out, leaving the situation where emplace_back returns a reference but push_back is still void. This ABI issue doesn’t apply to new types. Should push_back return a reference to be consistent with emplace_back, or should it be consistent with older containers?

Request LEWG to poll on that.

reserve and shrink_to_fit APIs

shrink_to_fit requests vector to decrease its capacity, but this request may be ignored. inplace_vector may implement it as a nop (and it may be noexcept).

reserve(n) requests the vector to potentially increase its capacity, failing if the request can’t be satisfied. inplace_vector may implement it as a nop if n <= capacity(), throwing bad_alloc otherwise.

These APIs make it easier and safe for programs to be “more” parametric over “vector-like” containers (vector, small_vector, inplace_vector), but since they do not do anything useful for inplace_vector, we may want to fail to compile instead.

Deduction guides

Unlike the other containers, inplace_vector does not have any deduction guides because there is no case in which it would be possible to deduce the second template argument, the capacity, from the initializer.

Summary of semantic differences with vector

Aspect vector inplace_vector
Capacity Indefinite N
Move and swap O(1), no iterators invalidated array semantics: O(size), invalidates all iterators
Moved from left empty (this happens with move assignment when using std::allocator<T> and always with move construction) valid but unspecified state except if T is trivially-copyable, in which case array semantics
Default construction and destruction of trivial types O(1) O(capacity)
Is empty when zero capacity? No Yes
Trivially-copyable if is_trivially_copyable_v<T>? No Yes

Name

The class template name was confirmed at LEWG Varna '23:

Options Votes
static_vector 4
inplace_vector 14
fixed_capacity_vector 5

Technical specification

EDITORIAL: This enhancement is a pure header-only addition to the C++ standard library as the <inplace_vector> header. It belongs in the “Sequence containers” ([sequences]) part of the “Containers library” ([containers]) as “Class template inplace_vector”.

[library.requirements.organization.headers]

Add <inplace_vector> to [tab:headers.cpp].

[iterator.range] Range access

Modify:

1 In addition to being available via inclusion of the <iterator> header, the function templates in [iterator.range] are available when any of the following headers are included: <array>, <deque>, <forward_list>, <inplace_vector>, <list>, <map>, <regex>, <set>, <span>, <string>, <string_view>, <unordered_map>, <unordered_set>, and <vector>.

[container.alloc.reqmts]

Modify:

1 All of the containers defined in [containers] and in [basic.string] except array and inplace_vector meet the additional requirements of an allocator-aware container, as described below.

[allocator.requirements.general]

1 The library describes a standard set of requirements for allocators, which are class-type objects that encapsulate the information about an allocation model. This information includes the knowledge of pointer types, the type of their difference, the type of the size of objects in this allocation model, as well as the memory allocation and deallocation primitives for it. All of the string types, containers (except array and inplace_vector), string buffers and string streams ([input.output]), and match_results are parameterized in terms of allocators.

[containers.general]

Modify [tab:containers.summary]:

Subclause Headers
[sequences] Sequence containers <array>, <deque>, <forward_list>, <list>, <vector>, <inplace_vector>

[container.reqmts] General container requirements

  1. A type X meets the container requirements if the following types, statements, and expressions are well-formed and have the specified semantics.
typename X::value_type
typename X::reference
typename X::const_reference
typename X::iterator
typename X::const_iterator
typename X::difference_type
typename X::size_type
X u;
X u = X();
X u(a);
X u = a;
X u(rv);
X u = rv;
a = rv
a.~X()
a.begin()
a.end()
a.cbegin()
a.cend()
i <=> j
a == b
a != b
a.swap(b)
swap(a, b)
r = a
a.size()
a.max_size()
a.empty()
  1. In the expressions
i == j
i != j
i < j
i <= j
i >= j
i > j
i <=> j
i - j

where i and j denote objects of a container’s iterator type, either or both may be replaced by an object of the container’s const_iterator type referring to the same element with no change in semantics.

Unless otherwise specified, all containers defined in this Clause obtain memory using an allocator (see [allocator.requirements]).

[Note 2: In particular, containers and iterators do not store references to allocated elements other than through the allocator’s pointer type, i.e., as objects of type P or pointer_traits<P>::template rebind<unspecified>, where P is allocator_traits<allocator_type>::pointer. — end note]

Copy constructors for these container types obtain an allocator by calling allocator_traits<allocator_type>::select_on_container_copy_construction on the allocator belonging to the container being copied. Move constructors obtain an allocator by move construction from the allocator belonging to the container being moved. Such move construction of the allocator shall not exit via an exception. All other constructors for these container types take a const allocator_type& argument.

[Note 3: If an invocation of a constructor uses the default value of an optional allocator argument, then the allocator type must support value-initialization. — end note]

A copy of this allocator is used for any memory allocation and element construction performed, by these constructors and by all member functions, during the lifetime of each container object or until the allocator is replaced. The allocator may be replaced only via assignment or swap(). Allocator replacement is performed by copy assignment, move assignment, or swapping of the allocator only if

  1. allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value,
  2. allocator_traits<allocator_type>::propagate_on_container_move_assignment::value, or
  3. allocator_traits<allocator_type>::propagate_on_container_swap::value
    is true within the implementation of the corresponding container operation. In all container types defined in this Clause, the member get_allocator() returns a copy of the allocator used to construct the container or, if that allocator has been replaced, a copy of the most recent replacement.

The expression a.swap(b), for containers a and b of a standard container type other than array and inplace_vector, shall exchange the values of a and b without invoking any move, copy, or swap operations on the individual container elements. Lvalues of any Compare, Pred, or Hash types belonging to a and b shall be swappable and shall be exchanged by calling swap as described in [swappable.requirements]. If allocator_traits<allocator_type>::propagate_on_container_swap::value is true, then lvalues of type allocator_type shall be swappable and the allocators of a and b shall also be exchanged by calling swap as described in [swappable.requirements]. Otherwise, the allocators shall not be swapped, and the behavior is undefined unless a.get_allocator() == b.get_allocator(). Every iterator referring to an element in one container before the swap shall refer to the same element in the other container after the swap. It is unspecified whether an iterator with value a.end() before the swap will have value b.end() after the swap.

The expression a.swap(b), for containers a and b of a standard container type other than array, shall exchange the values of a and b without invoking any move, copy, or swap operations on the individual container elements. Any Compare, Pred, or Hash types belonging to a and b shall meet the Cpp17Swappable requirements and shall be exchanged by calling swap as described in [swappable.requirements]. If allocator_traits<allocator_type>​::​propagate_on_container_swap​::​value is true, then allocator_type shall meet the Cpp17Swappable requirements and the allocators of a and b shall also be exchanged by calling swap as described in [swappable.requirements]. Otherwise, the allocators shall not be swapped, and the behavior is undefined unless a.get_allocator() == b.get_allocator(). Every iterator referring to an element in one container before the swap shall refer to the same element in the other container after the swap. It is unspecified whether an iterator with value a.end() before the swap will have value b.end() after the swap.

Unless otherwise specified (see [associative.reqmts.except], [unord.req.except], [deque.modifiers], [inline_vector.modifiers] and [vector.modifiers]) all container types defined in this Clause meet the following additional requirements:

  1. If an exception is thrown by an insert() or emplace() function while inserting a single element, that function has no effects.
  2. If an exception is thrown by a push_back(), push_front(), emplace_back(), or emplace_front() function, that function has no effects.
  3. No erase(), clear(), pop_back() or pop_front() function throws an exception.
  4. No copy constructor or assignment operator of a returned iterator throws an exception.
  5. No swap() function throws an exception.
  6. No swap() function invalidates any references, pointers, or iterators referring to the elements of the containers being swapped.
    [Note 4: The end() iterator does not refer to any element, so it can be invalidated. — end note]

[containers.sequences.general]

Modify:

1 The headers <array>, <deque>, <forward_list>, <inplace_vector>, <list>, and <vector> define class templates that meet the requirements for sequence containers.

[container.requirements.sequence.reqmts]

Modify:

sequence.reqmts.1 A sequence container organizes a finite set of objects, all of the same type, into a strictly linear arrangement. The library provides fourthe following basic kinds of sequence containers: vector, inplace_vector, forward_list, list, and deque. In addition, array is provided as a sequence container which provides limited sequence operations because it has a fixed number of elements. The library also provides container adaptors that make it easy to construct abstract data types, such as stacks, queues, flat_maps, flat_multimaps, flat_sets, or flat_multisets, out of the basic sequence container kinds (or out of other program-defined sequence containers).

sequence.reqmts.2 [Note 1: The sequence containers offer the programmer different complexity trade-offs. vector is appropriate in most circumstances. array has a fixed size known during translation. inplace_vector has a fixed capacity known during translation. list or forward_list support frequent insertions and deletions from the middle of the sequence. deque supports efficient insertions and deletions taking place at the beginning or at the end of the sequence. When choosing a container, remember vector is best; leave a comment to explain if you choose from the rest! — end note]

sequence.reqmts.69 The following operations are provided for some types of sequence containers but not others. An implementation shall implement them so as to take amortized constant time.

a.front()
a.back()
a.emplace_front(args)
a.emplace_back(args)
a.push_front(t)
a.push_front(rv)
a.prepend_range(rg)
a.push_back(t)
a.push_back(rv)
a.append_range(rg)
a.pop_front()
a.pop_back()
a[n]
a.at(n)

[containers.sequences.inplace_vector.syn] Header <inplace_vector> synopsis

Drafting note: not freestanding yet.

#include <compare> // see [compare.syn] #include <initializer_list> // see [initializer.list.syn] namespace std { // [inplace_vector], class template inplace_vector template <class T, size_t N> class inplace_vector; // [inplace_vector.special], specialized algorithms template <class T, size_t N> constexpr void swap(inplace_vector<T, N>& x, inplace_vector<T, N>& y) noexcept(noexcept(x.swap(y))); // [inplace_vector.erasure], erasure template<class T, size_t N, class U> constexpr typename inplace_vector<T, N>::size_type erase(inplace_vector<T, N>& c, const U& value); template<class T, size_t N, class Predicate> constexpr typename inplace_vector<T, N>::size_type erase_if(inplace_vector<T, N>& c, Predicate pred); } // namespace std

[containers.sequences.inplace_vector] Class template inplace_vector

[containers.sequences.inplace_vector.overview] Overview

  1. A inplace_vector is a contiguous container that supports constant time insert and erase operations at the end; insert and erase in the middle take linear time. Its capacity is part of its type and its elements are stored within the inplace_vector object itself such that if v is a inplace_vector<T, N>, then it obeys the identity &v[n] == &v[0] + n for all 0 <= n < v.size().
    EDITORIAL: Do we need this last “…such that if v is a inplace_vector<T, N>, then it obeys the identity &v[n] == &v[0] + n for all 0 <= n < v.size()”?
  2. A inplace_vector meets all of the requirements of a container ([container.requirements]) with the exception of the swap member function, whose complexity is linear instead of constant. A inplace_vector meets all ofthe requirements of a reversible container ([container.rev.reqmts]), of a contiguous container, and of a sequence container, including most of the optional sequence container requirements ([sequence.reqmts]). The exceptions are the push_front, prepend_range, pop_front, and emplace_front member functions, which are not provided. Descriptions are provided here only for operations on inplace_vector that are not described in one of these tables or for operations where there is additional semantic information.
  3. Class inplace_vector relies on the implicitly-declared special member functions [class.default.ctor], [class.dtor], and [class.copy.ctor] to conform to the container requirements table in [container.requirements]]. In addition to the requirements specified in the container requirements table, the move constructor and move assignment operator for inplace_vector require that T be Cpp17MoveConstructible or Cpp17MoveAssignable, respectively.
    EDITORIAL: Why do we need this paragraph 3.?
  4. The types iterator and const_iterator meet the constexpr iterator
    requirements ([iterator.requirements.general])."
template <class T, size_t N> class inplace_vector { public: // types: using value_type = T; using pointer = T*; using const_pointer = const T*; using reference = value_type&; using const_reference = const value_type&; using size_type = size_t; using difference_type = ptrdiff_t; 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>; // [containers.sequences.inplace_vector.cons], construct/copy/destroy constexpr inplace_vector() noexcept; constexpr explicit inplace_vector(size_type n); constexpr inplace_vector(size_type n, const T& value); template <class InputIterator> constexpr inplace_vector(InputIterator first, InputIterator last); template <container-compatible-range<T> R> constexpr inplace_vector(from_range_t, R&& rg); constexpr inplace_vector(const inplace_vector&); constexpr inplace_vector(inplace_vector&&) noexcept(N == 0 || is_nothrow_move_constructible_v<T>); constexpr inplace_vector(initializer_list<T> il); constexpr ~inplace_vector(); constexpr inplace_vector& operator=(const inplace_vector& other); constexpr inplace_vector& operator=(inplace_vector&& other) noexcept(N == 0 || is_nothrow_move_assignable_v<T>); template <class InputIterator> constexpr void assign(InputIterator first, InputIterator last); template<container-compatible-range<T> R> constexpr void assign_range(R&& rg); constexpr void assign(size_type n, const T& u); constexpr void assign(initializer_list<T> il); // 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; // [containers.sequences.inplace_vector.members] size/capacity [[nodiscard]] 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); constexpr void resize(size_type sz, const T& c); constexpr void reserve(size_type n); constexpr void shrink_to_fit(); // element access constexpr reference operator[](size_type n); constexpr const_reference operator[](size_type n) const; constexpr const_reference at(size_type n) const; constexpr reference at(size_type n); constexpr reference front(); constexpr const_reference front() const; constexpr reference back(); constexpr const_reference back() const; // [containers.sequences.inplace_vector.data], data access constexpr T* data() noexcept; constexpr const T* data() const noexcept; // [containers.sequences.inplace_vector.modifiers], modifiers template <class... Args> constexpr T& emplace_back(Args&&... args); constexpr T& push_back(const T& x); constexpr T& push_back(T&& x); template<container-compatible-range<T> R> constexpr void append_range(R&& rg); constexpr void pop_back(); template<class... Args> constexpr T* try_emplace_back(Args&&... args); constexpr T* try_push_back(const T& x); constexpr T* try_push_back(T&& x); template<class... Args> constexpr T& unchecked_emplace_back(Args&&... args); constexpr T& unchecked_push_back(const T& x); constexpr T& unchecked_push_back(T&& x); template <class... Args> constexpr iterator emplace(const_iterator position, Args&&... args); 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); 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>)); 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>)) { x.swap(y); } };

[containers.sequences.inplace_vector.cons] Constructors

Let IV denote a specialization of inplace_vector<T, N> with N > 0, then the following conditions are all satisfied:

Let IV0 denote a specialization of inplace_vector<T, 0>. Then IV0 has a trivial copy constructor, a trivial move constructor, trivial copy assignment operator, a trivial move assignment operator, and a trivial destructor.


constexpr inplace_vector() noexcept;

EDITORIAL: do we need this one or is implied by. container requirements?


constexpr explicit inplace_vector(size_type n);

constexpr inplace_vector(size_type n, const T& value);

template <class InputIterator>
constexpr inplace_vector(InputIterator first, InputIterator last);

template <container-compatible-range<T> R>
constexpr inplace_vector(from_range_t, R&& rg);

[containers.sequences.inplace_vector.capacity] Size and capacity

static constexpr size_type capacity() noexcept
static constexpr size_type max_size() noexcept

constexpr void resize(size_type sz);

EDITORIAL: are we missing Remarks here?


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

EDITORIAL: are we missing Remarks here?

[containers.sequences.inplace_vector.data] Data

constexpr       T* data()       noexcept;
constexpr const T* data() const noexcept;

[containers.sequences.inplace_vector.modifiers] 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(const_iterator position, R&& rg);
constexpr iterator insert(const_iterator position, initializer_list<T> il);
 
template <class... Args> constexpr iterator emplace_back(Args&&... args);
template <class... Args> constexpr iterator emplace(const_iterator position, Args&&... args);
constexpr T& push_back(const T& x);
constexpr T& push_back(T&& x);

EDITORIAL: push_back return a reference to the added element. Do we need to spell that?


template <class... Args>
  constexpr T* try_emplace_back(Args&&... x);
constexpr T* try_push_back(const T& x);
constexpr T* try_push_back(T&& x);

template <class... Args>
  constexpr T& unchecked_emplace_back(Args&&... x);
constexpr T& unchecked_push_back(const T& x);
constexpr T& unchecked_push_back(T&& x);

constexpr void reserve(size_type n);

constexpr void shrink_to_fit();

[containers.sequences.inplace_vector.erasure] Erasure

constexpr iterator erase(const_iterator position);

constexpr iterator erase(const_iterator first, const_iterator last);

template<class T, size_t N, class U>
  constexpr typename inplace_vector<T, N>::size_type
    erase(inplace_vector<T, N>& c, const U& value);
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, class Predicate>
  constexpr typename inplace_vector<T, N>::size_type
    erase_if(inplace_vector<T, N>& c, Predicate pred);
auto it = remove_if(c.begin(), c.end(), pred); auto r = distance(it, c.end()); c.erase(it, c.end()); return r;

[containers.sequence.inplace_vector.zero] Zero-sized inplace vector

If IV0, then

[version.syn]

Add:

#define __cpp_lib_inplace_vector   202306L // also in <inplace_vector>

[diff.cpp03.library] Compatibility

Modify:

Acknowledgments

This proposal is based on Boost.Container’s boost::container::static_vector, mainly authored by Adam Wulkiewicz, Andrew Hundt, and Ion Gaztanaga. The reference implementation is based on Howard Hinnant std::vector implementation in libc++ and its test-suite. The following people provided valuable feedback that influenced some aspects of this proposal: Walter Brown, Zach Laine, Rein Halbersma, Andrzej Krzemieński, Casey Carter and many others. Many thanks to Daniel Krügler for reviewing the wording.