diff --git a/include/.styleguide b/include/.styleguide index f3b2f0cf..8fb61fdf 100644 --- a/include/.styleguide +++ b/include/.styleguide @@ -6,6 +6,10 @@ cppSrcFileInclude { \.cpp$ } +licenseUpdateExclude { + include/sleipnir/util/small_vector\.hpp$ +} + includeOtherLibs { ^Eigen/ } diff --git a/include/sleipnir/autodiff/Expression.hpp b/include/sleipnir/autodiff/Expression.hpp index 5b8dc5dc..99997da5 100644 --- a/include/sleipnir/autodiff/Expression.hpp +++ b/include/sleipnir/autodiff/Expression.hpp @@ -14,8 +14,8 @@ #include "sleipnir/autodiff/ExpressionType.hpp" #include "sleipnir/util/IntrusiveSharedPtr.hpp" #include "sleipnir/util/Pool.hpp" -#include "sleipnir/util/SmallVector.hpp" #include "sleipnir/util/SymbolExports.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir::detail { diff --git a/include/sleipnir/autodiff/ExpressionGraph.hpp b/include/sleipnir/autodiff/ExpressionGraph.hpp index d338cfe3..56fe6bd5 100644 --- a/include/sleipnir/autodiff/ExpressionGraph.hpp +++ b/include/sleipnir/autodiff/ExpressionGraph.hpp @@ -6,8 +6,8 @@ #include "sleipnir/autodiff/Expression.hpp" #include "sleipnir/util/FunctionRef.hpp" -#include "sleipnir/util/SmallVector.hpp" #include "sleipnir/util/SymbolExports.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir::detail { diff --git a/include/sleipnir/autodiff/Hessian.hpp b/include/sleipnir/autodiff/Hessian.hpp index b964737d..b1317eef 100644 --- a/include/sleipnir/autodiff/Hessian.hpp +++ b/include/sleipnir/autodiff/Hessian.hpp @@ -12,8 +12,8 @@ #include "sleipnir/autodiff/Profiler.hpp" #include "sleipnir/autodiff/Variable.hpp" #include "sleipnir/autodiff/VariableMatrix.hpp" -#include "sleipnir/util/SmallVector.hpp" #include "sleipnir/util/SymbolExports.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir { diff --git a/include/sleipnir/autodiff/Jacobian.hpp b/include/sleipnir/autodiff/Jacobian.hpp index 4d85c2f4..254f60d2 100644 --- a/include/sleipnir/autodiff/Jacobian.hpp +++ b/include/sleipnir/autodiff/Jacobian.hpp @@ -10,8 +10,8 @@ #include "sleipnir/autodiff/Profiler.hpp" #include "sleipnir/autodiff/Variable.hpp" #include "sleipnir/autodiff/VariableMatrix.hpp" -#include "sleipnir/util/SmallVector.hpp" #include "sleipnir/util/SymbolExports.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir { diff --git a/include/sleipnir/autodiff/VariableBlock.hpp b/include/sleipnir/autodiff/VariableBlock.hpp index 3e06601a..1511a280 100644 --- a/include/sleipnir/autodiff/VariableBlock.hpp +++ b/include/sleipnir/autodiff/VariableBlock.hpp @@ -218,9 +218,9 @@ class VariableBlock { * @param row The scalar subblock's row. * @param col The scalar subblock's column. */ - template - requires(!std::is_const_v) - Variable& operator()(int row, int col) { + Variable& operator()(int row, int col) + requires(!std::is_const_v) + { Assert(row >= 0 && row < Rows()); Assert(col >= 0 && col < Cols()); return (*m_mat)(m_rowOffset + row, m_colOffset + col); @@ -243,9 +243,9 @@ class VariableBlock { * * @param row The scalar subblock's row. */ - template - requires(!std::is_const_v) - Variable& operator()(int row) { + Variable& operator()(int row) + requires(!std::is_const_v) + { Assert(row >= 0 && row < Rows() * Cols()); return (*this)(row / Cols(), row % Cols()); } diff --git a/include/sleipnir/autodiff/VariableMatrix.hpp b/include/sleipnir/autodiff/VariableMatrix.hpp index 5d34ef4e..9703cf7c 100644 --- a/include/sleipnir/autodiff/VariableMatrix.hpp +++ b/include/sleipnir/autodiff/VariableMatrix.hpp @@ -16,8 +16,8 @@ #include "sleipnir/autodiff/VariableBlock.hpp" #include "sleipnir/util/Assert.hpp" #include "sleipnir/util/FunctionRef.hpp" -#include "sleipnir/util/SmallVector.hpp" #include "sleipnir/util/SymbolExports.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir { diff --git a/include/sleipnir/optimization/Constraints.hpp b/include/sleipnir/optimization/Constraints.hpp index 61ed70b4..54190ac2 100644 --- a/include/sleipnir/optimization/Constraints.hpp +++ b/include/sleipnir/optimization/Constraints.hpp @@ -8,8 +8,8 @@ #include "sleipnir/autodiff/Variable.hpp" #include "sleipnir/util/Assert.hpp" #include "sleipnir/util/Concepts.hpp" -#include "sleipnir/util/SmallVector.hpp" #include "sleipnir/util/SymbolExports.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir { diff --git a/include/sleipnir/optimization/Multistart.hpp b/include/sleipnir/optimization/Multistart.hpp index a873062f..d905cafd 100644 --- a/include/sleipnir/optimization/Multistart.hpp +++ b/include/sleipnir/optimization/Multistart.hpp @@ -8,7 +8,7 @@ #include "sleipnir/optimization/SolverStatus.hpp" #include "sleipnir/util/FunctionRef.hpp" -#include "sleipnir/util/SmallVector.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir { diff --git a/include/sleipnir/optimization/OptimizationProblem.hpp b/include/sleipnir/optimization/OptimizationProblem.hpp index 03cadbe6..eecf5cb6 100644 --- a/include/sleipnir/optimization/OptimizationProblem.hpp +++ b/include/sleipnir/optimization/OptimizationProblem.hpp @@ -22,8 +22,8 @@ #include "sleipnir/optimization/SolverStatus.hpp" #include "sleipnir/optimization/solver/InteriorPoint.hpp" #include "sleipnir/util/Print.hpp" -#include "sleipnir/util/SmallVector.hpp" #include "sleipnir/util/SymbolExports.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir { diff --git a/include/sleipnir/util/Pool.hpp b/include/sleipnir/util/Pool.hpp index 87d093ce..02d8e190 100644 --- a/include/sleipnir/util/Pool.hpp +++ b/include/sleipnir/util/Pool.hpp @@ -5,8 +5,8 @@ #include #include -#include "sleipnir/util/SmallVector.hpp" #include "sleipnir/util/SymbolExports.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir { diff --git a/include/sleipnir/util/SmallVector.hpp b/include/sleipnir/util/SmallVector.hpp deleted file mode 100644 index 5e93ae49..00000000 --- a/include/sleipnir/util/SmallVector.hpp +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Sleipnir contributors - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace sleipnir { - -template -struct small_buffer_vector_allocator { - alignas(alignof(T)) std::byte m_smallBuffer[MaxSize * sizeof(T)]; - std::allocator m_alloc; - bool m_smallBufferUsed = false; - - using value_type = T; - // we have to set this three values, as they are responsible for the correct - // handling of the move assignment operator - using propagate_on_container_move_assignment = std::false_type; - using propagate_on_container_swap = std::false_type; - using is_always_equal = std::false_type; - - constexpr small_buffer_vector_allocator() noexcept = default; - - template - constexpr small_buffer_vector_allocator( // NOLINT - const small_buffer_vector_allocator&) noexcept {} - - template - struct rebind { - using other = small_buffer_vector_allocator; - }; - - // don't copy the small buffer for the copy/move constructors, as the copying - // is done through the vector - constexpr small_buffer_vector_allocator( - const small_buffer_vector_allocator& other) noexcept - : m_smallBufferUsed(other.m_smallBufferUsed) {} - - constexpr small_buffer_vector_allocator& operator=( - const small_buffer_vector_allocator& other) noexcept { - if (this == &other) { - return *this; - } - - m_smallBufferUsed = other.m_smallBufferUsed; - return *this; - } - - constexpr small_buffer_vector_allocator( - small_buffer_vector_allocator&&) noexcept {} - - constexpr small_buffer_vector_allocator& operator=( - const small_buffer_vector_allocator&&) noexcept { - return *this; - } - - [[nodiscard]] - constexpr T* allocate(const size_t n) { - // when the allocator was rebound we don't want to use the small buffer - if constexpr (std::is_same_v) { - if (n <= MaxSize) { - m_smallBufferUsed = true; - // as long as we use less memory than the small buffer, we return a - // pointer to it - return reinterpret_cast(&m_smallBuffer); - } - } - m_smallBufferUsed = false; - // otherwise use the default allocator - return m_alloc.allocate(n); - } - - constexpr void deallocate(void* p, const size_t n) { - // we don't deallocate anything if the memory was allocated in small buffer - if (&m_smallBuffer != p) { - m_alloc.deallocate(static_cast(p), n); - } - m_smallBufferUsed = false; - } - - // according to the C++ standard when propagate_on_container_move_assignment - // is set to false, the comparision operators are used to check if two - // allocators are equal. When they are not, an element wise move is done - // instead of just taking over the memory. For our implementation this means - // the comparision has to return false, when the small buffer is active - friend constexpr bool operator==(const small_buffer_vector_allocator& lhs, - const small_buffer_vector_allocator& rhs) { - return !lhs.m_smallBufferUsed && !rhs.m_smallBufferUsed; - } - - friend constexpr bool operator!=(const small_buffer_vector_allocator& lhs, - const small_buffer_vector_allocator& rhs) { - return !(lhs == rhs); - } -}; - -template -class small_vector - : public std::vector> { - public: - using vectorT = std::vector>; - - // default initialize with the small buffer size - constexpr small_vector() noexcept { vectorT::reserve(N); } - - small_vector(const small_vector&) = default; - small_vector& operator=(const small_vector&) = default; - - small_vector(small_vector&& other) noexcept( - std::is_nothrow_move_constructible_v) { - if (other.size() <= N) { - vectorT::reserve(N); - } - vectorT::operator=(std::move(other)); - } - - small_vector& operator=(small_vector&& other) noexcept( - std::is_nothrow_move_constructible_v) { - if (other.size() <= N) { - vectorT::reserve(N); - } - vectorT::operator=(std::move(other)); - return *this; - } - - // use the default constructor first to reserve then construct the values - explicit small_vector(size_t count) : small_vector() { - vectorT::resize(count); - } - - small_vector(size_t count, const T& value) : small_vector() { - vectorT::assign(count, value); - } - - template - small_vector(InputIt first, InputIt last) : small_vector() { - vectorT::insert(vectorT::begin(), first, last); - } - - small_vector(std::initializer_list init) : small_vector() { // NOLINT - vectorT::insert(vectorT::begin(), init); - } - - friend void swap(small_vector& a, small_vector& b) noexcept { - std::swap(static_cast(a), static_cast(b)); - } -}; - -template -constexpr small_vector::size_type erase_if(small_vector& c, - Pred pred) { - auto it = std::remove_if(c.begin(), c.end(), pred); - auto r = c.end() - it; - c.erase(it, c.end()); - return r; -} - -} // namespace sleipnir diff --git a/include/sleipnir/util/Spy.hpp b/include/sleipnir/util/Spy.hpp index 67b0b073..cb9b4e19 100644 --- a/include/sleipnir/util/Spy.hpp +++ b/include/sleipnir/util/Spy.hpp @@ -8,8 +8,8 @@ #include -#include "sleipnir/util/SmallVector.hpp" #include "sleipnir/util/SymbolExports.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir { diff --git a/include/sleipnir/util/small_vector.hpp b/include/sleipnir/util/small_vector.hpp new file mode 100644 index 00000000..8f28237f --- /dev/null +++ b/include/sleipnir/util/small_vector.hpp @@ -0,0 +1,4373 @@ +/** small_vector.hpp + * An implementation of `small_vector` (a vector with a small + * buffer optimization). I would probably have preferred to + * call this `inline_vector`, but I'll just go with the canonical + * name for now. + * + * Copyright © 2020-2021 Gene Harvey + * + * This software may be modified and distributed under the terms + * of the MIT license. See the small_vector_LICENSE.txt file for + * details. + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sleipnir { + +namespace concepts { + +template +concept Complete = requires { sizeof(T); }; + +// Note: this mirrors the named requirements, not the standard concepts, so we +// don't require the destructor to be noexcept for Destructible. +template +concept Destructible = std::is_destructible_v; + +template +concept TriviallyDestructible = std::is_trivially_destructible_v; + +template +concept NoThrowDestructible = std::is_nothrow_destructible_v; + +// Note: this mirrors the named requirements, not the standard library concepts, +// so we don't require Destructible here. + +template +concept ConstructibleFrom = std::is_constructible_v; + +template +concept NoThrowConstructibleFrom = std::is_nothrow_constructible_v; + +template +concept ConvertibleTo = + std::is_convertible_v && + requires(typename std::add_rvalue_reference_t (&f)()) { + static_cast(f()); + }; + +template +concept NoThrowConvertibleTo = + std::is_nothrow_convertible_v && + requires(typename std::add_rvalue_reference_t (&f)() noexcept) { + { static_cast(f()) } noexcept; + }; + +// Note: std::default_initializable requires std::destructible. +template +concept DefaultConstructible = ConstructibleFrom && requires { + T{}; +} && requires { ::new (static_cast(nullptr)) T; }; + +template +concept MoveAssignable = std::assignable_from; + +template +concept CopyAssignable = + MoveAssignable && std::assignable_from && + std::assignable_from && std::assignable_from; + +template +concept MoveConstructible = ConstructibleFrom && ConvertibleTo; + +template +concept NoThrowMoveConstructible = + NoThrowConstructibleFrom && NoThrowConvertibleTo; + +template +concept CopyConstructible = + MoveConstructible && ConstructibleFrom && ConvertibleTo && + ConstructibleFrom && ConvertibleTo && + ConstructibleFrom && ConvertibleTo; + +template +concept NoThrowCopyConstructible = + NoThrowMoveConstructible && NoThrowConstructibleFrom && + NoThrowConvertibleTo && NoThrowConstructibleFrom && + NoThrowConvertibleTo && NoThrowConstructibleFrom && + NoThrowConvertibleTo; + +template +concept Swappable = std::swappable; + +template +concept EqualityComparable = std::equality_comparable; + +// T is a type +// X is a Container +// A is an Allocator +// if X::allocator_type then +// std::same_as::template rebind_alloc> +// otherwise +// no condition; we use std::allocator regardless of A +// +// see [22.2.1].16 +template + concept EmplaceConstructible = + std::same_as + && ( ( requires { typename X::allocator_type; } // only perform this check if X is + && std::same_as::template rebind_alloc> + && ( requires (A m, T *p, Args&&... args) + { + m.construct (p, std::forward (args)...); + } + || requires (T *p, Args&&... args) + { + { std::construct_at (p, std::forward (args)...) } -> std::same_as; + })) + || (! requires { typename X::allocator_type; } + && requires (T *p, Args&&... args) + { + { std::construct_at (p, std::forward (args)...) } -> std::same_as; + })); + +template >> +concept DefaultInsertable = EmplaceConstructible; + +template >> +concept MoveInsertable = EmplaceConstructible; + +template >> +concept CopyInsertable = + MoveInsertable && EmplaceConstructible && + EmplaceConstructible; + +// same method as with EmplaceConstructible +template >> +concept Erasable = + std::same_as && + ((requires { typename X::allocator_type; } // if X is allocator aware + && std::same_as< + typename X::allocator_type, + typename std::allocator_traits::template rebind_alloc> && + (requires(A m, T* p) { m.destroy(p); } || std::is_destructible_v)) || + (!requires { typename X::allocator_type; } && std::is_destructible_v)); + +template +concept ContextuallyConvertibleToBool = std::constructible_from; + +template +concept BoolConstant = std::derived_from || + std::derived_from; + +template +concept NullablePointer = + EqualityComparable && DefaultConstructible && CopyConstructible && + CopyAssignable && Destructible && + ConstructibleFrom && ConvertibleTo && + requires(T p, T q, std::nullptr_t np) { + T(np); + { p = np } -> std::same_as; + { p != q } -> ContextuallyConvertibleToBool; + { p == np } -> ContextuallyConvertibleToBool; + { np == p } -> ContextuallyConvertibleToBool; + { p != np } -> ContextuallyConvertibleToBool; + { np != p } -> ContextuallyConvertibleToBool; + }; + +static_assert(NullablePointer); +static_assert(!NullablePointer); + +template +concept AllocatorFor = + NoThrowCopyConstructible && + requires(A a, typename std::allocator_traits::template rebind_alloc b, + U xp, typename std::allocator_traits::pointer p, + typename std::allocator_traits::const_pointer cp, + typename std::allocator_traits::void_pointer vp, + typename std::allocator_traits::const_void_pointer cvp, + typename std::allocator_traits::value_type& r, + typename std::allocator_traits::size_type n) { + /** Inner types **/ + // A::pointer + requires NullablePointer::pointer>; + requires std::random_access_iterator< + typename std::allocator_traits::pointer>; + requires std::contiguous_iterator< + typename std::allocator_traits::pointer>; + + // A::const_pointer + requires NullablePointer< + typename std::allocator_traits::const_pointer>; + requires std::random_access_iterator< + typename std::allocator_traits::const_pointer>; + requires std::contiguous_iterator< + typename std::allocator_traits::const_pointer>; + + requires std::convertible_to< + typename std::allocator_traits::pointer, + typename std::allocator_traits::const_pointer>; + + // A::void_pointer + requires NullablePointer::void_pointer>; + + requires std::convertible_to< + typename std::allocator_traits::pointer, + typename std::allocator_traits::void_pointer>; + + requires std::same_as< + typename std::allocator_traits::void_pointer, + typename std::allocator_traits::void_pointer>; + + // A::const_void_pointer + requires NullablePointer< + typename std::allocator_traits::const_void_pointer>; + + requires std::convertible_to< + typename std::allocator_traits::pointer, + typename std::allocator_traits::const_void_pointer>; + + requires std::convertible_to< + typename std::allocator_traits::const_pointer, + typename std::allocator_traits::const_void_pointer>; + + requires std::convertible_to< + typename std::allocator_traits::void_pointer, + typename std::allocator_traits::const_void_pointer>; + + requires std::same_as< + typename std::allocator_traits::const_void_pointer, + typename std::allocator_traits::const_void_pointer>; + + // A::value_type + typename A::value_type; + requires std::same_as; + requires std::same_as::value_type>; + + // A::size_type + requires std::unsigned_integral< + typename std::allocator_traits::size_type>; + + // A::difference_type + requires std::signed_integral< + typename std::allocator_traits::difference_type>; + + // A::template rebind::other [optional] + requires !requires { + typename A::template rebind::other; + } || requires { + requires std::same_as::other>; + requires std::same_as::other>; + }; + + /** Operations on pointers **/ + { *p } -> std::same_as; + { *cp } -> std::same_as; + + // Language in the standard implies that `decltype (p)` must either + // be a raw pointer or implement `operator->`. There is no mention + // of `std::to_address` or `std::pointer_traits::to_address`. + requires std::same_as || requires { + { p.operator->() } -> std::same_as; + }; + + requires std::same_as || + requires { + { + cp.operator->() + } -> std::same_as; + }; + + { static_cast(vp) } -> std::same_as; + { static_cast(cvp) } -> std::same_as; + + { + std::pointer_traits::pointer_to(r) + } -> std::same_as; + + /** Storage and lifetime operations **/ + // a.allocate (n) + { a.allocate(n) } -> std::same_as; + + // a.allocate (n, cvp) [optional] + requires !requires { a.allocate(n, cvp); } || requires { + { a.allocate(n, cvp) } -> std::same_as; + }; + + // a.deallocate (p, n) + { a.deallocate(p, n) } -> std::convertible_to; + + // a.max_size () [optional] + requires !requires { a.max_size(); } || requires { + { a.max_size() } -> std::same_as; + }; + + // a.construct (xp, args) [optional] + requires !requires { a.construct(xp); } || requires { + { a.construct(xp) } -> std::convertible_to; + }; + + // a.destroy (xp) [optional] + requires !requires { a.destroy(xp); } || requires { + { a.destroy(xp) } -> std::convertible_to; + }; + + /** Relationship between instances **/ + requires NoThrowConstructibleFrom; + requires NoThrowConstructibleFrom; + + requires BoolConstant::is_always_equal>; + + /** Influence on container operations **/ + // a.select_on_container_copy_construction () [optional] + requires !requires { a.select_on_container_copy_construction(); } || + requires { + { + a.select_on_container_copy_construction() + } -> std::same_as; + }; + + requires BoolConstant::propagate_on_container_copy_assignment>; + + requires BoolConstant::propagate_on_container_move_assignment>; + + requires BoolConstant< + typename std::allocator_traits::propagate_on_container_swap>; + + { a == b } -> std::same_as; + { a != b } -> std::same_as; + } && + requires(A a1, A a2) { + { a1 == a2 } -> std::same_as; + { a1 != a2 } -> std::same_as; + }; + +static_assert( + AllocatorFor, int>, + "std::allocator failed to meet Allocator concept requirements."); + +template +concept Allocator = AllocatorFor; + +namespace small_vector { + +// Basically, these shut off the concepts if we have an incomplete type. +// This namespace is only needed because of issues on Clang +// preventing us from short-circuiting for incomplete types. + +template +concept Destructible = !concepts::Complete || concepts::Destructible; + +template +concept MoveAssignable = !concepts::Complete || concepts::MoveAssignable; + +template +concept CopyAssignable = !concepts::Complete || concepts::CopyAssignable; + +template +concept MoveConstructible = + !concepts::Complete || concepts::MoveConstructible; + +template +concept CopyConstructible = + !concepts::Complete || concepts::CopyConstructible; + +template +concept Swappable = !concepts::Complete || concepts::Swappable; + +template +concept DefaultInsertable = !concepts::Complete || + concepts::DefaultInsertable; + +template +concept MoveInsertable = + !concepts::Complete || concepts::MoveInsertable; + +template +concept CopyInsertable = + !concepts::Complete || concepts::CopyInsertable; + +template +concept Erasable = + !concepts::Complete || concepts::Erasable; + +template +concept EmplaceConstructible = + !concepts::Complete || + concepts::EmplaceConstructible; + +template +concept AllocatorFor = + !concepts::Complete || concepts::AllocatorFor; + +template +concept Allocator = AllocatorFor; + +} // namespace small_vector + +} // namespace concepts + +template + requires concepts::small_vector::Allocator +struct default_buffer_size; + +template >::value, + typename Allocator = std::allocator> + requires concepts::small_vector::AllocatorFor +class small_vector; + +template + requires concepts::small_vector::Allocator +struct default_buffer_size { + private: + template + struct is_complete : std::false_type {}; + + template + struct is_complete(sizeof(U)))> + : std::true_type {}; + + template + inline static constexpr bool is_complete_v = is_complete::value; + + public: + using allocator_type = Allocator; + using value_type = typename std::allocator_traits::value_type; + using empty_small_vector = small_vector; + + static_assert(is_complete_v, + "Calculation of a default number of elements requires that `T` " + "be complete."); + + static constexpr unsigned buffer_max = 256; + + static constexpr unsigned ideal_total = 64; + + static constexpr unsigned ideal_buffer = + ideal_total - sizeof(empty_small_vector); + + static_assert(sizeof(empty_small_vector) != 0, + "Empty `small_vector` should not have size 0."); + + static_assert(ideal_buffer < ideal_total, + "Empty `small_vector` is larger than ideal_total."); + + static constexpr unsigned value = (sizeof(value_type) <= ideal_buffer) + ? (ideal_buffer / sizeof(value_type)) + : 1; +}; + +template +inline constexpr unsigned default_buffer_size_v = + default_buffer_size::value; + +template +class small_vector_iterator { + public: + using difference_type = DifferenceType; + using value_type = typename std::iterator_traits::value_type; + using pointer = typename std::iterator_traits::pointer; + using reference = typename std::iterator_traits::reference; + using iterator_category = + typename std::iterator_traits::iterator_category; + using iterator_concept = std::contiguous_iterator_tag; + + small_vector_iterator(const small_vector_iterator&) = default; + small_vector_iterator(small_vector_iterator&&) noexcept = default; + small_vector_iterator& operator=(const small_vector_iterator&) = default; + small_vector_iterator& operator=(small_vector_iterator&&) noexcept = default; + ~small_vector_iterator() = default; + +#ifdef NDEBUG + small_vector_iterator() = default; +#else + constexpr small_vector_iterator() noexcept : m_ptr() {} +#endif + + constexpr explicit small_vector_iterator(const Pointer& p) noexcept + : m_ptr(p) {} + + template + requires std::is_convertible_v + constexpr small_vector_iterator( // NOLINT + const small_vector_iterator& other) noexcept + : m_ptr(other.base()) {} + + constexpr small_vector_iterator& operator++() noexcept { + ++m_ptr; + return *this; + } + + constexpr small_vector_iterator operator++(int) noexcept { + return small_vector_iterator(m_ptr++); + } + + constexpr small_vector_iterator& operator--() noexcept { + --m_ptr; + return *this; + } + + constexpr small_vector_iterator operator--(int) noexcept { + return small_vector_iterator(m_ptr--); + } + + constexpr small_vector_iterator& operator+=(difference_type n) noexcept { + m_ptr += n; + return *this; + } + + constexpr small_vector_iterator operator+(difference_type n) const noexcept { + return small_vector_iterator(m_ptr + n); + } + + constexpr small_vector_iterator& operator-=(difference_type n) noexcept { + m_ptr -= n; + return *this; + } + + constexpr small_vector_iterator operator-(difference_type n) const noexcept { + return small_vector_iterator(m_ptr - n); + } + + constexpr reference operator*() const noexcept { + return launder_and_dereference(m_ptr); + } + + constexpr pointer operator->() const noexcept { return get_pointer(m_ptr); } + + constexpr reference operator[](difference_type n) const noexcept { + return launder_and_dereference(m_ptr + n); + } + + constexpr const Pointer& base() const noexcept { return m_ptr; } + + private: + static constexpr pointer get_pointer(Pointer ptr) noexcept + requires std::is_pointer_v + { + return ptr; + } + + static constexpr pointer get_pointer(Pointer ptr) noexcept + requires(!std::is_pointer_v) + { + // Given the requirements for Allocator, Pointer must either be a raw + // pointer, or have a defined operator-> which returns a raw pointer. + return ptr.operator->(); + } + + static constexpr reference launder_and_dereference(Pointer ptr) noexcept + requires std::is_pointer_v + { + return *std::launder(ptr); + } + + static constexpr reference launder_and_dereference(Pointer ptr) noexcept + requires(!std::is_pointer_v) + { + return *ptr; + } + + Pointer m_ptr; +}; + +template +constexpr bool operator==( + const small_vector_iterator& lhs, + const small_vector_iterator& + rhs) noexcept(noexcept(lhs.base() == rhs.base())) + requires requires { + { lhs.base() == rhs.base() } -> std::convertible_to; + } +{ + return lhs.base() == rhs.base(); +} + +template +constexpr bool operator==( + const small_vector_iterator& lhs, + const small_vector_iterator& + rhs) noexcept(noexcept(lhs.base() == rhs.base())) + requires requires { + { lhs.base() == rhs.base() } -> std::convertible_to; + } +{ + return lhs.base() == rhs.base(); +} + +template + requires std::three_way_comparable_with +constexpr auto operator<=>( + const small_vector_iterator& lhs, + const small_vector_iterator& + rhs) noexcept(noexcept(lhs.base() <=> rhs.base())) { + return lhs.base() <=> rhs.base(); +} + +template + requires std::three_way_comparable +constexpr auto operator<=>( + const small_vector_iterator& lhs, + const small_vector_iterator& + rhs) noexcept(noexcept(lhs.base() <=> rhs.base())) { + return lhs.base() <=> rhs.base(); +} + +template +constexpr auto operator<=>( + const small_vector_iterator& lhs, + const small_vector_iterator& + rhs) noexcept(noexcept(lhs.base() < rhs.base()) && + noexcept(rhs.base() < lhs.base())) { + using ordering = std::weak_ordering; + return (lhs.base() < rhs.base()) ? ordering::less + : (rhs.base() < lhs.base()) ? ordering::greater + : ordering::equivalent; +} + +template +constexpr auto operator<=>( + const small_vector_iterator& lhs, + const small_vector_iterator& + rhs) noexcept(noexcept(lhs.base() < rhs.base()) && + noexcept(rhs.base() < lhs.base())) { + using ordering = std::weak_ordering; + return (lhs.base() < rhs.base()) ? ordering::less + : (rhs.base() < lhs.base()) ? ordering::greater + : ordering::equivalent; +} + +template +constexpr DifferenceType operator-( + const small_vector_iterator& lhs, + const small_vector_iterator& rhs) noexcept { + return static_cast(lhs.base() - rhs.base()); +} + +template +constexpr DifferenceType operator-( + const small_vector_iterator& lhs, + const small_vector_iterator& rhs) noexcept { + return static_cast(lhs.base() - rhs.base()); +} + +template +constexpr small_vector_iterator operator+( + DifferenceType n, + const small_vector_iterator& it) noexcept { + return it + n; +} + +namespace detail { + +template +class inline_storage { + public: + using value_ty = T; + + inline_storage() = default; + inline_storage(const inline_storage&) = delete; + inline_storage(inline_storage&&) noexcept = delete; + inline_storage& operator=(const inline_storage&) = delete; + inline_storage& operator=(inline_storage&&) noexcept = delete; + ~inline_storage() = default; + + [[nodiscard]] + constexpr value_ty* get_inline_ptr() noexcept { + return static_cast(static_cast(std::addressof(*m_data))); + } + + [[nodiscard]] + constexpr const value_ty* get_inline_ptr() const noexcept { + return static_cast( + static_cast(std::addressof(*m_data))); + } + + static constexpr size_t element_size() noexcept { return sizeof(value_ty); } + + static constexpr size_t alignment() noexcept { return alignof(value_ty); } + + static constexpr unsigned num_elements() noexcept { return InlineCapacity; } + + static constexpr size_t num_bytes() noexcept { + return num_elements() * element_size(); + } + + private: + alignas(alignment()) std::byte m_data[element_size()][num_elements()]; +}; + +template +class inline_storage { + public: + using value_ty = T; + + inline_storage() = default; + inline_storage(const inline_storage&) = delete; + inline_storage(inline_storage&&) noexcept = delete; + inline_storage& operator=(const inline_storage&) = delete; + inline_storage& operator=(inline_storage&&) noexcept = delete; + ~inline_storage() = default; + + [[nodiscard]] + constexpr value_ty* get_inline_ptr() noexcept { + return nullptr; + } + + [[nodiscard]] + constexpr const value_ty* get_inline_ptr() const noexcept { + return nullptr; + } + + static constexpr size_t element_size() noexcept { return sizeof(value_ty); } + + static constexpr size_t alignment() noexcept { return alignof(value_ty); } + + static constexpr unsigned num_elements() noexcept { return 0; } + + static constexpr size_t num_bytes() noexcept { return 0; } +}; + +template && !std::is_final_v> +class allocator_inliner; + +template +class allocator_inliner : private Allocator { + using alloc_traits = std::allocator_traits; + + static constexpr bool copy_assign_is_noop = + !alloc_traits::propagate_on_container_copy_assignment::value; + + static constexpr bool move_assign_is_noop = + !alloc_traits::propagate_on_container_move_assignment::value; + + static constexpr bool swap_is_noop = + !alloc_traits::propagate_on_container_swap::value; + + template + requires IsNoOp + constexpr void maybe_assign(const allocator_inliner&) noexcept {} + + template + requires(!IsNoOp) + constexpr void maybe_assign(const allocator_inliner& other) noexcept( + noexcept(std::declval().operator=(other))) { + Allocator::operator=(other); + } + + template + requires IsNoOp + constexpr void maybe_assign(allocator_inliner&&) noexcept {} + + template + requires(!IsNoOp) + constexpr void maybe_assign(allocator_inliner&& other) noexcept( + noexcept(std::declval().operator=(std::move(other)))) { + Allocator::operator=(std::move(other)); + } + + public: + allocator_inliner() = default; + allocator_inliner(const allocator_inliner&) = default; + allocator_inliner(allocator_inliner&&) noexcept = default; + ~allocator_inliner() = default; + + constexpr explicit allocator_inliner(const Allocator& alloc) noexcept + : Allocator(alloc) {} + + constexpr allocator_inliner& + operator=(const allocator_inliner& other) noexcept( + noexcept(std::declval().maybe_assign(other))) { + assert( + &other != this && + "`allocator_inliner` should not participate in self-copy-assignment."); + maybe_assign(other); + return *this; + } + + constexpr allocator_inliner& operator=(allocator_inliner&& other) noexcept( + noexcept( + std::declval().maybe_assign(std::move(other)))) { + assert( + &other != this && + "`allocator_inliner` should not participate in self-move-assignment."); + maybe_assign(std::move(other)); + return *this; + } + + constexpr Allocator& allocator_ref() noexcept { return *this; } + + constexpr const Allocator& allocator_ref() const noexcept { return *this; } + + template + requires IsNoOp + constexpr void swap(allocator_inliner&) {} + + template + requires(!IsNoOp) + constexpr void swap(allocator_inliner& other) { + using std::swap; + swap(static_cast(*this), static_cast(other)); + } +}; + +template +class allocator_inliner { + using alloc_traits = std::allocator_traits; + + static constexpr bool copy_assign_is_noop = + !alloc_traits::propagate_on_container_copy_assignment::value; + + static constexpr bool move_assign_is_noop = + !alloc_traits::propagate_on_container_move_assignment::value; + + static constexpr bool swap_is_noop = + !alloc_traits::propagate_on_container_swap::value; + + template + requires IsNoOp + constexpr void maybe_assign(const allocator_inliner&) noexcept {} + + template + requires(!IsNoOp) + constexpr void maybe_assign(const allocator_inliner& other) noexcept( + noexcept(std::declval() = other.m_alloc)) { + m_alloc = other.m_alloc; + } + + template + requires IsNoOp + constexpr void maybe_assign(allocator_inliner&&) noexcept {} + + template + requires(!IsNoOp) + constexpr void maybe_assign(allocator_inliner&& other) noexcept(noexcept( + std::declval() = std::move(other.m_alloc))) { + m_alloc = std::move(other.m_alloc); + } + + public: + allocator_inliner() = default; + allocator_inliner(const allocator_inliner&) = default; + allocator_inliner(allocator_inliner&&) noexcept = default; + ~allocator_inliner() = default; + + constexpr explicit allocator_inliner(const Allocator& alloc) noexcept + : m_alloc(alloc) {} + + constexpr allocator_inliner& + operator=(const allocator_inliner& other) noexcept( + noexcept(std::declval().maybe_assign(other))) { + assert( + &other != this && + "`allocator_inliner` should not participate in self-copy-assignment."); + maybe_assign(other); + return *this; + } + + constexpr allocator_inliner& operator=(allocator_inliner&& other) noexcept( + noexcept( + std::declval().maybe_assign(std::move(other)))) { + assert( + &other != this && + "`allocator_inliner` should not participate in self-move-assignment."); + maybe_assign(std::move(other)); + return *this; + } + + constexpr Allocator& allocator_ref() noexcept { return m_alloc; } + + constexpr const Allocator& allocator_ref() const noexcept { return m_alloc; } + + template + requires IsNoOp + constexpr void swap(allocator_inliner&) {} + + template + requires(!IsNoOp) + constexpr void swap(allocator_inliner& other) { + using std::swap; + swap(m_alloc, other.m_alloc); + } + + private: + Allocator m_alloc; +}; + +template +class allocator_interface : public allocator_inliner { + public: + template + inline static constexpr bool is_complete_v = requires { sizeof(U); }; + + using size_type = typename std::allocator_traits::size_type; + + // If difference_type is larger than size_type then we need + // to rectify that problem. + using difference_type = typename std::conditional_t< + (static_cast( + (std::numeric_limits::max)()) < // less-than + static_cast((std::numeric_limits::difference_type>::max)())), + typename std::make_signed_t, + typename std::allocator_traits::difference_type>; + + private: + using alloc_base = allocator_inliner; + + protected: + using alloc_ty = Allocator; + using alloc_traits = std::allocator_traits; + using value_ty = typename alloc_traits::value_type; + using ptr = typename alloc_traits::pointer; + using cptr = typename alloc_traits::const_pointer; + using vptr = typename alloc_traits::void_pointer; + using cvptr = typename alloc_traits::const_void_pointer; + + // Select the fastest types larger than the user-facing types. These are only + // intended for internal computations, and should not have any memory + // footprint visible to consumers. + using size_ty = typename std::conditional_t< + (sizeof(size_type) <= sizeof(uint8_t)), uint_fast8_t, + typename std::conditional_t< + (sizeof(size_type) <= sizeof(uint16_t)), uint_fast16_t, + typename std::conditional_t< + (sizeof(size_type) <= sizeof(uint32_t)), uint_fast32_t, + typename std::conditional_t<(sizeof(size_type) <= + sizeof(uint64_t)), + uint_fast64_t, size_type>>>>; + + using diff_ty = typename std::conditional_t< + (sizeof(difference_type) <= sizeof(int8_t)), int_fast8_t, + typename std::conditional_t< + (sizeof(difference_type) <= sizeof(int16_t)), int_fast16_t, + typename std::conditional_t< + (sizeof(difference_type) <= sizeof(int32_t)), int_fast32_t, + typename std::conditional_t<(sizeof(difference_type) <= + sizeof(int64_t)), + int_fast64_t, difference_type>>>>; + + using alloc_base::allocator_ref; + + private: + template + struct underlying_if_enum { + using type = T; + }; + + template + requires std::is_enum_v + struct underlying_if_enum : std::underlying_type {}; + + template + using underlying_if_enum_t = typename underlying_if_enum::type; + + template + inline static constexpr bool has_ptr_traits_to_address_v = + requires { std::pointer_traits

