mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
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 ✅
953 lines
32 KiB
C++
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)
|