aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-03-19 13:02:24 +0100
committerFelix Morgner <felix.morgner@ost.ch>2026-03-19 13:02:24 +0100
commit217790ff1b970c1b679fbdb3df0055976c8d6204 (patch)
tree46d655184fb672d461126968efc920b296ed8e42
parentc86c898836b1564de2733330540f5d0cd56e8b10 (diff)
parentae2a264b117ecf556f742d8e9c357f906cb3fd83 (diff)
downloadteachos-217790ff1b970c1b679fbdb3df0055976c8d6204.tar.xz
teachos-217790ff1b970c1b679fbdb3df0055976c8d6204.zip
Merge branch 'fmorgner/allocator-aware-vector' into 'develop-BA-FS26'
kstd: make vector allocator-aware See merge request teachos/kernel!15
-rw-r--r--kapi/include/kapi/boot_module/boot_module_registry.hpp4
-rw-r--r--libs/kstd/include/kstd/allocator64
-rw-r--r--libs/kstd/include/kstd/ranges21
-rw-r--r--libs/kstd/include/kstd/vector980
4 files changed, 628 insertions, 441 deletions
diff --git a/kapi/include/kapi/boot_module/boot_module_registry.hpp b/kapi/include/kapi/boot_module/boot_module_registry.hpp
index 70b5592..0692d37 100644
--- a/kapi/include/kapi/boot_module/boot_module_registry.hpp
+++ b/kapi/include/kapi/boot_module/boot_module_registry.hpp
@@ -20,8 +20,8 @@ namespace kapi::boot_modules
using value_type = range_type::value_type;
using const_reference = range_type::const_reference;
- using const_iterator = range_type::const_pointer;
- using const_reverse_iterator = range_type::const_pointer;
+ using const_iterator = range_type::const_iterator;
+ using const_reverse_iterator = range_type::const_reverse_iterator;
using size_type = range_type::size_type;
[[nodiscard]] auto begin() const noexcept -> const_iterator
diff --git a/libs/kstd/include/kstd/allocator b/libs/kstd/include/kstd/allocator
new file mode 100644
index 0000000..0de0e10
--- /dev/null
+++ b/libs/kstd/include/kstd/allocator
@@ -0,0 +1,64 @@
+#ifndef KSTD_ALLOCATOR_HPP
+#define KSTD_ALLOCATOR_HPP
+
+#include <cstddef>
+#include <new>
+#include <type_traits>
+
+#if __has_builtin(__builtin_operator_new) >= 201'802L
+#define KSTD_OPERATOR_NEW __builtin_operator_new
+#define KSTD_OPERATOR_DELETE __builtin_operator_delete
+#else
+#define KSTD_OPERATOR_NEW ::operator new
+#define KSTD_OPERATOR_DELETE ::operator delete
+#endif
+
+namespace kstd
+{
+
+ template<typename T>
+ struct allocator
+ {
+ using value_type = T;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using propagate_on_container_move_assignment = std::true_type;
+ using is_always_equal = std::true_type;
+
+ constexpr allocator() noexcept = default;
+
+ template<typename U>
+ constexpr allocator(allocator<U> const &) noexcept
+ {}
+
+ constexpr auto allocate(std::size_t n) -> T *
+ {
+ if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
+ {
+ return static_cast<T *>(KSTD_OPERATOR_NEW(n * sizeof(T), std::align_val_t{alignof(T)}));
+ }
+ return static_cast<T *>(KSTD_OPERATOR_NEW(n * sizeof(T)));
+ }
+
+ constexpr void deallocate(T * p, std::size_t n) noexcept
+ {
+ if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
+ {
+ KSTD_OPERATOR_DELETE(p, n * sizeof(T), std::align_val_t{alignof(T)});
+ }
+ KSTD_OPERATOR_DELETE(p, n * sizeof(T));
+ }
+ };
+
+ template<typename T, typename U>
+ constexpr auto operator==(allocator<T> const &, allocator<U> const &) noexcept -> bool
+ {
+ return true;
+ }
+
+} // namespace kstd
+
+#undef KSTD_OPERATOR_NEW
+#undef KSTD_OPERATOR_DELETE
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/ranges b/libs/kstd/include/kstd/ranges
new file mode 100644
index 0000000..78c3adb
--- /dev/null
+++ b/libs/kstd/include/kstd/ranges
@@ -0,0 +1,21 @@
+#ifndef KSTD_RANGES
+#define KSTD_RANGES
+
+#include <ranges> // IWYU pragma: export
+
+namespace kstd
+{
+#if __glibcxx_ranges_to_container
+ using std::from_range;
+ using std::from_range_t;
+#else
+ struct from_range_t
+ {
+ explicit from_range_t() = default;
+ };
+ constexpr auto inline from_range = from_range_t{};
+#endif
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector
index 6af7c12..9709c2a 100644
--- a/libs/kstd/include/kstd/vector
+++ b/libs/kstd/include/kstd/vector
@@ -1,11 +1,20 @@
#ifndef KSTD_VECTOR_HPP
#define KSTD_VECTOR_HPP
+#include <kstd/allocator>
#include <kstd/os/error.hpp>
+#include <kstd/ranges>
#include <algorithm>
+#include <bits/ranges_base.h>
+#include <concepts>
#include <cstddef>
#include <initializer_list>
+#include <iterator>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+#include <utility>
namespace kstd
{
@@ -14,554 +23,647 @@ namespace kstd
* custom memory management.
*
* @tparam T Element the vector instance should contain.
+ * @tparam Allocator The allocator to use when allocating new elements.
*/
- template<typename T>
+ template<typename T, typename Allocator = kstd::allocator<T>>
struct vector
{
using value_type = T; ///< Type of the elements contained in the container.
+ using allocator_type = Allocator; ///< Type of the allocator used by the container.
using size_type = std::size_t; ///< Type of the size in the container.
+ using difference_type = std::ptrdiff_t; ///< Type of the difference between two iterators.
using reference = value_type &; ///< Type of reference to the elements.
using const_reference = value_type const &; ///< Type of constant reference to the elements.
using pointer = value_type *; ///< Type of pointer to the elements.
using const_pointer = value_type const *; ///< Type of constant pointer to the elements.
+ using iterator = pointer; ///< Type of iterator to the elements.
+ using const_iterator = const_pointer; ///< Type of constant iterator to the elements.
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ //! Construct a new, empty vector.
+ vector() noexcept(std::is_nothrow_default_constructible_v<allocator_type>)
+ : vector(allocator_type{})
+ {}
+
+ //! Construct a new, empty vector with a given allocator.
+ //!
+ //! @param allocator The allocator to use in the vector.
+ explicit constexpr vector(allocator_type const & allocator) noexcept(
+ std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ {}
+
+ //! Construct a new vector and fill it with the given number of default constructed elements.
+ //!
+ //! @param count The number of element to create the vector with.
+ //! @param allocator The allocator to use in the vector.
+ explicit constexpr vector(size_type count, allocator_type const & allocator = allocator_type{}) noexcept(
+ std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{count}
+ , m_capacity{count}
+ , m_data{allocate_n(m_capacity)}
+ {
+ for (auto i = 0uz; i < count; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i);
+ }
+ }
+
+ //! Construct a new vector and fill it with the given number of copy constructed elements.
+ //!
+ //! @param count The number of element to create the vector with.
+ //! @param value The value to copy for each element
+ //! @param allocator The allocator to use in the vector.
+ constexpr vector(size_type count, const_reference value,
+ allocator_type const & allocator =
+ allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{count}
+ , m_capacity{m_size}
+ , m_data{allocate_n(m_capacity)}
+ {
+ for (auto i = 0uz; i < count; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i, value);
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given range.
+ //!
+ //! @tparam InputIterator An iterator type used to describe the source range.
+ //! @param first The start of the source range.
+ //! @param last The end of the source range.
+ template<std::input_iterator InputIterator>
+ constexpr vector(InputIterator first, InputIterator last,
+ allocator_type const & allocator =
+ allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{std::ranges::distance(first, last)}
+ , m_capacity{m_size}
+ , m_data{allocate_n(m_capacity)}
+ {
+ for (auto destination = m_data; first != last; ++first, ++destination)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, destination, *first);
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given range.
+ //!
+ //!
+ template<typename Range>
+ requires(std::ranges::input_range<Range> && std::convertible_to<std::ranges::range_reference_t<Range>, T>)
+ constexpr vector(kstd::from_range_t, Range && range, allocator_type const & allocator = allocator_type{})
+ : m_allocator{allocator}
+ , m_size{std::ranges::size(range)}
+ , m_capacity{m_size}
+ , m_data{allocate_n(m_capacity)}
+ {
+ auto destination = m_data;
+ for (auto && element : std::forward<Range>(range))
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, destination++,
+ std::forward<std::ranges::range_reference_t<Range>>(element));
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements from a given vector.
+ //!
+ //! @param other The source vector.
+ constexpr vector(vector const & other)
+ : m_allocator{other.m_allocator}
+ , m_size{other.m_size}
+ , m_capacity{other.m_capacity}
+ , m_data(allocate_n(m_capacity))
+ {
+ uninitialized_copy_with_allocator(other.begin(), begin(), other.size());
+ }
+
+ //! Construct a new vector and initialize it's content by moving from a given vector.
+ //!
+ //! @param other The source vector.
+ constexpr vector(vector && other) noexcept
+ : m_allocator{std::move(std::exchange(other.m_allocator, allocator_type{}))}
+ , m_size{std::exchange(other.m_size, size_type{})}
+ , m_capacity(std::exchange(other.m_capacity, size_type{}))
+ , m_data(std::exchange(other.m_data, nullptr))
+ {}
+
+ //! Construct a new vector and initialize it's content by copying from a given vector.
+ //!
+ //! @param other The source vector.
+ //! @param allocator The allocator to use in the vector.
+ constexpr vector(vector const & other, std::type_identity_t<Allocator> const & allocator)
+ : m_allocator{allocator}
+ , m_size{other.m_size}
+ , m_capacity{other.m_capacity}
+ , m_data{allocate_n(m_capacity)}
+ {
+ uninitialized_copy_with_allocator(other.begin(), begin(), other.size());
+ }
+
+ //! Construct a new vector and initialize it's content by copying from a given vector.
+ //!
+ //! @param other The source vector.
+ //! @param allocator The allocator to use in the vector.
+ constexpr vector(vector && other, std::type_identity_t<Allocator> const & allocator)
+ : m_allocator{allocator}
+ , m_size{std::exchange(other.m_size, size_type{})}
+ , m_capacity(std::exchange(other.m_capacity, size_type{}))
+ , m_data(std::exchange(other.m_data, nullptr))
+ {}
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given initializer list.
+ //!
+ //! @param list The initializer list containing the source objects.
+ explicit vector(std::initializer_list<value_type> list, allocator_type const & allocator = allocator_type{})
+ : vector{std::ranges::begin(list), std::ranges::end(list), allocator}
+ {}
+
+ //! Destroy this vector.
+ constexpr ~vector()
+ {
+ clear_and_deallocate();
+ }
+
+ //! Replace the contents of this vector by the copying from the given source vector.
+ //!
+ //! @param other The source vector.
+ constexpr auto operator=(vector const & other) -> vector<value_type> &
+ {
+ if (this == std::addressof(other))
+ {
+ return *this;
+ }
+
+ if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
+ {
+ if (get_allocator() != other.get_allocator())
+ {
+ clear_and_deallocate();
+ }
+ m_allocator = other.get_allocator();
+ }
- /**
- * @brief Default Constructor.
- */
- vector() = default;
-
- /**
- * @brief Constructs data with the given amount of elements containing the given value or alternatively the default
- * constructed value.
- *
- * @param n Amount of elements we want to create and set the given value for.
- * @param initial Inital value of all elements in the underlying data array.
- */
- explicit vector(size_type n, value_type initial = value_type{})
- : _size(n)
- , _capacity(n)
- , _data(_capacity ? new value_type[_capacity]{} : nullptr)
- {
- std::ranges::fill(*this, initial);
- }
-
- /**
- * @brief Constructs data by copying all element from the given exclusive range.
- *
- * @tparam InputIterator Template that should have atleast input iterator characteristics.
- * @param first Input iterator to the first element in the range we want to copy from.
- * @param last Input iterator to one past the last element in the range we want to copy from.
- */
- template<typename InputIterator>
- explicit vector(InputIterator first, InputIterator last)
- : _size(std::distance(first, last))
- , _capacity(std::distance(first, last))
- , _data(_capacity ? new value_type[_capacity]() : nullptr)
- {
- std::ranges::copy(first, last, _data);
- }
-
- /**
- * @brief Construct data by copying all elements from the initializer list.
- *
- * @param initializer_list List we want to copy all elements from.
- */
- explicit vector(std::initializer_list<value_type> initializer_list)
- : _size(initializer_list.size())
- , _capacity(initializer_list.size())
- , _data(_capacity ? new value_type[_capacity]() : nullptr)
- {
- std::ranges::copy(initializer_list, _data);
- }
-
- /**
- * @brief Copy constructor.
- *
- * @note Allocates underlying data container with the same capacity as vector we are copying from and copies all
- * elements from it.
- *
- * @param other Other instance of vector we want to copy the data from.
- */
- vector(vector<value_type> const & other)
- : _size(other._size)
- , _capacity(other._capacity)
- , _data(_capacity ? new value_type[_capacity]() : nullptr)
- {
- std::ranges::copy(other, _data);
- }
-
- /**
- * @brief Copy assignment operator.
- *
- * @note Allocates underlying data container with the same capacity as vector we are copying from and copies all
- * elements from it.
- *
- * @param other Other instance of vector we want to copy the data from.
- * @return Newly created copy.
- */
- auto operator=(vector const & other) -> vector<value_type> &
- {
- delete[] _data;
- _size = other._size;
- _capacity = other._capacity;
- _data = _capacity ? new value_type[_capacity]() : nullptr;
- std::ranges::copy(other, _data);
+ if (capacity() >= other.size())
+ {
+ auto const overlap = std::min(m_size, other.m_size);
+ copy_elements(other.begin(), begin(), overlap);
+
+ if (m_size < other.m_size)
+ {
+ uninitialized_copy_with_allocator(other.begin() + size(), begin() + size(), other.m_size - size());
+ }
+ else if (m_size > other.m_size)
+ {
+ destroy_n(begin() + other.size(), size() - other.size());
+ }
+ }
+ else
+ {
+ auto new_data = std::allocator_traits<allocator_type>::allocate(m_allocator, other.size());
+ uninitialized_copy_with_allocator(other.begin(), new_data, other.size());
+ clear_and_deallocate();
+ m_data = new_data;
+ m_capacity = other.size();
+ }
+
+ m_size = other.size();
return *this;
}
- /**
- * @brief Destructor.
- */
- ~vector()
- {
- delete[] _data;
- }
-
- /**
- * @brief Amount of elements currently contained in this vector, will fill up until we have reached the capacity. If
- * that is the case the capacity is increased automatically.
- *
- * @return Current amount of elements.
- */
- [[nodiscard]] auto size() const -> size_type
- {
- return _size;
- }
-
- /**
- * @brief Amount of space the vector currently has, can be different than the size, because we allocate more than we
- * exactly require to decrease the amount of allocations and deallocation to improve speed.
- *
- * @return Current amount of space the vector has for elements.
- */
- [[nodiscard]] auto capacity() const -> size_type
- {
- return _capacity;
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Does not do any bounds checks use at() for that.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- auto operator[](size_type index) -> reference
- {
- return _data[index];
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Does not do any bounds checks use at() for that.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- auto operator[](size_type index) const -> const_reference
- {
- return _data[index];
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is halted.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- auto at(size_type index) -> reference
- {
- throw_if_out_of_range(index);
- return this->operator[](index);
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is halted.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- [[nodiscard]] auto at(size_type index) const -> const_reference
- {
- throw_if_out_of_range(index);
- return this->operator[](index);
- }
-
- /**
- * @brief Appends the given element value to the end of the container. The element is assigned through the
- * assignment operator of the template type. The value is forwarded to the constructor as
- * std::forward<U>(value), meaning it is either moved (rvalue) or copied (lvalue).
- *
- * @note If after the operation the new size() is greater than old capacity() a reallocation takes place,
- * in which case all iterators (including the end() iterator) and all references to the elements are invalidated.
- * Otherwise only the end() iterator is invalidated. Uses a forward reference for the actual value passed, which
- * allows the template method to be used by both lvalue and rvalues and compile a different implementation.
- *
- * @param value The value of the element to append.
- */
- template<class U>
- auto push_back(U && value) -> void
+ //! Replace the contents fo this vector by moving from the given source.
+ //!
+ //! @param other The source vector.
+ constexpr auto operator=(vector && other) noexcept(
+ std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value ||
+ std::allocator_traits<allocator_type>::is_always_equal::value) -> vector<value_type> &
{
- increase_capacity_if_full();
- _data[_size] = std::forward<U>(value);
- (void)_size++;
- }
-
- /**
- * @brief Appends a new element to the end of the container. The element is constructed through a constructor of the
- * template type. The arguments args... are forwarded to the constructor as std::forward<Args>(args)....
- *
- * If after the operation the new size() is greater than old capacity() a reallocation takes place, in which case
- * all iterators (including the end() iterator) and all references to the elements are invalidated. Otherwise only
- * the end() iterator is invalidated. Uses a forward reference for the actual value passed, which
- * allows the template method to be used by both lvalue and rvalues and compile a different implementation.
- *
- * @tparam Args
- * @param args Arguments to forward to the constructor of the element
- * @return value_type&
- */
- template<class... Args>
- auto emplace_back(Args &&... args) -> value_type &
+ using std::swap;
+
+ if (this == std::addressof(other))
+ {
+ return *this;
+ }
+
+ if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
+ {
+ clear_and_deallocate();
+ swap(m_allocator, other.m_allocator);
+ swap(m_size, other.m_size);
+ swap(m_capacity, other.m_capacity);
+ swap(m_data, other.m_data);
+ }
+ else if (m_allocator == other.m_allocator)
+ {
+ clear_and_deallocate();
+ swap(m_size, other.m_size);
+ swap(m_capacity, other.m_capacity);
+ swap(m_data, other.m_data);
+ }
+ else
+ {
+ if (capacity() >= other.size())
+ {
+ auto const overlap = std::min(size(), other.size());
+ move_elements(other.begin(), begin(), overlap);
+
+ if (size() < other.size())
+ {
+ uninitialized_move_with_allocator(other.begin() + size(), begin() + size(), other.size() - size());
+ }
+ else if (size() > other.size())
+ {
+ destroy_n(begin() + other.size(), size() - other.size());
+ }
+ }
+ else
+ {
+ auto new_data = std::allocator_traits<allocator_type>::allocate(get_allocator(), other.size());
+ uninitialized_move_with_allocator(other.begin(), new_data, other.size());
+ clear_and_deallocate();
+ m_data = new_data;
+ m_capacity = other.m_size;
+ }
+
+ other.destroy_n(other.begin(), other.size());
+ m_size = std::exchange(other.m_size, size_type{});
+ }
+
+ return *this;
+ }
+
+ //! Get a copy of the allocator associated with this vector.
+ [[nodiscard]] constexpr auto get_allocator() const noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ -> allocator_type
{
- increase_capacity_if_full();
- _data[_size] = value_type{std::forward<Args>(args)...};
- auto const index = _size++;
- return _data[index];
+ return m_allocator;
}
- /**
- * @brief Removes the last element of the container. Calling pop_back on an empty container results in halting the
- * further execution. Iterators and references to the last element are invalidated. The end()
- * iterator is also invalidated.
- */
- auto pop_back() -> void
+ //! Get a reference to the element at the given index.
+ //!
+ //! This function will panic if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto at(size_type index) -> reference
{
- throw_if_empty();
- (void)_size--;
+ panic_if_out_of_bounds(index);
+ return (*this)[index];
}
- /**
- * @brief Returns an iterator to the first element of the vector.
- * If the vector is empty, the returned iterator will be equal to end().
- *
- * @return Iterator to the first element.
- */
- auto begin() noexcept -> pointer
+ //! Get a reference to the element at the given index.
+ //!
+ //! This function will panic if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto at(size_type index) const -> const_reference
{
- return _data;
+ panic_if_out_of_bounds(index);
+ return (*this)[index];
}
- /**
- * @brief Returns an iterator to the first element of the vector.
- * If the vector is empty, the returned iterator will be equal to end().
- *
- * @return Iterator to the first element.
- */
- [[nodiscard]] auto begin() const noexcept -> const_pointer
+ //! Get a reference to the element at the given index.
+ //!
+ //! The behavior is undefined if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto operator[](size_type index) -> reference
{
- return _data;
+ return data()[index];
}
- /**
- * @brief Returns an iterator to the first element of the vector.
- * If the vector is empty, the returned iterator will be equal to end().
- *
- * @return Iterator to the first element.
- */
- [[nodiscard]] auto cbegin() const noexcept -> const_pointer
+ //! Get a reference to the element at the given index.
+ //!
+ //! The behavior is undefined if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto operator[](size_type index) const -> const_reference
{
- return begin();
+ return data()[index];
}
- /**
- * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element
- * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend().
- *
- * @return Reverse iterator to the first element.
- */
- auto rbegin() noexcept -> pointer
+ //! Get a reference to the first element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto front() -> reference
{
- return _data + _size - 1;
+ return *begin();
}
- /**
- * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element
- * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend().
- *
- * @return Reverse iterator to the first element.
- */
- [[nodiscard]] auto rbegin() const noexcept -> const_pointer
+ //! Get a reference to the first element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] auto front() const -> const_reference
{
- return _data + _size - 1;
+ return *begin();
}
- /**
- * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element
- * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend().
- *
- * @return Reverse iterator to the first element.
- */
- [[nodiscard]] auto crbegin() const noexcept -> const_pointer
+ //! Get a reference to the last element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto back() -> reference
{
- return rbegin();
+ return *rbegin();
+ }
+
+ //! Get a reference to the last element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto back() const -> const_reference
+ {
+ return *rbegin();
+ }
+
+ //! Get a pointer to the beginning of the underlying contiguous storage.
+ [[nodiscard]] constexpr auto data() noexcept -> pointer
+ {
+ return m_data;
+ }
+
+ //! Get a pointer to the beginning of the underlying contiguous storage.
+ [[nodiscard]] constexpr auto data() const noexcept -> const_pointer
+ {
+ return m_data;
+ }
+
+ //! Get an iterator to the first element of this vector.
+ //!
+ //! @return An iterator to the first element of this container, or end() if the container is empty.
+ [[nodiscard]] constexpr auto begin() noexcept -> iterator
+ {
+ return empty() ? end() : data();
+ }
+
+ //! Get an iterator to the first element of this vector.
+ //!
+ //! @return An iterator to the first element of this container, or end() if the container is empty.
+ [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator
+ {
+ return empty() ? end() : data();
+ }
+
+ //! Get an iterator to the first element of this vector.
+ //!
+ //! @return An iterator to the first element of this container, or end() if the container is empty.
+ [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator
+ {
+ return begin();
}
- /**
- * @brief Returns an iterator to the element following the last element of the vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Iterator to the element following the last element.
- */
- auto end() noexcept -> pointer
+ //! Get an iterator past the last element of this vector.
+ [[nodiscard]] constexpr auto end() noexcept -> pointer
{
- return _data + _size;
+ return capacity() ? data() + size() : nullptr;
}
- /**
- * @brief Returns an iterator to the element following the last element of the vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Iterator to the element following the last element.
- */
- [[nodiscard]] auto end() const noexcept -> const_pointer
+ //! Get an iterator past the last element of this vector.
+ [[nodiscard]] constexpr auto end() const noexcept -> const_pointer
{
- return _data + _size;
+ return capacity() ? data() + size() : nullptr;
}
- /**
- * @brief Returns an iterator to the element following the last element of the vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Iterator to the element following the last element.
- */
- [[nodiscard]] auto cend() const noexcept -> const_pointer
+ //! Get an iterator past the last element of this vector.
+ [[nodiscard]] constexpr auto cend() const noexcept -> const_pointer
{
return end();
}
- /**
- * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It
- * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Reverse iterator to the element following the last element.
- */
- auto rend() noexcept -> pointer
+ //! Get a reverse iterator to the reverse beginning.
+ [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator
{
- return _data + size() - 1;
+ return empty() ? rend() : reverse_iterator{begin() + (m_size - 1)};
}
- /**
- * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It
- * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Reverse iterator to the element following the last element.
- */
- [[nodiscard]] auto rend() const noexcept -> const_pointer
+ //! Get a reverse iterator to the reverse beginning.
+ [[nodiscard]] constexpr auto rbegin() const noexcept -> const_reverse_iterator
{
- return _data + size() - 1;
+ return empty() ? rend() : const_reverse_iterator{begin() + (m_size - 1)};
}
- /**
- * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It
- * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Reverse iterator to the element following the last element.
- */
- [[nodiscard]] auto crend() const noexcept -> const_pointer
+ //! Get a reverse iterator to the reverse beginning.
+ [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator
{
return rbegin();
}
- /**
- * @brief Returns a pointer to the underlying array serving as element storage. The pointer is such that range
- * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not dereferenceable
- * in that case).
- *
- * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares equal
- * to the address of the first element.
- */
- auto data() -> pointer
+ //! Get a reverse iterator to the reverse end.
+ [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator
{
- return _data;
+ return reverse_iterator{capacity() ? data() - 1 : nullptr};
}
- /**
- * @brief Returns a pointer to the underlying array serving as element storage. The pointer is such that range
- * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not dereferenceable
- * in that case).
- *
- * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares equal
- * to the address of the first element.
- */
- [[nodiscard]] auto data() const -> const_pointer
+ //! Get a reverse iterator to the reverse end.
+ [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator
{
- return _data;
+ return const_reverse_iterator{capacity() ? data() - 1 : nullptr};
}
- /**
- * @brief Returns a reference to the first element in the container. Calling front on an empty container causes
- * undefined behavior.
- *
- * @return Reference to the first element.
- */
- auto front() -> reference
+ //! Get a reverse iterator to the reverse end.
+ [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator
{
- throw_if_empty();
- return *begin();
+ return rend();
}
- /**
- * @brief Returns a reference to the first element in the container. Calling front on an empty container causes
- * undefined behavior.
- *
- * @return Reference to the first element.
- */
- [[nodiscard]] auto front() const -> const_reference
+ //! Check whether this vector is empty.
+ [[nodiscard]] constexpr auto empty() const noexcept