::to_address(std::declval

()); }; + + template + inline static constexpr bool has_alloc_construct_v = + is_complete_v && requires { + std::declval().construct(std::declval(), + std::declval()...); + }; + + template + inline static constexpr bool must_use_alloc_construct_v = + !std::is_same_v> && + has_alloc_construct_v; + + template + inline static constexpr bool has_alloc_destroy_v = + is_complete_v && + requires { std::declval().destroy(std::declval()); }; + + template + inline static constexpr bool must_use_alloc_destroy_v = + !std::is_same_v> && has_alloc_destroy_v; + + public: + allocator_interface() = default; + allocator_interface(allocator_interface&&) noexcept = default; + + constexpr allocator_interface& operator=(const allocator_interface&) = + default; + + constexpr allocator_interface& operator=(allocator_interface&&) noexcept = + default; + + ~allocator_interface() = default; + + constexpr allocator_interface(const allocator_interface& other) noexcept + : alloc_base(alloc_traits::select_on_container_copy_construction( + other.allocator_ref())) {} + + constexpr explicit allocator_interface(const alloc_ty& alloc) noexcept + : alloc_base(alloc) {} + + template + constexpr explicit allocator_interface(T&&, const alloc_ty& alloc) noexcept + : allocator_interface(alloc) {} + + template + inline static constexpr bool is_memcpyable_integral_v = + is_complete_v && + (sizeof(underlying_if_enum_t) == + sizeof(underlying_if_enum_t)) && + (std::is_same_v> == + std::is_same_v>) && + std::is_integral_v> && + std::is_integral_v>; + + template + inline static constexpr bool is_convertible_pointer_v = + std::is_pointer_v && std::is_pointer_v && + std::is_convertible_v; + + // Memcpyable assignment. + template + inline static constexpr bool is_memcpyable_v = + is_complete_v && !std::is_reference_v && + std::is_trivially_assignable_v && + std::is_trivially_copyable_v> && + (std::is_same_v>>, + std::remove_cv_t> || + is_memcpyable_integral_v< + std::remove_reference_t>, + std::remove_cv_t> || + is_convertible_pointer_v< + std::remove_reference_t>, + std::remove_cv_t>); + + // Memcpyable construction. + template + inline static constexpr bool is_uninitialized_memcpyable_v = + !std::is_reference_v && std::is_trivially_constructible_v && + std::is_trivially_copyable_v> && + (std::is_same_v< + std::remove_cv_t>>, + std::remove_cv_t> || + is_memcpyable_integral_v>, + std::remove_cv_t> || + is_convertible_pointer_v>, + std::remove_cv_t>) && + (!must_use_alloc_construct_v< + alloc_ty, value_ty, + std::remove_reference_t>> && + !must_use_alloc_destroy_v); + + template + struct is_small_vector_iterator : std::false_type {}; + + template + struct is_small_vector_iterator> + : std::true_type {}; + + template + inline static constexpr bool is_small_vector_iterator_v = + is_small_vector_iterator::value; + + template + inline static constexpr bool is_contiguous_iterator_v = + std::is_same_v || std::is_same_v || + is_small_vector_iterator_v || std::contiguous_iterator; + + template + struct is_memcpyable_iterator { + inline static constexpr bool value = + is_memcpyable_v())> && + is_contiguous_iterator_v; + }; + + // Unwrap move_iterators + template + struct is_memcpyable_iterator> + : is_memcpyable_iterator {}; + + template + inline static constexpr bool is_memcpyable_iterator_v = + is_memcpyable_iterator::value; + + template + struct is_uninitialized_memcpyable_iterator { + inline static constexpr bool value = + is_uninitialized_memcpyable_v())> && + is_contiguous_iterator_v; + }; + + // Unwrap move_iterators + template + struct is_uninitialized_memcpyable_iterator, V> + : is_uninitialized_memcpyable_iterator {}; + + template + inline static constexpr bool is_uninitialized_memcpyable_iterator_v = + is_uninitialized_memcpyable_iterator::value; + + [[noreturn]] + static constexpr void throw_range_length_error() { + throw std::length_error("The specified range is too long."); + } + + static constexpr value_ty* to_address(value_ty* p) noexcept { + static_assert(!std::is_function_v, + "value_ty is a function pointer."); + return p; + } + + static constexpr const value_ty* to_address(const value_ty* p) noexcept { + static_assert(!std::is_function_v, + "value_ty is a function pointer."); + return p; + } + + template + requires has_ptr_traits_to_address_v + static constexpr auto to_address(const Pointer& p) noexcept + -> decltype(std::pointer_traits::to_address(p)) { + return std::pointer_traits::to_address(p); + } + + template + requires(!has_ptr_traits_to_address_v) + static constexpr auto to_address(const Pointer& p) noexcept + -> decltype(to_address(p.operator->())) { + return to_address(p.operator->()); + } + + template + [[nodiscard]] + static consteval size_t numeric_max() noexcept { + static_assert(0 <= (std::numeric_limits::max)(), + "Integer is nonpositive."); + return static_cast((std::numeric_limits::max)()); + } + + [[nodiscard]] + static constexpr size_ty internal_range_length(cptr first, + cptr last) noexcept { + // This is guaranteed to be less than or equal to max size_ty. + return static_cast(last - first); + } + + template + [[nodiscard]] + static constexpr size_ty external_range_length_impl( + RandomIt first, RandomIt last, std::random_access_iterator_tag) { + assert(0 <= (last - first) && "Invalid range."); + const auto len = static_cast(last - first); +#ifndef NDEBUG + if (numeric_max() < len) + throw_range_length_error(); +#endif + return static_cast(len); + } + + template + [[nodiscard]] + static constexpr size_ty external_range_length_impl( + ForwardIt first, ForwardIt last, std::forward_iterator_tag) { + if (std::is_constant_evaluated()) { + // Make sure constexpr doesn't get broken by `using namespace + // std::rel_ops`. + typename std::iterator_traits::difference_type len = 0; + for (; !(first == last); ++first) { + ++len; + } + assert(static_cast(len) <= numeric_max()); + return static_cast(len); + } + + const auto len = static_cast(std::distance(first, last)); +#ifndef NDEBUG + if (numeric_max() < len) + throw_range_length_error(); +#endif + return static_cast(len); + } + + template ::difference_type> + requires(numeric_max() < numeric_max()) + [[nodiscard]] + static constexpr size_ty external_range_length(ForwardIt first, + ForwardIt last) { + using iterator_cat = + typename std::iterator_traits::iterator_category; + return external_range_length_impl(first, last, iterator_cat{}); + } + + template ::difference_type> + requires(!(numeric_max() < numeric_max())) + [[nodiscard]] + static constexpr size_ty external_range_length(ForwardIt first, + ForwardIt last) noexcept { + if (std::is_constant_evaluated()) { + // Make sure constexpr doesn't get broken by `using namespace + // std::rel_ops`. + size_ty len = 0; + for (; !(first == last); ++first) { + ++len; + } + return len; + } + + return static_cast(std::distance(first, last)); + } + + template ::difference_type, + typename Integer = IteratorDiffT> + [[nodiscard]] + static constexpr Iterator unchecked_next(Iterator pos, + Integer n = 1) noexcept { + unchecked_advance(pos, static_cast(n)); + return pos; + } + + template ::difference_type, + typename Integer = IteratorDiffT> + [[nodiscard]] + static constexpr Iterator unchecked_prev(Iterator pos, + Integer n = 1) noexcept { + unchecked_advance(pos, -static_cast(n)); + return pos; + } + + template ::difference_type, + typename Integer = IteratorDiffT> + static constexpr void unchecked_advance(Iterator& pos, Integer n) noexcept { + std::advance(pos, static_cast(n)); + } + + [[nodiscard]] + constexpr size_ty get_max_size() const noexcept { + // This is protected from max/min macros. + return (std::min)( + static_cast(alloc_traits::max_size(allocator_ref())), + static_cast(numeric_max())); + } + + [[nodiscard]] + constexpr ptr allocate(size_ty n) { + return alloc_traits::allocate(allocator_ref(), static_cast(n)); + } + + [[nodiscard]] + constexpr ptr allocate_with_hint(size_ty n, cptr hint) { + return alloc_traits::allocate(allocator_ref(), static_cast(n), + hint); + } + + constexpr void deallocate(ptr p, size_ty n) { + alloc_traits::deallocate(allocator_ref(), to_address(p), + static_cast(n)); + } + + template + requires is_uninitialized_memcpyable_v + constexpr void construct(ptr p, U&& val) noexcept { + if (std::is_constant_evaluated()) { + alloc_traits::construct(allocator_ref(), to_address(p), + std::forward(val)); + return; + } + std::memcpy(to_address(p), &val, sizeof(value_ty)); + } + + // basically alloc_traits::construct + // all this is so we can replicate C++20 behavior in the other overload + template + requires(sizeof...(Args) != 1 || + !is_uninitialized_memcpyable_v) && + has_alloc_construct_v + constexpr void construct(ptr p, Args&&... args) noexcept( + noexcept(alloc_traits::construct(std::declval(), + std::declval(), + std::forward(args)...))) { + alloc_traits::construct(allocator_ref(), to_address(p), + std::forward(args)...); + } + + template + requires(sizeof...(Args) != 1 || + !is_uninitialized_memcpyable_v) && + (!has_alloc_construct_v) && requires { + ::new (std::declval()) V(std::declval()...); + } + constexpr void construct(ptr p, Args&&... args) noexcept(noexcept( + ::new(std::declval()) value_ty(std::declval()...))) { + construct_at(to_address(p), std::forward(args)...); + } + + template + requires std::is_trivially_destructible_v && + (!must_use_alloc_destroy_v) + constexpr void destroy(ptr) const noexcept {} + + template + requires(!std::is_trivially_destructible_v || + must_use_alloc_destroy_v) && + has_alloc_destroy_v + constexpr void destroy(ptr p) noexcept { + alloc_traits::destroy(allocator_ref(), to_address(p)); + } + + // defined so we match C++20 behavior in all cases. + template + requires(!std::is_trivially_destructible_v || + must_use_alloc_destroy_v) && + (!has_alloc_destroy_v) + constexpr void destroy(ptr p) noexcept { + destroy_at(to_address(p)); + } + + template + requires std::is_trivially_destructible_v && + (!must_use_alloc_destroy_v) + constexpr void destroy_range(ptr, ptr) const noexcept {} + + template + requires(!std::is_trivially_destructible_v || + must_use_alloc_destroy_v) + constexpr void destroy_range(ptr first, ptr last) noexcept { + for (; !(first == last); ++first) { + destroy(first); + } + } + + // allowed if trivially copyable and we use the standard allocator + // and InputIt is a contiguous iterator + template + requires is_uninitialized_memcpyable_iterator_v + constexpr ptr uninitialized_copy(ForwardIt first, ForwardIt last, + ptr dest) noexcept { + static_assert(std::is_constructible_v, + "`value_type` must be copy constructible."); + + if (std::is_constant_evaluated()) { + return default_uninitialized_copy(first, last, dest); + } + + const size_ty num_copy = external_range_length(first, last); + if (num_copy != 0) { + std::memcpy(to_address(dest), to_address(first), + num_copy * sizeof(value_ty)); + } + return unchecked_next(dest, num_copy); + } + + template + requires is_uninitialized_memcpyable_iterator_v + constexpr ptr uninitialized_copy(std::move_iterator first, + std::move_iterator last, + ptr dest) noexcept { + return uninitialized_copy(first.base(), last.base(), dest); + } + + template + requires(!is_uninitialized_memcpyable_iterator_v) + constexpr ptr uninitialized_copy(InputIt first, InputIt last, ptr d_first) { + return default_uninitialized_copy(first, last, d_first); + } + + template + constexpr ptr default_uninitialized_copy(InputIt first, InputIt last, + ptr d_first) { + ptr d_last = d_first; + try { + for (; !(first == last); ++first, static_cast(++d_last)) { + construct(d_last, *first); + } + return d_last; + } catch (...) { + destroy_range(d_first, d_last); + throw; + } + } + + template + requires(std::is_trivially_constructible_v && + !must_use_alloc_construct_v) + constexpr ptr uninitialized_value_construct(ptr first, ptr last) { + if (std::is_constant_evaluated()) { + return default_uninitialized_value_construct(first, last); + } + std::fill(first, last, value_ty()); + return last; + } + + template + requires(!std::is_trivially_constructible_v || + must_use_alloc_construct_v) + constexpr ptr uninitialized_value_construct(ptr first, ptr last) { + return default_uninitialized_value_construct(first, last); + } + + constexpr ptr default_uninitialized_value_construct(ptr first, ptr last) { + ptr curr = first; + try { + for (; !(curr == last); ++curr) { + construct(curr); + } + return curr; + } catch (...) { + destroy_range(first, curr); + throw; + } + } + + constexpr ptr uninitialized_fill(ptr first, ptr last) { + return uninitialized_value_construct(first, last); + } + + constexpr ptr uninitialized_fill(ptr first, ptr last, const value_ty& val) { + ptr curr = first; + try { + for (; !(curr == last); ++curr) { + construct(curr, val); + } + return curr; + } catch (...) { + destroy_range(first, curr); + throw; + } + } + + private: + // If value_ty is an array, replicate C++20 behavior (I don't think that + // value_ty can actually be an array because of the Erasable requirement, but + // there shouldn't be any runtime cost for being defensive here). + template + requires std::is_array_v + static constexpr void destroy_at(value_ty* p) noexcept { + for (auto& e : *p) { + destroy_at(std::addressof(e)); + } + } + + template + requires(!std::is_array_v) + static constexpr void destroy_at(value_ty* p) noexcept { + p->~value_ty(); + } + + template + static constexpr auto construct_at(value_ty* p, Args&&... args) noexcept( + noexcept(::new(std::declval()) V(std::declval()...))) + -> decltype(::new(std::declval()) V(std::declval()...)) { + if (std::is_constant_evaluated()) { + return std::construct_at(p, std::forward(args)...); + } + void* vp = const_cast(static_cast(p)); + return ::new (vp) value_ty(std::forward(args)...); + } +}; + +template +class small_vector_data_base { + public: + using ptr = Pointer; + using size_ty = SizeT; + + small_vector_data_base() = default; + small_vector_data_base(const small_vector_data_base&) = default; + small_vector_data_base(small_vector_data_base&&) noexcept = default; + small_vector_data_base& operator=(const small_vector_data_base&) = default; + small_vector_data_base& operator=(small_vector_data_base&&) noexcept = + default; + ~small_vector_data_base() = default; + + constexpr ptr data_ptr() const noexcept { return m_data_ptr; } + constexpr size_ty capacity() const noexcept { return m_capacity; } + constexpr size_ty size() const noexcept { return m_size; } + + constexpr void set_data_ptr(ptr data_ptr) noexcept { m_data_ptr = data_ptr; } + constexpr void set_capacity(size_ty capacity) noexcept { + m_capacity = capacity; + } + constexpr void set_size(size_ty size) noexcept { m_size = size; } + + constexpr void set(ptr data_ptr, size_ty capacity, size_ty size) { + m_data_ptr = data_ptr; + m_capacity = capacity; + m_size = size; + } + + constexpr void swap_data_ptr(small_vector_data_base& other) noexcept { + using std::swap; + swap(m_data_ptr, other.m_data_ptr); + } + + constexpr void swap_capacity(small_vector_data_base& other) noexcept { + using std::swap; + swap(m_capacity, other.m_capacity); + } + + constexpr void swap_size(small_vector_data_base& other) noexcept { + using std::swap; + swap(m_size, other.m_size); + } + + constexpr void swap(small_vector_data_base& other) noexcept { + using std::swap; + swap(m_data_ptr, other.m_data_ptr); + swap(m_capacity, other.m_capacity); + swap(m_size, other.m_size); + } + + private: + ptr m_data_ptr; + size_ty m_capacity; + size_ty m_size; +}; + +template +class small_vector_data : public small_vector_data_base { + public: + using value_ty = T; + + small_vector_data() = default; + small_vector_data(const small_vector_data&) = delete; + small_vector_data(small_vector_data&&) noexcept = delete; + small_vector_data& operator=(const small_vector_data&) = delete; + small_vector_data& operator=(small_vector_data&&) noexcept = delete; + ~small_vector_data() = default; + + constexpr value_ty* storage() noexcept { return m_storage.get_inline_ptr(); } + + constexpr const value_ty* storage() const noexcept { + return m_storage.get_inline_ptr(); + } + + private: + inline_storage m_storage; +}; + +template +class small_vector_data + : public small_vector_data_base, + private inline_storage { + using base = inline_storage; + + public: + using value_ty = T; + + small_vector_data() = default; + small_vector_data(const small_vector_data&) = delete; + small_vector_data(small_vector_data&&) noexcept = delete; + small_vector_data& operator=(const small_vector_data&) = delete; + small_vector_data& operator=(small_vector_data&&) noexcept = delete; + ~small_vector_data() = default; + + constexpr value_ty* storage() noexcept { return base::get_inline_ptr(); } + + constexpr const value_ty* storage() const noexcept { + return base::get_inline_ptr(); + } +}; + +template +class small_vector_base : public allocator_interface { + public: + using size_type = typename allocator_interface::size_type; + using difference_type = + typename allocator_interface::difference_type; + + template + friend class small_vector_base; + + protected: + using alloc_interface = allocator_interface; + using alloc_traits = typename alloc_interface::alloc_traits; + using alloc_ty = Allocator; + + using value_ty = typename alloc_interface::value_ty; + using ptr = typename alloc_interface::ptr; + using cptr = typename alloc_interface::cptr; + using size_ty = typename alloc_interface::size_ty; + using diff_ty = typename alloc_interface::diff_ty; + + static_assert( + alloc_interface::template is_complete_v || InlineCapacity == 0, + "`value_type` must be complete for instantiation of a non-zero number " + "of inline elements."); + + template + inline static constexpr bool is_complete_v = + alloc_interface::template is_complete_v; + + using alloc_interface::allocator_ref; + using alloc_interface::construct; + using alloc_interface::deallocate; + using alloc_interface::destroy; + using alloc_interface::destroy_range; + using alloc_interface::external_range_length; + using alloc_interface::get_max_size; + using alloc_interface::internal_range_length; + using alloc_interface::to_address; + using alloc_interface::unchecked_advance; + using alloc_interface::unchecked_next; + using alloc_interface::unchecked_prev; + using alloc_interface::uninitialized_copy; + using alloc_interface::uninitialized_fill; + using alloc_interface::uninitialized_value_construct; + + template + [[nodiscard]] + static consteval size_t numeric_max() noexcept { + return alloc_interface::template numeric_max(); + } + + [[nodiscard]] + static consteval size_ty get_inline_capacity() noexcept { + return static_cast(InlineCapacity); + } + + template + inline static constexpr bool is_emplace_constructible_v = + is_complete_v && requires { + std::declval().construct(std::declval(), + std::declval()...); + }; + + template + inline static constexpr bool is_nothrow_emplace_constructible_v = + is_complete_v && requires { + noexcept(std::declval().construct( + std::declval(), std::declval()...)); + }; + + template + inline static constexpr bool is_explicitly_move_insertable_v = + is_emplace_constructible_v; + + template + inline static constexpr bool is_explicitly_nothrow_move_insertable_v = + is_nothrow_emplace_constructible_v; + + template + inline static constexpr bool is_explicitly_copy_insertable_v = + is_emplace_constructible_v && is_emplace_constructible_v; + + template + inline static constexpr bool is_explicitly_nothrow_copy_insertable_v = + is_nothrow_emplace_constructible_v && + is_nothrow_emplace_constructible_v; + + template + inline static constexpr bool relocate_with_move_v = + std::is_nothrow_move_constructible_v || + !is_explicitly_copy_insertable_v; + + template + inline static constexpr bool allocations_are_movable_v = + std::is_same_v, A> || + std::allocator_traits::propagate_on_container_move_assignment::value || + std::allocator_traits::is_always_equal::value; + + template + inline static constexpr bool allocations_are_swappable_v = + std::is_same_v, A> || + std::allocator_traits::propagate_on_container_swap::value || + std::allocator_traits::is_always_equal::value; + + template + inline static constexpr bool is_memcpyable_v = + alloc_interface::template is_memcpyable_v; + + template + inline static constexpr bool is_memcpyable_iterator_v = + alloc_interface::template is_memcpyable_iterator_v; + + [[noreturn]] + static constexpr void throw_overflow_error() { + throw std::overflow_error("The requested conversion would overflow."); + } + + [[noreturn]] + static constexpr void throw_index_error() { + throw std::out_of_range("The requested index was out of range."); + } + + [[noreturn]] + static constexpr void throw_increment_error() { + throw std::domain_error( + "The requested increment was outside of the allowed range."); + } + + [[noreturn]] + static constexpr void throw_allocation_size_error() { + throw std::length_error( + "The required allocation exceeds the maximum size."); + } + + [[nodiscard]] + constexpr ptr ptr_cast( + const small_vector_iterator& it) noexcept { + return unchecked_next(begin_ptr(), it.base() - begin_ptr()); + } + + private: + class stack_temporary { + public: + stack_temporary() = delete; + stack_temporary(const stack_temporary&) = delete; + stack_temporary(stack_temporary&&) noexcept = delete; + stack_temporary& operator=(const stack_temporary&) = delete; + stack_temporary& operator=(stack_temporary&&) noexcept = delete; + + template + constexpr explicit stack_temporary(alloc_interface& alloc_iface, + Args&&... args) + : m_interface(alloc_iface) { + m_interface.construct(get_pointer(), std::forward(args)...); + } + + constexpr ~stack_temporary() { m_interface.destroy(get_pointer()); } + + [[nodiscard]] + constexpr const value_ty& get() const noexcept { + return *get_pointer(); + } + + [[nodiscard]] + constexpr value_ty&& release() noexcept { + return std::move(*get_pointer()); + } + + private: + [[nodiscard]] + constexpr cptr get_pointer() const noexcept { + return static_cast( + static_cast(std::addressof(m_data))); + } + + [[nodiscard]] + constexpr ptr get_pointer() noexcept { + return static_cast(static_cast(std::addressof(m_data))); + } + + alloc_interface& m_interface; + alignas(value_ty) std::byte m_data[sizeof(value_ty)]; + }; + + class heap_temporary { + public: + heap_temporary() = delete; + heap_temporary(const heap_temporary&) = delete; + heap_temporary(heap_temporary&&) noexcept = delete; + heap_temporary& operator=(const heap_temporary&) = delete; + heap_temporary& operator=(heap_temporary&&) noexcept = delete; + + template + constexpr explicit heap_temporary(alloc_interface& alloc_iface, + Args&&... args) + : m_interface(alloc_iface), + m_data_ptr(alloc_iface.allocate(sizeof(value_ty))) { + try { + m_interface.construct(m_data_ptr, std::forward(args)...); + } catch (...) { + m_interface.deallocate(m_data_ptr, sizeof(value_ty)); + throw; + } + } + + constexpr ~heap_temporary() { + m_interface.destroy(m_data_ptr); + m_interface.deallocate(m_data_ptr, sizeof(value_ty)); + } + + [[nodiscard]] + constexpr const value_ty& get() const noexcept { + return *m_data_ptr; + } + + [[nodiscard]] + constexpr value_ty&& release() noexcept { + return std::move(*m_data_ptr); + } + + private: + alloc_interface& m_interface; + ptr m_data_ptr; + }; + + constexpr void wipe() { + destroy_range(begin_ptr(), end_ptr()); + if (has_allocation()) { + deallocate(data_ptr(), get_capacity()); + } + } + + constexpr void set_data_ptr(ptr data_ptr) noexcept { + m_data.set_data_ptr(data_ptr); + } + + constexpr void set_capacity(size_ty capacity) noexcept { + m_data.set_capacity(static_cast(capacity)); + } + + constexpr void set_size(size_ty size) noexcept { + m_data.set_size(static_cast(size)); + } + + constexpr void set_data(ptr data_ptr, size_ty capacity, + size_ty size) noexcept { + m_data.set(data_ptr, static_cast(capacity), + static_cast(size)); + } + + constexpr void swap_data_ptr(small_vector_base& other) noexcept { + m_data.swap_data_ptr(other.m_data); + } + + constexpr void swap_capacity(small_vector_base& other) noexcept { + m_data.swap_capacity(other.m_data); + } + + constexpr void swap_size(small_vector_base& other) noexcept { + m_data.swap_size(other.m_data); + } + + constexpr void swap_allocation(small_vector_base& other) noexcept { + m_data.swap(other.m_data); + } + + constexpr void reset_data(ptr data_ptr, size_ty capacity, size_ty size) { + wipe(); + m_data.set(data_ptr, static_cast(capacity), + static_cast(size)); + } + + constexpr void increase_size(size_ty n) noexcept { + m_data.set_size(get_size() + n); + } + + constexpr void decrease_size(size_ty n) noexcept { + m_data.set_size(get_size() - n); + } + + constexpr ptr unchecked_allocate(size_ty n) { + assert(InlineCapacity < n && + "Allocated capacity should be greater than InlineCapacity."); + return alloc_interface::allocate(n); + } + + constexpr ptr unchecked_allocate(size_ty n, cptr hint) { + assert(InlineCapacity < n && + "Allocated capacity should be greater than InlineCapacity."); + return alloc_interface::allocate_with_hint(n, hint); + } + + constexpr ptr checked_allocate(size_ty n) { + if (get_max_size() < n) { + throw_allocation_size_error(); + } + return unchecked_allocate(n); + } + + protected: + [[nodiscard]] + constexpr size_ty unchecked_calculate_new_capacity( + const size_ty minimum_required_capacity) const noexcept { + const size_ty current_capacity = get_capacity(); + + assert(current_capacity < minimum_required_capacity); + + if (get_max_size() - current_capacity <= current_capacity) { + return get_max_size(); + } + + // Note: This growth factor might be theoretically superior, but in testing + // it falls flat: size_ty new_capacity = current_capacity + + // (current_capacity / 2); + + const size_ty new_capacity = 2 * current_capacity; + if (new_capacity < minimum_required_capacity) { + return minimum_required_capacity; + } + return new_capacity; + } + + [[nodiscard]] + constexpr size_ty checked_calculate_new_capacity( + const size_ty minimum_required_capacity) const { + if (get_max_size() < minimum_required_capacity) { + throw_allocation_size_error(); + } + return unchecked_calculate_new_capacity(minimum_required_capacity); + } + + template + constexpr small_vector_base& copy_assign_default( + const small_vector_base& other) { + if (get_capacity() < other.get_size()) { + // Reallocate. + size_ty new_capacity = unchecked_calculate_new_capacity(other.get_size()); + ptr new_data_ptr = + unchecked_allocate(new_capacity, other.allocation_end_ptr()); + + try { + uninitialized_copy(other.begin_ptr(), other.end_ptr(), new_data_ptr); + } catch (...) { + deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, other.get_size()); + } else { + if (get_size() < other.get_size()) { + // No reallocation, partially in uninitialized space. + std::copy_n(other.begin_ptr(), get_size(), begin_ptr()); + uninitialized_copy(unchecked_next(other.begin_ptr(), get_size()), + other.end_ptr(), end_ptr()); + } else { + destroy_range( + copy_range(other.begin_ptr(), other.end_ptr(), begin_ptr()), + end_ptr()); + } + + // data_ptr and capacity do not change in this case. + set_size(other.get_size()); + } + + alloc_interface::operator=(other); + return *this; + } + + template + requires(AT::propagate_on_container_copy_assignment::value && + !AT::is_always_equal::value) + constexpr small_vector_base& copy_assign( + const small_vector_base& other) { + if (other.allocator_ref() == allocator_ref()) { + return copy_assign_default(other); + } + + if (InlineCapacity < other.get_size()) { + alloc_interface new_alloc(other); + + const size_ty new_capacity = other.get_size(); + const ptr new_data_ptr = new_alloc.allocate_with_hint( + new_capacity, other.allocation_end_ptr()); + + try { + uninitialized_copy(other.begin_ptr(), other.end_ptr(), new_data_ptr); + } catch (...) { + new_alloc.deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, other.get_size()); + alloc_interface::operator=(new_alloc); + } else { + if (has_allocation()) { + ptr new_data_ptr; + if (std::is_constant_evaluated()) { + alloc_interface new_alloc(other); + new_data_ptr = new_alloc.allocate(InlineCapacity); + } else { + new_data_ptr = storage_ptr(); + } + + uninitialized_copy(other.begin_ptr(), other.end_ptr(), new_data_ptr); + destroy_range(begin_ptr(), end_ptr()); + deallocate(data_ptr(), get_capacity()); + set_data_ptr(new_data_ptr); + set_capacity(InlineCapacity); + } else if (get_size() < other.get_size()) { + std::copy_n(other.begin_ptr(), get_size(), begin_ptr()); + uninitialized_copy(unchecked_next(other.begin_ptr(), get_size()), + other.end_ptr(), end_ptr()); + } else { + destroy_range( + copy_range(other.begin_ptr(), other.end_ptr(), begin_ptr()), + end_ptr()); + } + set_size(other.get_size()); + alloc_interface::operator=(other); + } + + return *this; + } + + template + requires(!AT::propagate_on_container_copy_assignment::value || + AT::is_always_equal::value) + constexpr small_vector_base& copy_assign( + const small_vector_base& other) { + return copy_assign_default(other); + } + + template + constexpr void move_allocation_pointer( + small_vector_base&& other) noexcept { + reset_data(other.data_ptr(), other.get_capacity(), other.get_size()); + other.set_default(); + } + + template + requires(N == 0) + constexpr small_vector_base& move_assign_default( + small_vector_base&& other) noexcept { + move_allocation_pointer(std::move(other)); + alloc_interface::operator=(std::move(other)); + return *this; + } + + template + requires(LessEqualI <= InlineCapacity) + constexpr small_vector_base& move_assign_default( + small_vector_base&& + other) noexcept(std::is_nothrow_move_assignable_v && + std::is_nothrow_move_constructible_v) { + // We only move the allocation pointer over if it has strictly greater + // capacity than the inline capacity of `*this` because allocations can + // never have a smaller capacity than the inline capacity. + if (InlineCapacity < other.get_capacity()) { + move_allocation_pointer(std::move(other)); + } else { + // We are guaranteed to have sufficient capacity to store the elements. + if (InlineCapacity < get_capacity()) { + ptr new_data_ptr; + if (std::is_constant_evaluated()) { + new_data_ptr = other.allocate(InlineCapacity); + } else { + new_data_ptr = storage_ptr(); + } + + uninitialized_move(other.begin_ptr(), other.end_ptr(), new_data_ptr); + destroy_range(begin_ptr(), end_ptr()); + deallocate(data_ptr(), get_capacity()); + set_data_ptr(new_data_ptr); + set_capacity(InlineCapacity); + } else if (get_size() < other.get_size()) { + // There are more elements in `other`. + // Overwrite the existing range and uninitialized move the rest. + ptr other_pivot = unchecked_next(other.begin_ptr(), get_size()); + std::move(other.begin_ptr(), other_pivot, begin_ptr()); + uninitialized_move(other_pivot, other.end_ptr(), end_ptr()); + } else { + // There are the same number or fewer elements in `other`. + // Overwrite part of the existing range and destroy the rest. + ptr new_end = + std::move(other.begin_ptr(), other.end_ptr(), begin_ptr()); + destroy_range(new_end, end_ptr()); + } + + set_size(other.get_size()); + + // Note: We do not need to deallocate any allocations in `other` because + // the value of + // an object meeting the Allocator named requirements does not + // change value after a move. + } + + alloc_interface::operator=(std::move(other)); + return *this; + } + + template + requires(InlineCapacity < GreaterI) + constexpr small_vector_base& move_assign_default( + small_vector_base&& other) { + if (other.has_allocation()) { + move_allocation_pointer(std::move(other)); + } else if (get_capacity() < other.get_size() || + (has_allocation() && + !(other.allocator_ref() == allocator_ref()))) { + // Reallocate. + + // The compiler should be able to optimize this. + size_ty new_capacity = + get_capacity() < other.get_size() + ? unchecked_calculate_new_capacity(other.get_size()) + : get_capacity(); + + ptr new_data_ptr = + other.allocate_with_hint(new_capacity, other.allocation_end_ptr()); + + try { + uninitialized_move(other.begin_ptr(), other.end_ptr(), new_data_ptr); + } catch (...) { + other.deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, other.get_size()); + } else { + if (get_size() < other.get_size()) { + // There are more elements in `other`. + // Overwrite the existing range and uninitialized move the rest. + ptr other_pivot = unchecked_next(other.begin_ptr(), get_size()); + std::move(other.begin_ptr(), other_pivot, begin_ptr()); + uninitialized_move(other_pivot, other.end_ptr(), end_ptr()); + } else { + // fewer elements in other + // overwrite part of the existing range and destroy the rest + ptr new_end = + std::move(other.begin_ptr(), other.end_ptr(), begin_ptr()); + destroy_range(new_end, end_ptr()); + } + + // `data_ptr` and `capacity` do not change in this case. + set_size(other.get_size()); + } + + alloc_interface::operator=(std::move(other)); + return *this; + } + + template + constexpr small_vector_base& move_assign_unequal_no_propagate( + small_vector_base&& other) { + if (get_capacity() < other.get_size()) { + // Reallocate. + size_ty new_capacity = unchecked_calculate_new_capacity(other.get_size()); + ptr new_data_ptr = + unchecked_allocate(new_capacity, other.allocation_end_ptr()); + + try { + uninitialized_move(other.begin_ptr(), other.end_ptr(), new_data_ptr); + } catch (...) { + deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, other.get_size()); + } else { + if (get_size() < other.get_size()) { + // There are more elements in `other`. + // Overwrite the existing range and uninitialized move the rest. + ptr other_pivot = unchecked_next(other.begin_ptr(), get_size()); + std::move(other.begin_ptr(), other_pivot, begin_ptr()); + uninitialized_move(other_pivot, other.end_ptr(), end_ptr()); + } else { + // There are fewer elements in `other`. + // Overwrite part of the existing range and destroy the rest. + destroy_range( + std::move(other.begin_ptr(), other.end_ptr(), begin_ptr()), + end_ptr()); + } + + // data_ptr and capacity do not change in this case + set_size(other.get_size()); + } + + alloc_interface::operator=(std::move(other)); + return *this; + } + + template + requires allocations_are_movable_v + constexpr small_vector_base& + move_assign(small_vector_base&& other) noexcept( + noexcept(std::declval().move_assign_default( + std::move(other)))) { + return move_assign_default(std::move(other)); + } + + template + requires(!allocations_are_movable_v) + constexpr small_vector_base& move_assign( + small_vector_base&& other) { + if (other.allocator_ref() == allocator_ref()) { + return move_assign_default(std::move(other)); + } + return move_assign_unequal_no_propagate(std::move(other)); + } + + template + requires(I == 0) + constexpr void move_initialize(small_vector_base&& other) noexcept { + set_data(other.data_ptr(), other.get_capacity(), other.get_size()); + other.set_default(); + } + + template + requires(LessEqualI <= InlineCapacity) + constexpr void + move_initialize(small_vector_base&& other) noexcept( + std::is_nothrow_move_constructible_v) { + if (InlineCapacity < other.get_capacity()) { + set_data(other.data_ptr(), other.get_capacity(), other.get_size()); + other.set_default(); + } else { + set_to_inline_storage(); + uninitialized_move(other.begin_ptr(), other.end_ptr(), data_ptr()); + set_size(other.get_size()); + } + } + + template + requires(InlineCapacity < GreaterI) + constexpr void move_initialize( + small_vector_base&& other) { + if (other.has_allocation()) { + set_data(other.data_ptr(), other.get_capacity(), other.get_size()); + other.set_default(); + } else { + if (InlineCapacity < other.get_size()) { + // We may throw in this case. + set_data_ptr( + unchecked_allocate(other.get_size(), other.allocation_end_ptr())); + set_capacity(other.get_size()); + + try { + uninitialized_move(other.begin_ptr(), other.end_ptr(), data_ptr()); + } catch (...) { + deallocate(data_ptr(), get_capacity()); + throw; + } + } else { + set_to_inline_storage(); + uninitialized_move(other.begin_ptr(), other.end_ptr(), data_ptr()); + } + + set_size(other.get_size()); + } + } + + public: + small_vector_base(const small_vector_base&) = delete; + small_vector_base(small_vector_base&&) noexcept = delete; + small_vector_base& operator=(const small_vector_base&) = delete; + small_vector_base& operator=(small_vector_base&&) noexcept = delete; + + constexpr small_vector_base() noexcept { set_default(); } + + static constexpr struct bypass_tag { + } bypass{}; + + template + constexpr small_vector_base(bypass_tag, + const small_vector_base& other, + const MaybeAlloc&... alloc) + : alloc_interface(other, alloc...) { + if (InlineCapacity < other.get_size()) { + set_data_ptr( + unchecked_allocate(other.get_size(), other.allocation_end_ptr())); + set_capacity(other.get_size()); + + try { + uninitialized_copy(other.begin_ptr(), other.end_ptr(), data_ptr()); + } catch (...) { + deallocate(data_ptr(), get_capacity()); + throw; + } + } else { + set_to_inline_storage(); + uninitialized_copy(other.begin_ptr(), other.end_ptr(), data_ptr()); + } + + set_size(other.get_size()); + } + + template + constexpr small_vector_base( + bypass_tag, + small_vector_base&& + other) noexcept(std::is_nothrow_move_constructible_v || + (I == 0 && I == InlineCapacity)) + : alloc_interface(std::move(other)) { + move_initialize(std::move(other)); + } + + template + requires std::same_as> || + std::allocator_traits::is_always_equal::value + constexpr small_vector_base( + bypass_tag, small_vector_base&& other, + const alloc_ty&) noexcept(noexcept(small_vector_base(bypass, + std::move(other)))) + : small_vector_base(bypass, std::move(other)) {} + + template + requires(!(std::same_as> || + std::allocator_traits::is_always_equal::value)) + constexpr small_vector_base(bypass_tag, + small_vector_base&& other, + const alloc_ty& alloc) + : alloc_interface(alloc) { + if (other.allocator_ref() == alloc) { + move_initialize(std::move(other)); + return; + } + + if (InlineCapacity < other.get_size()) { + // We may throw in this case. + set_data_ptr( + unchecked_allocate(other.get_size(), other.allocation_end_ptr())); + set_capacity(other.get_size()); + + try { + uninitialized_move(other.begin_ptr(), other.end_ptr(), data_ptr()); + } catch (...) { + deallocate(data_ptr(), get_capacity()); + throw; + } + } else { + set_to_inline_storage(); + uninitialized_move(other.begin_ptr(), other.end_ptr(), data_ptr()); + } + + set_size(other.get_size()); + } + + constexpr explicit small_vector_base(const alloc_ty& alloc) noexcept + : alloc_interface(alloc) { + set_default(); + } + + constexpr small_vector_base(size_ty count, const alloc_ty& alloc) + : alloc_interface(alloc) { + if (InlineCapacity < count) { + set_data_ptr(checked_allocate(count)); + set_capacity(count); + } else { + set_to_inline_storage(); + } + + try { + uninitialized_value_construct(begin_ptr(), + unchecked_next(begin_ptr(), count)); + } catch (...) { + if (has_allocation()) { + deallocate(data_ptr(), get_capacity()); + } + throw; + } + set_size(count); + } + + constexpr small_vector_base(size_ty count, const value_ty& val, + const alloc_ty& alloc) + : alloc_interface(alloc) { + if (InlineCapacity < count) { + set_data_ptr(checked_allocate(count)); + set_capacity(count); + } else { + set_to_inline_storage(); + } + + try { + uninitialized_fill(begin_ptr(), unchecked_next(begin_ptr(), count), val); + } catch (...) { + if (has_allocation()) { + deallocate(data_ptr(), get_capacity()); + } + throw; + } + set_size(count); + } + + template + constexpr small_vector_base(size_ty count, Generator& g, + const alloc_ty& alloc) + : alloc_interface(alloc) { + if (InlineCapacity < count) { + set_data_ptr(checked_allocate(count)); + set_capacity(count); + } else { + set_to_inline_storage(); + } + + ptr curr = begin_ptr(); + const ptr new_end = unchecked_next(begin_ptr(), count); + try { + for (; !(curr == new_end); ++curr) { + construct(curr, g()); + } + } catch (...) { + destroy_range(begin_ptr(), curr); + if (has_allocation()) { + deallocate(data_ptr(), get_capacity()); + } + throw; + } + set_size(count); + } + + template + constexpr small_vector_base(InputIt first, InputIt last, + std::input_iterator_tag, const alloc_ty& alloc) + : small_vector_base(alloc) { + using iterator_cat = + typename std::iterator_traits::iterator_category; + append_range(first, last, iterator_cat{}); + } + + template + constexpr small_vector_base(ForwardIt first, ForwardIt last, + std::forward_iterator_tag, const alloc_ty& alloc) + : alloc_interface(alloc) { + size_ty count = external_range_length(first, last); + if (InlineCapacity < count) { + set_data_ptr(unchecked_allocate(count)); + set_capacity(count); + try { + uninitialized_copy(first, last, begin_ptr()); + } catch (...) { + deallocate(data_ptr(), get_capacity()); + throw; + } + } else { + set_to_inline_storage(); + uninitialized_copy(first, last, begin_ptr()); + } + + set_size(count); + } + + constexpr ~small_vector_base() noexcept { + assert(InlineCapacity <= get_capacity() && "Invalid capacity."); + wipe(); + } + + protected: + constexpr void set_to_inline_storage() { + set_capacity(InlineCapacity); + if (std::is_constant_evaluated()) { + return set_data_ptr(alloc_interface::allocate(InlineCapacity)); + } + set_data_ptr(storage_ptr()); + } + + constexpr void assign_with_copies(size_ty count, const value_ty& val) { + if (get_capacity() < count) { + size_ty new_capacity = checked_calculate_new_capacity(count); + ptr new_begin = unchecked_allocate(new_capacity); + + try { + uninitialized_fill(new_begin, unchecked_next(new_begin, count), val); + } catch (...) { + deallocate(new_begin, new_capacity); + throw; + } + + reset_data(new_begin, new_capacity, count); + } else if (get_size() < count) { + std::fill(begin_ptr(), end_ptr(), val); + uninitialized_fill(end_ptr(), unchecked_next(begin_ptr(), count), val); + set_size(count); + } else { + erase_range(std::fill_n(begin_ptr(), count, val), end_ptr()); + } + } + + template + requires std::is_assignable_v())> + constexpr void assign_with_range(InputIt first, InputIt last, + std::input_iterator_tag) { + using iterator_cat = + typename std::iterator_traits::iterator_category; + + ptr curr = begin_ptr(); + for (; !(end_ptr() == curr || first == last); + ++curr, static_cast(++first)) { + *curr = *first; + } + + if (first == last) { + erase_to_end(curr); + } else { + append_range(first, last, iterator_cat{}); + } + } + + template + requires std::is_assignable_v())> + constexpr void assign_with_range(ForwardIt first, ForwardIt last, + std::forward_iterator_tag) { + const size_ty count = external_range_length(first, last); + if (get_capacity() < count) { + size_ty new_capacity = checked_calculate_new_capacity(count); + ptr new_begin = unchecked_allocate(new_capacity); + + try { + uninitialized_copy(first, last, new_begin); + } catch (...) { + deallocate(new_begin, new_capacity); + throw; + } + + reset_data(new_begin, new_capacity, count); + } else if (get_size() < count) { + ForwardIt pivot = copy_n_return_in(first, get_size(), begin_ptr()); + uninitialized_copy(pivot, last, end_ptr()); + set_size(count); + } else { + erase_range(copy_range(first, last, begin_ptr()), end_ptr()); + } + } + + template + requires( + !std::is_assignable_v())>) + constexpr void assign_with_range(InputIt first, InputIt last, + std::input_iterator_tag) { + using iterator_cat = + typename std::iterator_traits::iterator_category; + + // If not assignable then destroy all elements and append. + erase_all(); + append_range(first, last, iterator_cat{}); + } + + // Ie. move-if-noexcept. + struct strong_exception_policy {}; + + template + requires is_explicitly_move_insertable_v && + (!std::same_as || + relocate_with_move_v) + constexpr ptr uninitialized_move(ptr first, ptr last, ptr d_first) noexcept( + std::is_nothrow_move_constructible_v) { + return uninitialized_copy(std::make_move_iterator(first), + std::make_move_iterator(last), d_first); + } + + template + requires(!is_explicitly_move_insertable_v || + (std::same_as && + !relocate_with_move_v)) + constexpr ptr uninitialized_move(ptr first, ptr last, ptr d_first) noexcept( + alloc_interface::template is_uninitialized_memcpyable_iterator_v) { + return uninitialized_copy(first, last, d_first); + } + + constexpr ptr shift_into_uninitialized(ptr pos, size_ty n_shift) { + // Shift elements over to the right into uninitialized space. + // Returns the start of the shifted range. + // Precondition: shift < end_ptr () - pos + assert(n_shift != 0 && "The value of `n_shift` should not be 0."); + + const ptr original_end = end_ptr(); + const ptr pivot = unchecked_prev(original_end, n_shift); + + uninitialized_move(pivot, original_end, original_end); + increase_size(n_shift); + return move_right(pos, pivot, original_end); + } + + template + constexpr ptr append_element(Args&&... args) { + if (get_size() < get_capacity()) { + return emplace_into_current_end(std::forward(args)...); + } + return emplace_into_reallocation_end(std::forward(args)...); + } + + constexpr ptr append_copies(size_ty count, const value_ty& val) { + if (num_uninitialized() < count) { + // Reallocate. + if (get_max_size() - get_size() < count) { + throw_allocation_size_error(); + } + + size_ty original_size = get_size(); + size_ty new_size = get_size() + count; + + // The check is handled by the if-guard. + size_ty new_capacity = unchecked_calculate_new_capacity(new_size); + ptr new_data_ptr = unchecked_allocate(new_capacity, allocation_end_ptr()); + ptr new_last = unchecked_next(new_data_ptr, original_size); + + try { + new_last = + uninitialized_fill(new_last, unchecked_next(new_last, count), val); + uninitialized_move(begin_ptr(), end_ptr(), new_data_ptr); + } catch (...) { + destroy_range(unchecked_next(new_data_ptr, original_size), new_last); + deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, new_size); + return unchecked_next(new_data_ptr, original_size); + } else { + const ptr ret = end_ptr(); + uninitialized_fill(ret, unchecked_next(ret, count), val); + increase_size(count); + return ret; + } + } + + template MovePolicy, typename InputIt> + constexpr ptr append_range(InputIt first, InputIt last, + std::input_iterator_tag) { + // Append with a strong exception guarantee. + size_ty original_size = get_size(); + for (; !(first == last); ++first) { + try { + append_element(*first); + } catch (...) { + erase_range(unchecked_next(begin_ptr(), original_size), end_ptr()); + throw; + } + } + return unchecked_next(begin_ptr(), original_size); + } + + template + requires(!std::same_as) + constexpr ptr append_range(InputIt first, InputIt last, + std::input_iterator_tag) { + size_ty original_size = get_size(); + for (; !(first == last); ++first) { + append_element(*first); + } + return unchecked_next(begin_ptr(), original_size); + } + + template + constexpr ptr append_range(ForwardIt first, ForwardIt last, + std::forward_iterator_tag) { + const size_ty num_insert = external_range_length(first, last); + + if (num_uninitialized() < num_insert) { + // Reallocate. + if (get_max_size() - get_size() < num_insert) { + throw_allocation_size_error(); + } + + size_ty original_size = get_size(); + size_ty new_size = get_size() + num_insert; + + // The check is handled by the if-guard. + size_ty new_capacity = unchecked_calculate_new_capacity(new_size); + ptr new_data_ptr = unchecked_allocate(new_capacity, allocation_end_ptr()); + ptr new_last = unchecked_next(new_data_ptr, original_size); + + try { + new_last = uninitialized_copy(first, last, new_last); + uninitialized_move(begin_ptr(), end_ptr(), new_data_ptr); + } catch (...) { + destroy_range(unchecked_next(new_data_ptr, original_size), new_last); + deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, new_size); + return unchecked_next(new_data_ptr, original_size); + } else { + ptr ret = end_ptr(); + uninitialized_copy(first, last, ret); + increase_size(num_insert); + return ret; + } + } + + template + constexpr ptr emplace_at(ptr pos, Args&&... args) { + assert(get_size() <= get_capacity() && "size was greater than capacity"); + + if (get_size() < get_capacity()) { + return emplace_into_current(pos, std::forward(args)...); + } + return emplace_into_reallocation(pos, std::forward(args)...); + } + + constexpr ptr insert_copies(ptr pos, size_ty count, const value_ty& val) { + if (0 == count) { + return pos; + } + + if (end_ptr() == pos) { + if (1 == count) { + return append_element(val); + } + return append_copies(count, val); + } + + if (num_uninitialized() < count) { + // Reallocate. + if (get_max_size() - get_size() < count) { + throw_allocation_size_error(); + } + + const size_ty offset = internal_range_length(begin_ptr(), pos); + + const size_ty new_size = get_size() + count; + + // The check is handled by the if-guard. + const size_ty new_capacity = unchecked_calculate_new_capacity(new_size); + ptr new_data_ptr = unchecked_allocate(new_capacity, allocation_end_ptr()); + ptr new_first = unchecked_next(new_data_ptr, offset); + ptr new_last = new_first; + + try { + uninitialized_fill(new_first, unchecked_next(new_first, count), val); + unchecked_advance(new_last, count); + + uninitialized_move(begin_ptr(), pos, new_data_ptr); + new_first = new_data_ptr; + uninitialized_move(pos, end_ptr(), new_last); + } catch (...) { + destroy_range(new_first, new_last); + deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, new_size); + return unchecked_next(begin_ptr(), offset); + } else { + // If we have fewer to insert than tailing elements after `pos`, we shift + // into uninitialized and then copy over. + + const size_ty tail_size = internal_range_length(pos, end_ptr()); + if (tail_size < count) { + // The number inserted is larger than the number after `pos`, + // so part of the input will be used to construct new elements, + // and another part of it will assign existing ones. + // In order: + // Construct new elements immediately after end_ptr () using the + // input. Move-construct existing elements over to the tail. Assign + // existing elements using the input. + + ptr original_end = end_ptr(); + + // Place a portion of the input into the uninitialized section. + size_ty num_val_tail = count - tail_size; + + if (std::is_constant_evaluated()) { + uninitialized_fill(end_ptr(), unchecked_next(end_ptr(), num_val_tail), + val); + increase_size(num_val_tail); + + const heap_temporary tmp(*this, val); + + uninitialized_move(pos, original_end, end_ptr()); + increase_size(tail_size); + + std::fill_n(pos, tail_size, tmp.get()); + + return pos; + } + + uninitialized_fill(end_ptr(), unchecked_next(end_ptr(), num_val_tail), + val); + increase_size(num_val_tail); + + try { + // We need to handle possible aliasing here. + const stack_temporary tmp(*this, val); + + // Now, move the tail to the end. + uninitialized_move(pos, original_end, end_ptr()); + increase_size(tail_size); + + try { + // Finally, try to copy the rest of the elements over. + std::fill_n(pos, tail_size, tmp.get()); + } catch (...) { + // Attempt to roll back and destroy the tail if we fail. + ptr inserted_end = unchecked_prev(end_ptr(), tail_size); + move_left(inserted_end, end_ptr(), pos); + destroy_range(inserted_end, end_ptr()); + decrease_size(tail_size); + throw; + } + } catch (...) { + // Destroy the elements constructed from the input. + destroy_range(original_end, end_ptr()); + decrease_size(internal_range_length(original_end, end_ptr())); + throw; + } + } else { + if (std::is_constant_evaluated()) { + const heap_temporary tmp(*this, val); + + ptr inserted_end = shift_into_uninitialized(pos, count); + std::fill(pos, inserted_end, tmp.get()); + + return pos; + } + const stack_temporary tmp(*this, val); + + ptr inserted_end = shift_into_uninitialized(pos, count); + + // Attempt to copy over the elements. + // If we fail we'll attempt a full roll-back. + try { + std::fill(pos, inserted_end, tmp.get()); + } catch (...) { + ptr original_end = move_left(inserted_end, end_ptr(), pos); + destroy_range(original_end, end_ptr()); + decrease_size(count); + throw; + } + } + return pos; + } + } + + template + constexpr ptr insert_range_helper(ptr pos, ForwardIt first, ForwardIt last) { + assert(!(first == last) && "The range should not be empty."); + assert(!(end_ptr() == pos) && "`pos` should not be at the end."); + + const size_ty num_insert = external_range_length(first, last); + if (num_uninitialized() < num_insert) { + // Reallocate. + if (get_max_size() - get_size() < num_insert) { + throw_allocation_size_error(); + } + + const size_ty offset = internal_range_length(begin_ptr(), pos); + const size_ty new_size = get_size() + num_insert; + + // The check is handled by the if-guard. + const size_ty new_capacity = unchecked_calculate_new_capacity(new_size); + const ptr new_data_ptr = + unchecked_allocate(new_capacity, allocation_end_ptr()); + ptr new_first = unchecked_next(new_data_ptr, offset); + ptr new_last = new_first; + + try { + uninitialized_copy(first, last, new_first); + unchecked_advance(new_last, num_insert); + + uninitialized_move(begin_ptr(), pos, new_data_ptr); + new_first = new_data_ptr; + uninitialized_move(pos, end_ptr(), new_last); + } catch (...) { + destroy_range(new_first, new_last); + deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, new_size); + return unchecked_next(begin_ptr(), offset); + } else { + // if we have fewer to insert than tailing elements after + // `pos` we shift into uninitialized and then copy over + const size_ty tail_size = internal_range_length(pos, end_ptr()); + if (tail_size < num_insert) { + // Use the same method as insert_copies. + ptr original_end = end_ptr(); + ForwardIt pivot = unchecked_next(first, tail_size); + + // Place a portion of the input into the uninitialized section. + uninitialized_copy(pivot, last, end_ptr()); + increase_size(num_insert - tail_size); + + try { + // Now move the tail to the end. + uninitialized_move(pos, original_end, end_ptr()); + increase_size(tail_size); + + try { + // Finally, try to copy the rest of the elements over. + copy_range(first, pivot, pos); + } catch (...) { + // Attempt to roll back and destroy the tail if we fail. + ptr inserted_end = unchecked_prev(end_ptr(), tail_size); + move_left(inserted_end, end_ptr(), pos); + destroy_range(inserted_end, end_ptr()); + decrease_size(tail_size); + throw; + } + } catch (...) { + // If we throw, destroy the first copy we made. + destroy_range(original_end, end_ptr()); + decrease_size(internal_range_length(original_end, end_ptr())); + throw; + } + } else { + shift_into_uninitialized(pos, num_insert); + + // Attempt to copy over the elements. + // If we fail we'll attempt a full roll-back. + try { + copy_range(first, last, pos); + } catch (...) { + ptr inserted_end = unchecked_next(pos, num_insert); + ptr original_end = move_left(inserted_end, end_ptr(), pos); + destroy_range(original_end, end_ptr()); + decrease_size(num_insert); + throw; + } + } + return pos; + } + } + + template + constexpr ptr insert_range(ptr pos, InputIt first, InputIt last, + std::input_iterator_tag) { + assert(!(first == last) && "The range should not be empty."); + + // Ensure we use this specific overload to give a strong exception guarantee + // for 1 element. + if (end_ptr() == pos) { + return append_range(first, last, std::input_iterator_tag{}); + } + + using iterator_cat = + typename std::iterator_traits::iterator_category; + small_vector_base tmp(first, last, iterator_cat{}, allocator_ref()); + + return insert_range_helper(pos, std::make_move_iterator(tmp.begin_ptr()), + std::make_move_iterator(tmp.end_ptr())); + } + + template + constexpr ptr insert_range(ptr pos, ForwardIt first, ForwardIt last, + std::forward_iterator_tag) { + if (!(end_ptr() == pos)) { + return insert_range_helper(pos, first, last); + } + + if (unchecked_next(first) == last) { + return append_element(*first); + } + + using iterator_cat = + typename std::iterator_traits::iterator_category; + return append_range(first, last, iterator_cat{}); + } + + template + constexpr ptr emplace_into_current_end(Args&&... args) { + construct(end_ptr(), std::forward(args)...); + increase_size(1); + return unchecked_prev(end_ptr()); + } + + template + requires std::is_nothrow_move_constructible_v + constexpr ptr emplace_into_current(ptr pos, value_ty&& val) { + if (pos == end_ptr()) { + return emplace_into_current_end(std::move(val)); + } + + // In the special case of value_ty&& we don't make a copy because behavior + // is unspecified when it is an internal element. Hence, we'll take the + // opportunity to optimize and assume that it isn't an internal element. + shift_into_uninitialized(pos, 1); + destroy(pos); + construct(pos, std::move(val)); + return pos; + } + + template + constexpr ptr emplace_into_current(ptr pos, Args&&... args) { + if (pos == end_ptr()) { + return emplace_into_current_end(std::forward(args)...); + } + + if (std::is_constant_evaluated()) { + heap_temporary tmp(*this, std::forward(args)...); + shift_into_uninitialized(pos, 1); + *pos = tmp.release(); + return pos; + } + + // This is necessary because of possible aliasing. + stack_temporary tmp(*this, std::forward(args)...); + shift_into_uninitialized(pos, 1); + *pos = tmp.release(); + return pos; + } + + template + constexpr ptr emplace_into_reallocation_end(Args&&... args) { + // Appending; strong exception guarantee. + if (get_max_size() == get_size()) { + throw_allocation_size_error(); + } + + const size_ty new_size = get_size() + 1; + + // The check is handled by the if-guard. + const size_ty new_capacity = unchecked_calculate_new_capacity(new_size); + const ptr new_data_ptr = + unchecked_allocate(new_capacity, allocation_end_ptr()); + const ptr emplace_pos = unchecked_next(new_data_ptr, get_size()); + + try { + construct(emplace_pos, std::forward(args)...); + try { + uninitialized_move(begin_ptr(), end_ptr(), + new_data_ptr); + } catch (...) { + destroy(emplace_pos); + throw; + } + } catch (...) { + deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, new_size); + return emplace_pos; + } + + template + constexpr ptr emplace_into_reallocation(ptr pos, Args&&... args) { + const size_ty offset = internal_range_length(begin_ptr(), pos); + if (offset == get_size()) { + return emplace_into_reallocation_end(std::forward(args)...); + } + + if (get_max_size() == get_size()) { + throw_allocation_size_error(); + } + + const size_ty new_size = get_size() + 1; + + // The check is handled by the if-guard. + const size_ty new_capacity = unchecked_calculate_new_capacity(new_size); + const ptr new_data_ptr = + unchecked_allocate(new_capacity, allocation_end_ptr()); + ptr new_first = unchecked_next(new_data_ptr, offset); + ptr new_last = new_first; + + try { + construct(new_first, std::forward(args)...); + unchecked_advance(new_last, 1); + + uninitialized_move(begin_ptr(), pos, new_data_ptr); + new_first = new_data_ptr; + uninitialized_move(pos, end_ptr(), new_last); + } catch (...) { + destroy_range(new_first, new_last); + deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, new_size); + return unchecked_next(begin_ptr(), offset); + } + + constexpr ptr shrink_to_size() { + if (!has_allocation() || get_size() == get_capacity()) { + return begin_ptr(); + } + + // The rest runs only if allocated. + + size_ty new_capacity; + ptr new_data_ptr; + + if (InlineCapacity < get_size()) { + new_capacity = get_size(); + new_data_ptr = unchecked_allocate(new_capacity, allocation_end_ptr()); + } else { + // We move to inline storage. + new_capacity = InlineCapacity; + if (std::is_constant_evaluated()) { + new_data_ptr = alloc_interface::allocate(InlineCapacity); + } else { + new_data_ptr = storage_ptr(); + } + } + + uninitialized_move(begin_ptr(), end_ptr(), new_data_ptr); + + destroy_range(begin_ptr(), end_ptr()); + deallocate(data_ptr(), get_capacity()); + + set_data_ptr(new_data_ptr); + set_capacity(new_capacity); + + return begin_ptr(); + } + + template + constexpr void resize_with(size_ty new_size, const ValueT&... val) { + // ValueT... should either be value_ty or empty. + + if (new_size == 0) { + erase_all(); + } + + if (get_capacity() < new_size) { + // Reallocate. + + if (get_max_size() < new_size) { + throw_allocation_size_error(); + } + + const size_ty original_size = get_size(); + + // The check is handled by the if-guard. + const size_ty new_capacity = unchecked_calculate_new_capacity(new_size); + ptr new_data_ptr = unchecked_allocate(new_capacity, allocation_end_ptr()); + ptr new_last = unchecked_next(new_data_ptr, original_size); + + try { + new_last = uninitialized_fill( + new_last, unchecked_next(new_data_ptr, new_size), val...); + + // Strong exception guarantee. + uninitialized_move(begin_ptr(), end_ptr(), + new_data_ptr); + } catch (...) { + destroy_range(unchecked_next(new_data_ptr, original_size), new_last); + deallocate(new_data_ptr, new_capacity); + throw; + } + + reset_data(new_data_ptr, new_capacity, new_size); + } else if (get_size() < new_size) { + // Construct in the uninitialized section. + uninitialized_fill(end_ptr(), unchecked_next(begin_ptr(), new_size), + val...); + set_size(new_size); + } else { + erase_range(unchecked_next(begin_ptr(), new_size), end_ptr()); + } + + // Do nothing if the count is the same as the current size. + } + + constexpr void request_capacity(size_ty request) { + if (request <= get_capacity()) { + return; + } + + size_ty new_capacity = checked_calculate_new_capacity(request); + ptr new_begin = unchecked_allocate(new_capacity); + + try { + uninitialized_move(begin_ptr(), end_ptr(), + new_begin); + } catch (...) { + deallocate(new_begin, new_capacity); + throw; + } + + wipe(); + + set_data_ptr(new_begin); + set_capacity(new_capacity); + } + + constexpr ptr erase_at(ptr pos) { + move_left(unchecked_next(pos), end_ptr(), pos); + erase_last(); + return pos; + } + + constexpr void erase_last() { + decrease_size(1); + + // The element located at end_ptr is still alive since the size decreased. + destroy(end_ptr()); + } + + constexpr ptr erase_range(ptr first, ptr last) { + if (!(first == last)) { + erase_to_end(move_left(last, end_ptr(), first)); + } + return first; + } + + constexpr void erase_to_end(ptr pos) { + assert(0 <= (end_ptr() - pos) && "`pos` was in the uninitialized range"); + if (size_ty change = internal_range_length(pos, end_ptr())) { + decrease_size(change); + destroy_range(pos, unchecked_next(pos, change)); + } + } + + constexpr void erase_all() { + ptr curr_end = end_ptr(); + set_size(0); + destroy_range(begin_ptr(), curr_end); + } + + constexpr void swap_elements(small_vector_base& other) noexcept( + std::is_nothrow_move_constructible_v && + std::is_nothrow_swappable_v) { + assert(get_size() <= other.get_size()); + + const ptr other_tail = + std::swap_ranges(begin_ptr(), end_ptr(), other.begin_ptr()); + uninitialized_move(other_tail, other.end_ptr(), end_ptr()); + destroy_range(other_tail, other.end_ptr()); + + swap_size(other); + } + + constexpr void swap_default(small_vector_base& other) noexcept( + std::is_nothrow_move_constructible_v && + std::is_nothrow_swappable_v) { + // This function is used when: + // We are using the standard allocator. + // The allocators propagate and are equal. + // The allocators are always equal. + // The allocators do not propagate and are equal. + // The allocators propagate and are not equal. + + // Not handled: + // The allocators do not propagate and are not equal. + + assert(get_capacity() <= other.get_capacity()); + + if (has_allocation()) { // Implies that `other` also has an allocation. + swap_allocation(other); + } else if (other.has_allocation()) { + // Note: This will never be constant evaluated because both are always + // allocated. + uninitialized_move(begin_ptr(), end_ptr(), other.storage_ptr()); + destroy_range(begin_ptr(), end_ptr()); + + set_data_ptr(other.data_ptr()); + set_capacity(other.get_capacity()); + + other.set_data_ptr(other.storage_ptr()); + other.set_capacity(InlineCapacity); + + swap_size(other); + } else if (get_size() < other.get_size()) { + swap_elements(other); + } else { + other.swap_elements(*this); + } + + alloc_interface::swap(other); + } + + constexpr void swap_unequal_no_propagate(small_vector_base& other) { + assert(get_capacity() <= other.get_capacity()); + + if (get_capacity() < other.get_size()) { + // Reallocation required. + // We should always be able to reuse the allocation of `other`. + const size_ty new_capacity = + unchecked_calculate_new_capacity(other.get_size()); + const ptr new_data_ptr = unchecked_allocate(new_capacity, end_ptr()); + + try { + uninitialized_move(other.begin_ptr(), other.end_ptr(), new_data_ptr); + try { + destroy_range(std::move(begin_ptr(), end_ptr(), other.begin_ptr()), + other.end_ptr()); + } catch (...) { + destroy_range(new_data_ptr, + unchecked_next(new_data_ptr, other.get_size())); + throw; + } + } catch (...) { + deallocate(new_data_ptr, new_capacity); + throw; + } + + destroy_range(begin_ptr(), end_ptr()); + if (has_allocation()) { + deallocate(data_ptr(), get_capacity()); + } + + set_data_ptr(new_data_ptr); + set_capacity(new_capacity); + swap_size(other); + } else if (get_size() < other.get_size()) { + swap_elements(other); + } else { + other.swap_elements(*this); + } + + // This should have no effect. + alloc_interface::swap(other); + } + + template + requires allocations_are_swappable_v && (InlineCapacity == 0) + constexpr void swap(small_vector_base& other) noexcept { + swap_allocation(other); + alloc_interface::swap(other); + } + + template + requires allocations_are_swappable_v && (InlineCapacity != 0) + constexpr void swap(small_vector_base& other) noexcept( + std::is_nothrow_move_constructible_v && + std::is_nothrow_swappable_v) { + if (get_capacity() < other.get_capacity()) { + swap_default(other); + } else { + other.swap_default(*this); + } + } + + template + requires(!allocations_are_swappable_v) + constexpr void swap(small_vector_base& other) { + if (get_capacity() < other.get_capacity()) { + if (other.allocator_ref() == allocator_ref()) { + swap_default(other); + } else { + swap_unequal_no_propagate(other); + } + } else { + if (other.allocator_ref() == allocator_ref()) { + other.swap_default(*this); + } else { + other.swap_unequal_no_propagate(*this); + } + } + } + +#ifdef __GLIBCXX__ + + // These are compatibility fixes for libstdc++ because std::copy doesn't work + // for `move_iterator`s when constant evaluated. + + template + static constexpr InputIt unmove_iterator(InputIt it) { + return it; + } + + template + static constexpr auto unmove_iterator(std::move_iterator it) + -> decltype(unmove_iterator(it.base())) { + return unmove_iterator(it.base()); + } + + template + static constexpr auto unmove_iterator(std::reverse_iterator it) + -> std::reverse_iterator { + return std::reverse_iterator( + unmove_iterator(it.base())); + } + +#endif + + template + constexpr ptr copy_range(InputIt first, InputIt last, ptr dest) { +#ifdef __GLIBCXX__ + if (std::is_constant_evaluated()) { + if constexpr (!std::is_same_v())), + InputIt>) { + return std::move(unmove_iterator(first), unmove_iterator(last), dest); + } + } +#endif + + return std::copy(first, last, dest); + } + + template + requires is_memcpyable_iterator_v + constexpr InputIt copy_n_return_in(InputIt first, size_ty count, + ptr dest) noexcept { + if (std::is_constant_evaluated()) { + std::copy_n(first, count, dest); + return unchecked_next(first, count); + } + + if (count != 0) { + std::memcpy(to_address(dest), to_address(first), + count * sizeof(value_ty)); + } + // Note: The unsafe cast here should be proven to be safe in the caller + // function. + return unchecked_next(first, count); + } + + template + requires is_memcpyable_iterator_v + constexpr std::move_iterator copy_n_return_in( + std::move_iterator first, size_ty count, ptr dest) noexcept { + return std::move_iterator( + copy_n_return_in(first.base(), count, dest)); + } + + template + requires(!is_memcpyable_iterator_v && + std::is_base_of_v< + std::random_access_iterator_tag, + typename std::iterator_traits::iterator_category>) + constexpr RandomIt copy_n_return_in(RandomIt first, size_ty count, ptr dest) { + if (std::is_constant_evaluated()) { + if constexpr (!std::is_same_v())), + RandomIt>) { + auto bfirst = unmove_iterator(first); + auto blast = unchecked_next(bfirst, count); + std::move(bfirst, blast, dest); + return unchecked_next(first, count); + } + } + + std::copy_n(first, count, dest); + // Note: This unsafe cast should be proven safe in the caller function. + return unchecked_next(first, count); + } + + template + requires(!is_memcpyable_iterator_v && + !std::is_base_of_v< + std::random_access_iterator_tag, + typename std::iterator_traits::iterator_category>) + constexpr InputIt copy_n_return_in(InputIt first, size_ty count, ptr dest) { + for (; count != 0; + --count, static_cast(++dest), static_cast(++first)) { + *dest = *first; + } + return first; + } + + template + requires is_memcpyable_v + constexpr ptr move_left(ptr first, ptr last, ptr d_first) { + // Shift initialized elements to the left. + + if (std::is_constant_evaluated()) { + return std::move(first, last, d_first); + } + + const size_ty num_moved = internal_range_length(first, last); + if (num_moved != 0) { + std::memmove(to_address(d_first), to_address(first), + num_moved * sizeof(value_ty)); + } + return unchecked_next(d_first, num_moved); + } + + template + requires(!is_memcpyable_v) + constexpr ptr move_left(ptr first, ptr last, ptr d_first) { + // Shift initialized elements to the left. + return std::move(first, last, d_first); + } + + template + requires is_memcpyable_v + constexpr ptr move_right(ptr first, ptr last, ptr d_last) { + // Move initialized elements to the right. + + if (std::is_constant_evaluated()) { + return std::move_backward(first, last, d_last); + } + + const size_ty num_moved = internal_range_length(first, last); + const ptr dest = unchecked_prev(d_last, num_moved); + if (num_moved != 0) { + std::memmove(to_address(dest), to_address(first), + num_moved * sizeof(value_ty)); + } + return dest; + } + + template + requires(!is_memcpyable_v) + constexpr ptr move_right(ptr first, ptr last, ptr d_last) { + // move initialized elements to the right + // n should not be 0 + return std::move_backward(first, last, d_last); + } + + public: + constexpr void set_default() { + set_to_inline_storage(); + set_size(0); + } + + [[nodiscard]] + constexpr ptr data_ptr() noexcept { + return m_data.data_ptr(); + } + + [[nodiscard]] + constexpr cptr data_ptr() const noexcept { + return m_data.data_ptr(); + } + + [[nodiscard]] + constexpr size_ty get_capacity() const noexcept { + return m_data.capacity(); + } + + [[nodiscard]] + constexpr size_ty get_size() const noexcept { + return m_data.size(); + } + + [[nodiscard]] + constexpr size_ty num_uninitialized() const noexcept { + return get_capacity() - get_size(); + } + + [[nodiscard]] + constexpr ptr begin_ptr() noexcept { + return data_ptr(); + } + + [[nodiscard]] + constexpr cptr begin_ptr() const noexcept { + return data_ptr(); + } + + [[nodiscard]] + constexpr ptr end_ptr() noexcept { + return unchecked_next(begin_ptr(), get_size()); + } + + [[nodiscard]] + constexpr cptr end_ptr() const noexcept { + return unchecked_next(begin_ptr(), get_size()); + } + + [[nodiscard]] + constexpr ptr allocation_end_ptr() noexcept { + return unchecked_next(begin_ptr(), get_capacity()); + } + + [[nodiscard]] + constexpr cptr allocation_end_ptr() const noexcept { + return unchecked_next(begin_ptr(), get_capacity()); + } + + [[nodiscard]] + constexpr alloc_ty copy_allocator() const noexcept { + return alloc_ty(allocator_ref()); + } + + [[nodiscard]] + constexpr ptr storage_ptr() noexcept { + return m_data.storage(); + } + + [[nodiscard]] + constexpr cptr storage_ptr() const noexcept { + return m_data.storage(); + } + + [[nodiscard]] + constexpr bool has_allocation() const noexcept { + if (std::is_constant_evaluated()) { + return true; + } + return InlineCapacity < get_capacity(); + } + + [[nodiscard]] + constexpr bool is_inlinable() const noexcept { + return get_size() <= InlineCapacity; + } + + private: + small_vector_data m_data; +}; + +} // namespace detail + +template + requires concepts::small_vector::AllocatorFor +class small_vector + : private detail::small_vector_base { + using base = detail::small_vector_base; + + public: + static_assert(std::is_same_v, + "`Allocator::value_type` must be the same as `T`."); + + template + requires concepts::small_vector::AllocatorFor + friend class small_vector; + + using value_type = T; + using allocator_type = Allocator; + using size_type = typename base::size_type; + using difference_type = typename base::difference_type; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = typename std::allocator_traits::pointer; + using const_pointer = + typename std::allocator_traits::const_pointer; + + using iterator = small_vector_iterator; + using const_iterator = small_vector_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + static_assert(InlineCapacity <= (std::numeric_limits::max)(), + "InlineCapacity must be less than or equal to the maximum " + "value of size_type."); + + static constexpr unsigned inline_capacity_v = InlineCapacity; + + private: + static constexpr bool Destructible = + concepts::small_vector::Destructible; + + static constexpr bool MoveAssignable = + concepts::small_vector::MoveAssignable; + + static constexpr bool CopyAssignable = + concepts::small_vector::CopyAssignable; + + static constexpr bool MoveConstructible = + concepts::small_vector::MoveConstructible; + + static constexpr bool CopyConstructible = + concepts::small_vector::CopyConstructible; + + static constexpr bool Swappable = + concepts::small_vector::Swappable; + + static constexpr bool DefaultInsertable = + concepts::small_vector::DefaultInsertable; + + static constexpr bool MoveInsertable = + concepts::small_vector::MoveInsertable; + + static constexpr bool CopyInsertable = + concepts::small_vector::CopyInsertable; + + static constexpr bool Erasable = + concepts::small_vector::Erasable; + + template + struct EmplaceConstructible { + static constexpr bool value = + concepts::small_vector::EmplaceConstructible; + }; + + public: + constexpr small_vector() noexcept(noexcept(allocator_type())) + requires concepts::DefaultConstructible + = default; + + constexpr small_vector(const small_vector& other) + requires CopyInsertable + : base(base::bypass, other) {} + + constexpr small_vector(small_vector&& other) noexcept( + std::is_nothrow_move_constructible_v || InlineCapacity == 0) + requires MoveInsertable + : base(base::bypass, std::move(other)) {} + + constexpr explicit small_vector(const allocator_type& alloc) noexcept + : base(alloc) {} + + constexpr small_vector(const small_vector& other, const allocator_type& alloc) + requires CopyInsertable + : base(base::bypass, other, alloc) {} + + constexpr small_vector(small_vector&& other, const allocator_type& alloc) + requires MoveInsertable + : base(base::bypass, std::move(other), alloc) {} + + constexpr explicit small_vector( + size_type count, const allocator_type& alloc = allocator_type()) + requires DefaultInsertable + : base(count, alloc) {} + + constexpr small_vector(size_type count, const_reference value, + const allocator_type& alloc = allocator_type()) + requires CopyInsertable + : base(count, value, alloc) {} + + template + requires std::invocable && + EmplaceConstructible>::value + constexpr small_vector(size_type count, Generator g, + const allocator_type& alloc = allocator_type()) + : base(count, g, alloc) {} + + template + requires EmplaceConstructible>::value && + (std::forward_iterator || MoveInsertable) + constexpr small_vector(InputIt first, InputIt last, + const allocator_type& alloc = allocator_type()) + : base(first, last, + typename std::iterator_traits::iterator_category{}, + alloc) {} + + constexpr small_vector(std::initializer_list init, + const allocator_type& alloc = allocator_type()) + requires EmplaceConstructible::value + : small_vector(init.begin(), init.end(), alloc) {} + + template + requires CopyInsertable + constexpr explicit small_vector(const small_vector& other) + : base(base::bypass, other) {} + + template + requires MoveInsertable + constexpr explicit small_vector( + small_vector&& + other) noexcept(std::is_nothrow_move_constructible:: + value && + I < InlineCapacity) + : base(base::bypass, std::move(other)) {} + + template + requires CopyInsertable + constexpr small_vector(const small_vector& other, + const allocator_type& alloc) + : base(base::bypass, other, alloc) {} + + template + requires MoveInsertable + constexpr small_vector(small_vector&& other, + const allocator_type& alloc) + : base(base::bypass, std::move(other), alloc) {} + + constexpr ~small_vector() + requires Erasable + = default; + + constexpr small_vector& operator=(const small_vector& other) + requires CopyInsertable && CopyAssignable + { + assign(other); + return *this; + } + + constexpr small_vector& operator=(small_vector&& other) noexcept( + (std::is_same_v, Allocator> || + std::allocator_traits< + Allocator>::propagate_on_container_move_assignment::value || + std::allocator_traits::is_always_equal::value) && + ((std::is_nothrow_move_assignable_v && + std::is_nothrow_move_constructible_v) || + InlineCapacity == 0)) + // Note: The standard says here that + // std::allocator_traits::propagate_on_container_move_assignment + // == false implies MoveInsertable && MoveAssignable, but since we have + // inline storage we must always require moves [tab:container.alloc.req]. + requires MoveInsertable && MoveAssignable + { + assign(std::move(other)); + return *this; + } + + constexpr small_vector& operator=(std::initializer_list ilist) + requires CopyInsertable && CopyAssignable + { + assign(ilist); + return *this; + } + + constexpr void assign(size_type count, const_reference value) + requires CopyInsertable && CopyAssignable + { + base::assign_with_copies(count, value); + } + + template + requires EmplaceConstructible>::value && + (std::forward_iterator || MoveInsertable) + constexpr void assign(InputIt first, InputIt last) { + using iterator_cat = + typename std::iterator_traits::iterator_category; + base::assign_with_range(first, last, iterator_cat{}); + } + + constexpr void assign(std::initializer_list ilist) + requires EmplaceConstructible::value + { + assign(ilist.begin(), ilist.end()); + } + + constexpr void assign(const small_vector& other) + requires CopyInsertable && CopyAssignable + { + if (&other != this) { + base::copy_assign(other); + } + } + + template + requires CopyInsertable && CopyAssignable + constexpr void assign(const small_vector& other) { + base::copy_assign(other); + } + + constexpr void assign(small_vector&& other) noexcept( + (std::is_same_v, Allocator> || + std::allocator_traits< + Allocator>::propagate_on_container_move_assignment::value || + std::allocator_traits::is_always_equal::value) && + ((std::is_nothrow_move_assignable_v && + std::is_nothrow_move_constructible_v) || + InlineCapacity == 0)) + requires MoveInsertable && MoveAssignable + { + if (&other != this) { + base::move_assign(std::move(other)); + } + } + + template + requires MoveInsertable && MoveAssignable + constexpr void assign(small_vector&& other) noexcept( + I <= InlineCapacity && + (std::is_same_v, Allocator> || + std::allocator_traits< + Allocator>::propagate_on_container_move_assignment::value || + std::allocator_traits::is_always_equal::value) && + std::is_nothrow_move_assignable_v && + std::is_nothrow_move_constructible_v) { + base::move_assign(std::move(other)); + } + + constexpr void swap(small_vector& other) noexcept( + (std::is_same_v, Allocator> || + std::allocator_traits::propagate_on_container_swap::value || + std::allocator_traits::is_always_equal::value) && + ((std::is_nothrow_move_constructible_v && + std::is_nothrow_move_assignable_v && + std::is_nothrow_swappable_v) || + InlineCapacity == 0)) + requires(MoveInsertable && MoveAssignable && Swappable) || + ((std::is_same_v, Allocator> || + std::allocator_traits< + Allocator>::propagate_on_container_swap::value || + std::allocator_traits::is_always_equal::value) && + InlineCapacity == 0) + { + base::swap(other); + } + + constexpr iterator begin() noexcept { return iterator{base::begin_ptr()}; } + + constexpr const_iterator begin() const noexcept { + return const_iterator{base::begin_ptr()}; + } + + constexpr const_iterator cbegin() const noexcept { return begin(); } + + constexpr iterator end() noexcept { return iterator{base::end_ptr()}; } + + constexpr const_iterator end() const noexcept { + return const_iterator{base::end_ptr()}; + } + + constexpr const_iterator cend() const noexcept { return end(); } + + constexpr reverse_iterator rbegin() noexcept { + return reverse_iterator{end()}; + } + + constexpr const_reverse_iterator rbegin() const noexcept { + return const_reverse_iterator{end()}; + } + + constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + + constexpr reverse_iterator rend() noexcept { + return reverse_iterator{begin()}; + } + + constexpr const_reverse_iterator rend() const noexcept { + return const_reverse_iterator{begin()}; + } + + constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + constexpr reference at(size_type pos) { + if (size() <= pos) { + base::throw_index_error(); + } + return begin()[static_cast(pos)]; + } + + constexpr const_reference at(size_type pos) const { + if (size() <= pos) { + base::throw_index_error(); + } + return begin()[static_cast(pos)]; + } + + constexpr reference operator[](size_type pos) { + return begin()[static_cast(pos)]; + } + + constexpr const_reference operator[](size_type pos) const { + return begin()[static_cast(pos)]; + } + + constexpr reference front() { return (*this)[0]; } + + constexpr const_reference front() const { return (*this)[0]; } + + constexpr reference back() { return (*this)[size() - 1]; } + + constexpr const_reference back() const { return (*this)[size() - 1]; } + + constexpr pointer data() noexcept { return base::begin_ptr(); } + + constexpr const_pointer data() const noexcept { return base::begin_ptr(); } + + constexpr size_type size() const noexcept { + return static_cast(base::get_size()); + } + + [[nodiscard]] + constexpr bool empty() const noexcept { + return size() == 0; + } + + constexpr size_type max_size() const noexcept { + return static_cast(base::get_max_size()); + } + + constexpr size_type capacity() const noexcept { + return static_cast(base::get_capacity()); + } + + constexpr allocator_type get_allocator() const noexcept { + return base::copy_allocator(); + } + + constexpr iterator insert(const_iterator pos, const_reference value) + requires CopyInsertable && CopyAssignable + { + return emplace(pos, value); + } + + constexpr iterator insert(const_iterator pos, value_type&& value) + requires MoveInsertable && MoveAssignable + { + return emplace(pos, std::move(value)); + } + + constexpr iterator insert(const_iterator pos, size_type count, + const_reference value) + requires CopyInsertable && CopyAssignable + { + return iterator(base::insert_copies(base::ptr_cast(pos), count, value)); + } + + // Note: Unlike std::vector, this does not require MoveConstructible because + // we + // don't use std::rotate (as was the reason for the change in C++17). + // Relevant: https://cplusplus.github.io/LWG/issue2266). + template + requires EmplaceConstructible>::value && + MoveInsertable && MoveAssignable + constexpr iterator insert(const_iterator pos, InputIt first, InputIt last) { + if (first == last) { + return iterator(base::ptr_cast(pos)); + } + + using iterator_cat = + typename std::iterator_traits::iterator_category; + return iterator( + base::insert_range(base::ptr_cast(pos), first, last, iterator_cat{})); + } + + constexpr iterator insert(const_iterator pos, + std::initializer_list ilist) + requires EmplaceConstructible::value && MoveInsertable + && MoveAssignable + { + return insert(pos, ilist.begin(), ilist.end()); + } + + template + requires EmplaceConstructible::value && MoveInsertable && + MoveAssignable + constexpr iterator emplace(const_iterator pos, Args&&... args) { + return iterator( + base::emplace_at(base::ptr_cast(pos), std::forward(args)...)); + } + + constexpr iterator erase(const_iterator pos) + requires MoveAssignable && Erasable + { + assert(0 <= (pos - begin()) && + "`pos` is out of bounds (before `begin ()`)."); + assert(0 < (end() - pos) && + "`pos` is out of bounds (at or after `end ()`)."); + + return iterator(base::erase_at(base::ptr_cast(pos))); + } + + constexpr iterator erase(const_iterator first, const_iterator last) + requires MoveAssignable && Erasable + { + assert(0 <= (last - first) && "Invalid range."); + assert(0 <= (first - begin()) && + "`first` is out of bounds (before `begin ()`)."); + assert(0 <= (end() - last) && "`last` is out of bounds (after `end ()`)."); + + return iterator( + base::erase_range(base::ptr_cast(first), base::ptr_cast(last))); + } + + constexpr void push_back(const_reference value) + requires CopyInsertable + { + emplace_back(value); + } + + constexpr void push_back(value_type&& value) + requires MoveInsertable + { + emplace_back(std::move(value)); + } + + template + requires EmplaceConstructible::value && MoveInsertable + constexpr reference emplace_back(Args&&... args) { + return *base::append_element(std::forward(args)...); + } + + constexpr void pop_back() + requires Erasable + { + assert(!empty() && "`pop_back ()` called on an empty `small_vector`."); + base::erase_last(); + } + + constexpr void reserve(size_type new_capacity) + requires MoveInsertable + { + base::request_capacity(new_capacity); + } + + constexpr void shrink_to_fit() + requires MoveInsertable + { + base::shrink_to_size(); + } + + constexpr void clear() noexcept + requires Erasable + { + base::erase_all(); + } + + constexpr void resize(size_type count) + requires MoveInsertable && DefaultInsertable + { + base::resize_with(count); + } + + constexpr void resize(size_type count, const_reference value) + requires CopyInsertable + { + base::resize_with(count, value); + } + + [[nodiscard]] + constexpr bool inlined() const noexcept { + return !base::has_allocation(); + } + + [[nodiscard]] + constexpr bool inlinable() const noexcept { + return base::is_inlinable(); + } + + [[nodiscard]] + static consteval size_type inline_capacity() noexcept { + return static_cast(inline_capacity_v); + } + + template + requires EmplaceConstructible>::value && + MoveInsertable + constexpr small_vector& append(InputIt first, InputIt last) { + using policy = typename base::strong_exception_policy; + using iterator_cat = + typename std::iterator_traits::iterator_category; + base::template append_range(first, last, iterator_cat{}); + return *this; + } + + constexpr small_vector& append(std::initializer_list ilist) + requires EmplaceConstructible::value && MoveInsertable + { + return append(ilist.begin(), ilist.end()); + } + + template + constexpr small_vector& append(const small_vector& other) + requires CopyInsertable + { + return append(other.begin(), other.end()); + } + + template + constexpr small_vector& append(small_vector&& other) + requires MoveInsertable + { + // Provide a strong exception guarantee for `other` as well. + using move_iter_type = typename std::conditional_t< + base::template relocate_with_move_v, + std::move_iterator, iterator>; + + append(move_iter_type{other.begin()}, move_iter_type{other.end()}); + other.clear(); + return *this; + } +}; + +template +inline constexpr bool operator==( + const small_vector& lhs, + const small_vector& rhs) { + return lhs.size() == rhs.size() && + std::equal(lhs.begin(), lhs.end(), rhs.begin()); +} + +template +inline constexpr bool operator==( + const small_vector& lhs, + const small_vector& rhs) { + return lhs.size() == rhs.size() && + std::equal(lhs.begin(), lhs.end(), rhs.begin()); +} + +template + requires std::three_way_comparable +constexpr auto operator<=>( + const small_vector& lhs, + const small_vector& rhs) { + return std::lexicographical_compare_three_way( + lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::compare_three_way{}); +} + +template + requires std::three_way_comparable +constexpr auto operator<=>( + const small_vector& lhs, + const small_vector& rhs) { + return std::lexicographical_compare_three_way( + lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::compare_three_way{}); +} + +template +constexpr auto operator<=>( + const small_vector& lhs, + const small_vector& rhs) { + constexpr auto comparison = [](const T& l, const T& r) { + return (l < r) ? std::weak_ordering::less + : (r < l) ? std::weak_ordering::greater + : std::weak_ordering::equivalent; + }; + + return std::lexicographical_compare_three_way( + lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), comparison); +} + +template +constexpr auto operator<=>( + const small_vector& lhs, + const small_vector& rhs) { + constexpr auto comparison = [](const T& l, const T& r) { + return (l < r) ? std::weak_ordering::less + : (r < l) ? std::weak_ordering::greater + : std::weak_ordering::equivalent; + }; + + return std::lexicographical_compare_three_way( + lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), comparison); +} + +template +inline constexpr void swap(small_vector& lhs, + small_vector& + rhs) noexcept(noexcept(lhs.swap(rhs))) + requires concepts::MoveInsertable< + T, small_vector, Allocator> && + concepts::Swappable +{ + lhs.swap(rhs); +} + +template +inline constexpr typename small_vector::size_type +erase(small_vector& v, const U& value) { + const auto original_size = v.size(); + v.erase(std::remove(v.begin(), v.end(), value), v.end()); + return original_size - v.size(); +} + +template +inline constexpr typename small_vector::size_type +erase_if(small_vector& v, Pred pred) { + const auto original_size = v.size(); + v.erase(std::remove_if(v.begin(), v.end(), pred), v.end()); + return original_size - v.size(); +} + +template +constexpr typename small_vector::iterator begin( + small_vector& v) noexcept { + return v.begin(); +} + +template +constexpr typename small_vector::const_iterator +begin(const small_vector& v) noexcept { + return v.begin(); +} + +template +constexpr typename small_vector::const_iterator +cbegin(const small_vector& v) noexcept { + return begin(v); +} + +template +constexpr typename small_vector::iterator end( + small_vector& v) noexcept { + return v.end(); +} + +template +constexpr typename small_vector::const_iterator +end(const small_vector& v) noexcept { + return v.end(); +} + +template +constexpr typename small_vector::const_iterator +cend(const small_vector& v) noexcept { + return end(v); +} + +template +constexpr typename small_vector::reverse_iterator +rbegin(small_vector& v) noexcept { + return v.rbegin(); +} + +template +constexpr + typename small_vector::const_reverse_iterator + rbegin(const small_vector& v) noexcept { + return v.rbegin(); +} + +template +constexpr + typename small_vector::const_reverse_iterator + crbegin(const small_vector& v) noexcept { + return rbegin(v); +} + +template +constexpr typename small_vector::reverse_iterator +rend(small_vector& v) noexcept { + return v.rend(); +} + +template +constexpr + typename small_vector::const_reverse_iterator + rend(const small_vector& v) noexcept { + return v.rend(); +} + +template +constexpr + typename small_vector::const_reverse_iterator + crend(const small_vector& v) noexcept { + return rend(v); +} + +template +constexpr typename small_vector::size_type size( + const small_vector& v) noexcept { + return v.size(); +} + +template +constexpr typename std::common_type_t< + std::ptrdiff_t, typename std::make_signed_t::size_type>> +ssize(const small_vector& v) noexcept { + using ret_type = typename std::common_type_t< + std::ptrdiff_t, typename std::make_signed_t>; + return static_cast(v.size()); +} + +template +[[nodiscard]] +constexpr bool empty( + const small_vector& v) noexcept { + return v.empty(); +} + +template +constexpr typename small_vector::pointer data( + small_vector& v) noexcept { + return v.data(); +} + +template +constexpr typename small_vector::const_pointer +data(const small_vector& v) noexcept { + return v.data(); +} + +template < + typename InputIt, + unsigned InlineCapacity = default_buffer_size_v< + std::allocator::value_type>>, + typename Allocator = + std::allocator::value_type>> +small_vector(InputIt, InputIt, Allocator = Allocator()) + -> small_vector::value_type, + InlineCapacity, Allocator>; + +} // namespace sleipnir diff --git a/include/sleipnir/util/small_vector_LICENSE.txt b/include/sleipnir/util/small_vector_LICENSE.txt new file mode 100644 index 00000000..4cd586a7 --- /dev/null +++ b/include/sleipnir/util/small_vector_LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Gene Harvey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/optimization/solver/util/FeasibilityRestoration.hpp b/src/optimization/solver/util/FeasibilityRestoration.hpp index 776bf8a2..37adf64e 100644 --- a/src/optimization/solver/util/FeasibilityRestoration.hpp +++ b/src/optimization/solver/util/FeasibilityRestoration.hpp @@ -16,7 +16,7 @@ #include "sleipnir/optimization/SolverStatus.hpp" #include "sleipnir/optimization/solver/InteriorPoint.hpp" #include "sleipnir/util/FunctionRef.hpp" -#include "sleipnir/util/SmallVector.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir { diff --git a/src/optimization/solver/util/Filter.hpp b/src/optimization/solver/util/Filter.hpp index ee5c7c3f..bab80b46 100644 --- a/src/optimization/solver/util/Filter.hpp +++ b/src/optimization/solver/util/Filter.hpp @@ -10,7 +10,7 @@ #include #include "sleipnir/autodiff/Variable.hpp" -#include "sleipnir/util/SmallVector.hpp" +#include "sleipnir/util/small_vector.hpp" namespace sleipnir {