terminal/src/inc/til/small_vector.h
Leonard Hecker 2e78665ee0
Move all blink handling into Renderer (#19330)
This PR moves the cursor blinker and VT blink rendition timer into
`Renderer`. To do so, this PR introduces a generic timer system with
which you can schedule arbitrary timer jobs. Thanks to this, this PR
removes a crapton of code, particularly throughout conhost.

## Validation Steps Performed
* Focus/unfocus starts/stops blinking 
* OS-wide blink settings apply on focus 
2025-11-11 15:29:48 +01:00

953 lines
32 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#pragma warning(push)
// small_vector::_data can reference both non-owned (&_buffer[0]) and owned (new[]) data.
#pragma warning(disable : 26402) // Return a scoped object instead of a heap-allocated if it has a move constructor (r.3).
// small_vector manages a _capacity of potentially uninitialized data. We can't use regular new/delete.
#pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11).
// That's how the STL implemented their std::vector<>::iterator. I simply copied the concept.
#pragma warning(disable : 26434) // Function '...' hides a non-virtual function '...'.
// Functions like front()/back()/operator[]() are explicitly unchecked, just like the std::vector equivalents.
#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
// small_vector::_data references potentially uninitialized data and so we can't pass it regular iterators which reference initialized data.
#pragma warning(disable : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe ... (stl.1).
// small_vector::_data references potentially uninitialized data and so we can't pass it regular iterators which reference initialized data.
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
// small_vector::_buffer is explicitly uninitialized, because we manage its initialization manually.
#pragma warning(disable : 26495) // Variable '...' is uninitialized. Always initialize a member variable (type.6).
namespace til
{
// This class was adopted from std::span<>::iterator.
template<typename T>
struct small_vector_const_iterator
{
using iterator_concept = std::contiguous_iterator_tag;
using iterator_category = std::random_access_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = const T*;
using reference = const T&;
small_vector_const_iterator() = default;
#if _ITERATOR_DEBUG_LEVEL == 0
constexpr small_vector_const_iterator(pointer ptr) :
_ptr{ ptr }
{
}
#else // _ITERATOR_DEBUG_LEVEL == 0
constexpr small_vector_const_iterator(pointer ptr, pointer begin, pointer end) :
_ptr{ ptr },
_begin{ begin },
_end{ end }
{
}
#endif // _ITERATOR_DEBUG_LEVEL == 0
[[nodiscard]] constexpr reference operator*() const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
_STL_VERIFY(_begin, "cannot dereference value-initialized iterator");
_STL_VERIFY(_ptr < _end, "cannot dereference end iterator");
#endif // _ITERATOR_DEBUG_LEVEL >= 1
return *_ptr;
}
[[nodiscard]] constexpr pointer operator->() const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
_STL_VERIFY(_begin, "cannot dereference value-initialized iterator");
_STL_VERIFY(_ptr < _end, "cannot dereference end iterator");
#endif // _ITERATOR_DEBUG_LEVEL >= 1
return _ptr;
}
constexpr small_vector_const_iterator& operator++() noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
_STL_VERIFY(_begin, "cannot increment value-initialized iterator");
_STL_VERIFY(_ptr < _end, "cannot increment iterator past end");
#endif // _ITERATOR_DEBUG_LEVEL >= 1
++_ptr;
return *this;
}
constexpr small_vector_const_iterator operator++(int) noexcept
{
small_vector_const_iterator tmp{ *this };
++*this;
return tmp;
}
constexpr small_vector_const_iterator& operator--() noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
_STL_VERIFY(_begin, "cannot decrement value-initialized iterator");
_STL_VERIFY(_begin < _ptr, "cannot decrement iterator before begin");
#endif // _ITERATOR_DEBUG_LEVEL >= 1
--_ptr;
return *this;
}
constexpr small_vector_const_iterator operator--(int) noexcept
{
small_vector_const_iterator tmp{ *this };
--*this;
return tmp;
}
constexpr void _Verify_offset([[maybe_unused]] const difference_type off) const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
if (off != 0)
{
_STL_VERIFY(_begin, "cannot seek value-initialized iterator");
}
if (off < 0)
{
_STL_VERIFY(_ptr - _begin >= -off, "cannot seek iterator before begin");
}
if (off > 0)
{
_STL_VERIFY(_end - _ptr >= off, "cannot seek iterator after end");
}
#endif // _ITERATOR_DEBUG_LEVEL >= 1
}
constexpr small_vector_const_iterator& operator+=(const difference_type off) noexcept
{
_Verify_offset(off);
_ptr += off;
return *this;
}
[[nodiscard]] constexpr small_vector_const_iterator operator+(const difference_type off) const noexcept
{
small_vector_const_iterator tmp{ *this };
tmp += off;
return tmp;
}
[[nodiscard]] friend constexpr small_vector_const_iterator operator+(const difference_type off, small_vector_const_iterator next) noexcept
{
next += off;
return next;
}
constexpr small_vector_const_iterator& operator-=(const difference_type off) noexcept
{
_Verify_offset(-off);
_ptr -= off;
return *this;
}
[[nodiscard]] constexpr small_vector_const_iterator operator-(const difference_type off) const noexcept
{
small_vector_const_iterator tmp{ *this };
tmp -= off;
return tmp;
}
[[nodiscard]] constexpr difference_type operator-(const small_vector_const_iterator& right) const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
_STL_VERIFY(_begin == right._begin && _end == right._end, "cannot subtract incompatible iterators");
#endif // _ITERATOR_DEBUG_LEVEL >= 1
return _ptr - right._ptr;
}
[[nodiscard]] constexpr reference operator[](const difference_type off) const noexcept
{
return *(*this + off);
}
[[nodiscard]] constexpr bool operator==(const small_vector_const_iterator& right) const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
_STL_VERIFY(_begin == right._begin && _end == right._end, "cannot compare incompatible iterators for equality");
#endif // _ITERATOR_DEBUG_LEVEL >= 1
return _ptr == right._ptr;
}
[[nodiscard]] constexpr std::strong_ordering operator<=>(const small_vector_const_iterator& right) const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
_STL_VERIFY(_begin == right._begin && _end == right._end, "cannot compare incompatible iterators");
#endif // _ITERATOR_DEBUG_LEVEL >= 1
return _ptr <=> right._ptr;
}
#if _ITERATOR_DEBUG_LEVEL >= 1
friend constexpr void _Verify_range(const small_vector_const_iterator& first, const small_vector_const_iterator& last) noexcept
{
_STL_VERIFY(first._begin == last._begin && first._end == last._end, "iterators from different views do not form a range");
_STL_VERIFY(first._ptr <= last._ptr, "iterator range transposed");
}
#endif // _ITERATOR_DEBUG_LEVEL >= 1
using _Prevent_inheriting_unwrap = small_vector_const_iterator;
[[nodiscard]] constexpr pointer _Unwrapped() const noexcept
{
return _ptr;
}
static constexpr bool _Unwrap_when_unverified = _ITERATOR_DEBUG_LEVEL == 0;
constexpr void _Seek_to(const pointer it) noexcept
{
_ptr = it;
}
pointer _ptr = nullptr;
#if _ITERATOR_DEBUG_LEVEL >= 1
pointer _begin = nullptr;
pointer _end = nullptr;
#endif // _ITERATOR_DEBUG_LEVEL >= 1
};
// This class was adopted from std::array<>::iterator.
template<typename T>
struct small_vector_iterator : small_vector_const_iterator<T>
{
using base = small_vector_const_iterator<T>;
using iterator_concept = std::contiguous_iterator_tag;
using iterator_category = std::random_access_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
using base::base;
[[nodiscard]] constexpr reference operator*() const noexcept
{
#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3).
return const_cast<reference>(base::operator*());
}
[[nodiscard]] constexpr pointer operator->() const noexcept
{
#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3).
return const_cast<pointer>(base::operator->());
}
constexpr small_vector_iterator& operator++() noexcept
{
base::operator++();
return *this;
}
constexpr small_vector_iterator operator++(int) noexcept
{
small_vector_iterator tmp = *this;
base::operator++();
return tmp;
}
constexpr small_vector_iterator& operator--() noexcept
{
base::operator--();
return *this;
}
constexpr small_vector_iterator operator--(int) noexcept
{
small_vector_iterator tmp = *this;
base::operator--();
return tmp;
}
constexpr small_vector_iterator& operator+=(const difference_type off) noexcept
{
base::operator+=(off);
return *this;
}
[[nodiscard]] constexpr small_vector_iterator operator+(const difference_type off) const noexcept
{
small_vector_iterator tmp = *this;
tmp += off;
return tmp;
}
[[nodiscard]] friend constexpr small_vector_iterator operator+(const difference_type off, small_vector_iterator next) noexcept
{
next += off;
return next;
}
constexpr small_vector_iterator& operator-=(const difference_type off) noexcept
{
base::operator-=(off);
return *this;
}
using base::operator-;
[[nodiscard]] constexpr small_vector_iterator operator-(const difference_type off) const noexcept
{
small_vector_iterator tmp = *this;
tmp -= off;
return tmp;
}
[[nodiscard]] constexpr reference operator[](const difference_type off) const noexcept
{
return const_cast<reference>(base::operator[](off));
}
using _Prevent_inheriting_unwrap = small_vector_iterator;
[[nodiscard]] constexpr pointer _Unwrapped() const noexcept
{
#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3).
return const_cast<pointer>(base::_Unwrapped());
}
};
template<typename T, size_t N>
class small_vector
{
public:
static_assert(N != 0, "A small_vector without a small buffer isn't very useful");
static_assert(std::is_nothrow_move_assignable_v<T>, "_generic_insert doesn't guard against exceptions");
static_assert(std::is_nothrow_move_constructible_v<T>, "_grow/_generic_insert don't guard against exceptions");
using value_type = T;
using allocator_type = std::allocator<T>;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = size_t;
using difference_type = ptrdiff_t;
using iterator = small_vector_iterator<T>;
using const_iterator = small_vector_const_iterator<T>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
small_vector() noexcept :
_data{ &_buffer[0] },
_capacity{ N },
_size{ 0 }
{
}
explicit small_vector(size_type count, const T& value = T{}) :
small_vector{}
{
insert(end(), count, value);
}
template<std::input_iterator InputIt>
small_vector(InputIt first, InputIt last) :
small_vector{}
{
insert(end(), first, last);
}
small_vector(std::initializer_list<T> list) :
small_vector{}
{
insert(end(), list.begin(), list.end());
}
// NOTE: If an exception is thrown while copying, the vector is left empty.
small_vector(const small_vector& other) :
small_vector{}
{
_copy_assign(other);
}
// NOTE: If an exception is thrown while copying, the vector is left empty.
small_vector& operator=(const small_vector& other)
{
if (this != &other)
{
clear();
_copy_assign(other);
}
return *this;
}
small_vector(small_vector&& other) noexcept
{
_move_assign(other);
}
small_vector& operator=(small_vector&& other) noexcept
{
if (this != &other)
{
std::destroy(begin(), end());
if (_capacity != N)
{
_deallocate(_data);
}
_move_assign(other);
}
return *this;
}
~small_vector()
{
std::destroy(begin(), end());
if (_capacity != N)
{
_deallocate(_data);
}
}
constexpr size_type max_size() const noexcept { return static_cast<size_t>(-1) / sizeof(T); }
constexpr pointer data() noexcept { return _data; }
constexpr const_pointer data() const noexcept { return _data; }
constexpr size_type capacity() const noexcept { return _capacity; }
constexpr size_type size() const noexcept { return _size; }
constexpr size_type empty() const noexcept { return _size == 0; }
constexpr iterator begin() noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
return { _data, _data, _data + _size };
#else
return { _data };
#endif
}
constexpr const_iterator begin() const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
return { _data, _data, _data + _size };
#else
return { _data };
#endif
}
constexpr const_iterator cbegin() const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
return { _data, _data, _data + _size };
#else
return { _data };
#endif
}
constexpr reverse_iterator rbegin() noexcept
{
return std::make_reverse_iterator(end());
}
constexpr const_reverse_iterator rbegin() const noexcept
{
return std::make_reverse_iterator(end());
}
constexpr const_reverse_iterator crbegin() const noexcept
{
return std::make_reverse_iterator(end());
}
constexpr iterator end() noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
return { _data + _size, _data, _data + _size };
#else
return { _data + _size };
#endif
}
constexpr const_iterator end() const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
return { _data + _size, _data, _data + _size };
#else
return { _data + _size };
#endif
}
constexpr const_iterator cend() const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
return { _data + _size, _data, _data + _size };
#else
return { _data + _size };
#endif
}
constexpr reverse_iterator rend() noexcept
{
return std::make_reverse_iterator(begin());
}
constexpr const_reverse_iterator rend() const noexcept
{
return std::make_reverse_iterator(begin());
}
constexpr const_reverse_iterator crend() const noexcept
{
return std::make_reverse_iterator(begin());
}
bool operator==(const small_vector& other) const noexcept
{
return std::equal(begin(), end(), other.begin(), other.end());
}
reference front() noexcept
{
return operator[](0);
}
const_reference front() const noexcept
{
return operator[](0);
}
reference back() noexcept
{
return operator[](_size - 1);
}
const_reference back() const noexcept
{
return operator[](_size - 1);
}
reference operator[](size_type off) noexcept
{
#if _CONTAINER_DEBUG_LEVEL > 0
_STL_VERIFY(off < _size, "subscript out of range");
#endif // _CONTAINER_DEBUG_LEVEL > 0
return _data[off];
}
const_reference operator[](size_type off) const noexcept
{
#if _CONTAINER_DEBUG_LEVEL > 0
_STL_VERIFY(off < _size, "subscript out of range");
#endif // _CONTAINER_DEBUG_LEVEL > 0
return _data[off];
}
reference at(size_type off)
{
if (off >= _size)
{
_throw_invalid_subscript();
}
return _data[off];
}
const_reference at(size_type off) const
{
if (off >= _size)
{
_throw_invalid_subscript();
}
return _data[off];
}
void clear() noexcept
{
std::destroy(begin(), end());
_size = 0;
}
void reserve(size_type capacity)
{
if (capacity > _capacity)
{
_grow(capacity);
}
}
void resize(size_type new_size)
{
_generic_resize(new_size, [](iterator&& beg, iterator&& end) {
std::uninitialized_value_construct(beg, end);
});
}
void resize(size_type new_size, const_reference value)
{
_generic_resize(new_size, [&](iterator&& beg, iterator&& end) {
std::uninitialized_fill(beg, end, value);
});
}
void resize_and_overwrite(size_type new_size, auto op)
requires std::is_trivial_v<T>
{
_size = 0;
reserve(new_size);
_size = std::move(op)(_data, new_size);
}
void shrink_to_fit()
{
if (_capacity == N || _size == _capacity)
{
return;
}
auto data = &_buffer[0];
auto capacity = N;
if (_size > N)
{
data = _allocate(_size);
capacity = _size;
}
std::uninitialized_move(begin(), end(), data);
std::destroy(begin(), end());
_deallocate(_data);
_data = data;
_capacity = capacity;
}
// This is a very unsafe shortcut to free the buffer and get a direct
// hold to the _buffer. The caller can then fill it with `size` items.
[[nodiscard]] T* unsafe_shrink_to_size(size_t size) noexcept
{
assert(size <= N);
if (_capacity != N)
{
_deallocate(_data);
}
_data = &_buffer[0];
_capacity = N;
_size = size;
return &_buffer[0];
}
void push_back(const T& value)
{
emplace_back(value);
}
void push_back(T&& value)
{
emplace_back(std::move(value));
}
template<typename... Args>
reference emplace_back(Args&&... args)
{
const auto new_size = _ensure_fits(1);
const auto it = std::construct_at(_data + _size, std::forward<Args>(args)...);
_size = new_size;
return *it;
}
void pop_back()
{
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_size != 0, "empty before pop");
#endif // _ITERATOR_DEBUG_LEVEL == 2
std::destroy_at(_data + _size - 1);
_size--;
}
// NOTE: If the copy constructor throws, the range [pos, end()) is erased.
iterator insert(const_iterator pos, const T& value)
{
return _generic_insert(pos, 1, [&](auto&& it) noexcept(std::is_nothrow_copy_constructible_v<T>) {
std::construct_at(&*it, value);
});
}
// NOTE: If the move constructor throws, the range [pos, end()) is erased.
iterator insert(const_iterator pos, T&& value)
{
// The earlier static_assert(std::is_nothrow_move_constructible_v<T>)
// ensures that this lambda is noexcept.
return _generic_insert(pos, 1, [&](auto&& it) noexcept {
std::construct_at(&*it, std::move(value));
});
}
// NOTE: If the copy constructor throws, the range [pos, end()) is erased.
iterator insert(const_iterator pos, size_type count, const T& value)
{
return _generic_insert(pos, count, [&](auto&& it) noexcept(std::is_nothrow_copy_constructible_v<T>) {
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
std::uninitialized_fill_n(it, count, value);
});
}
// NOTE: If the copy/move constructor throws, the range [pos, end()) is erased.
template<std::input_iterator InputIt>
iterator insert(const_iterator pos, InputIt first, InputIt last)
{
return _generic_insert(pos, std::distance(first, last), [&](auto&& it) noexcept(std::is_nothrow_constructible_v<T, decltype(*first)>) {
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
std::uninitialized_copy(first, last, it);
});
}
// NOTE: If the copy constructor throws, the range [pos, end()) is erased.
iterator insert(const_iterator pos, std::initializer_list<T> list)
{
return insert(pos, list.begin(), list.end());
}
void erase(const_iterator pos)
{
erase(pos, pos + 1);
}
void erase(const_iterator first, const_iterator last)
{
if (first >= last)
{
return;
}
const auto beg = begin();
const auto off = first - cbegin();
const auto it = beg + off;
const auto end_old = end();
const auto end_new = std::move<const_iterator, iterator>(last, end_old, it);
std::destroy(end_new, end_old);
_size = end_new - beg;
}
private:
[[noreturn]] static void _throw_invalid_subscript()
{
throw std::out_of_range("invalid small_vector subscript");
}
[[noreturn]] static void _throw_too_long()
{
throw std::length_error("small_vector too long");
}
static T* _allocate(size_t size)
{
if constexpr (alignof(T) <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
{
return static_cast<T*>(::operator new(size * sizeof(T)));
}
else
{
return static_cast<T*>(::operator new(size * sizeof(T), static_cast<std::align_val_t>(alignof(T))));
}
}
static void _deallocate(T* data) noexcept
{
if constexpr (alignof(T) <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
{
::operator delete(data);
}
else
{
::operator delete(data, static_cast<std::align_val_t>(alignof(T)));
}
}
iterator _uninitialized_begin() const noexcept
{
#if _ITERATOR_DEBUG_LEVEL >= 1
return { _data, _data + _size, _data + _capacity };
#else
return { _data };
#endif
}
void _copy_assign(const small_vector& other)
{
reserve(other._size);
std::uninitialized_copy(other.begin(), other.end(), _uninitialized_begin());
_size = other._size;
}
void _move_assign(small_vector& other) noexcept
{
if (other._capacity == N)
{
_data = &_buffer[0];
_capacity = N;
_size = other._size;
// The earlier static_assert(std::is_nothrow_move_constructible_v<T>)
// ensures that we don't exit in a weird state with invalid `_size`.
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
std::uninitialized_move(other.begin(), other.end(), _uninitialized_begin());
std::destroy(other.begin(), other.end());
}
else
{
_data = other._data;
_capacity = other._capacity;
_size = other._size;
}
other._data = &other._buffer[0];
other._capacity = N;
other._size = 0;
}
size_type _ensure_fits(size_type add)
{
const auto new_size = _size + add;
if (new_size < _size)
{
_throw_too_long();
}
if (new_size > _capacity)
{
_grow(new_size);
}
return new_size;
}
// What use is a small_vector when you need it to heap allocate often?
// --> _grow() is noinline because we expect this to be called in slow paths only.
__declspec(noinline) void _grow(size_type min_cap)
{
const auto new_cap = std::max(min_cap, _capacity + _capacity / 2);
// Protect against overflow in the multiplication in _allocate.
if (new_cap > max_size())
{
_throw_too_long();
}
const auto data = _allocate(new_cap);
// The earlier static_assert(std::is_nothrow_move_constructible_v<T>)
// ensures that we don't leak `data` here since no exceptions will be thrown.
std::uninitialized_move(begin(), end(), data);
std::destroy(begin(), end());
if (_capacity != N)
{
_deallocate(_data);
}
_data = data;
_capacity = new_cap;
}
void _generic_resize(size_type new_size, auto&& func)
{
if (new_size < _size)
{
std::destroy(begin() + new_size, end());
}
else if (new_size > _size)
{
reserve(new_size);
func(_uninitialized_begin() + _size, _uninitialized_begin() + new_size);
}
_size = new_size;
}
iterator _generic_insert(const_iterator pos, size_type count, auto&& func)
{
const auto offset = pos - cbegin();
const auto old_size = _size;
const auto new_size = _ensure_fits(count);
const auto moveable = old_size - offset;
// An optimization for the most common vector type which is trivially and copyable and noexcept constructible.
// Compared to the complex form below, we don't need the 2 moves and 1 destroy, because is_trivially_copyable_v implies
// that we can just memmove() the items all at once. We don't need a try/catch either because func() is noexcept.
if constexpr (noexcept(func(begin())) && std::is_trivially_copyable_v<T>)
{
_size = new_size;
// Make space for `count` uninitialized items, to be filled by `func`.
const auto it = begin() + offset;
const auto dest = it + count;
memmove(dest._Unwrapped(), it._Unwrapped(), moveable * sizeof(T));
func(it);
return it;
}
else
{
// The earlier static_assert(std::is_nothrow_move_assignable_v<T>)
// ensures that we don't exit in a weird state with invalid `_size`.
// 1. We've resized the vector to fit an additional `count` items.
// Initialize the `count` items at the end by moving existing items over.
//
// This line is noexcept:
// It can't throw because of the earlier static_assert(std::is_nothrow_move_assignable_v<T>).
const auto displacement = std::min(count, moveable);
std::uninitialized_move(end() - displacement, end(), _uninitialized_begin() + (new_size - displacement));
_size = new_size;
// 2. Now that all `new_size` items are initialized we can move as many
// items towards the end as we need to make space for `count` items.
//
// This line is noexcept:
// It can't throw because of the earlier static_assert(std::is_nothrow_move_assignable_v<T>).
const auto displacement_beg = begin() + offset;
const auto displacement_end = displacement_beg + displacement;
std::move_backward(displacement_beg, displacement_beg + (moveable - displacement), end() - displacement);
// 3. Destroy the displaced existing items, so that we can use
// std::uninitialized_*/std::construct_* functions inside `func`.
// This isn't optimal, but better than nothing.
std::destroy(displacement_beg, displacement_end);
try
{
func(displacement_beg);
}
catch (...)
{
std::move(displacement_beg + count, end(), displacement_beg);
std::destroy(displacement_end, end());
_size = old_size;
throw;
}
return displacement_beg;
}
}
T* _data;
size_t _capacity;
size_t _size;
union
{
T _buffer[N];
};
};
}
namespace std
{
template<typename T, size_t N, typename F>
void erase_if(til::small_vector<T, N>& vec, F&& pred)
{
const auto beg = vec.begin();
const auto end = vec.end();
vec.erase(std::remove_if(beg, end, std::forward<F>(pred)), end);
}
}
#pragma warning(pop)