diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-03-19 13:00:08 +0100 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-03-19 13:00:08 +0100 |
| commit | ae2a264b117ecf556f742d8e9c357f906cb3fd83 (patch) | |
| tree | 46d655184fb672d461126968efc920b296ed8e42 | |
| parent | cc55324b0f3a988befaecff15e0ff87e3c6a4dee (diff) | |
| download | teachos-ae2a264b117ecf556f742d8e9c357f906cb3fd83.tar.xz teachos-ae2a264b117ecf556f742d8e9c357f906cb3fd83.zip | |
kstd: finish preliminary vector implementation
| -rw-r--r-- | kapi/include/kapi/boot_module/boot_module_registry.hpp | 4 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/ranges | 21 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/vector | 810 |
3 files changed, 403 insertions, 432 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/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 b87756a..9709c2a 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -1,14 +1,19 @@ #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 @@ -20,7 +25,7 @@ namespace kstd * @tparam T Element the vector instance should contain. * @tparam Allocator The allocator to use when allocating new elements. */ - template<typename T, typename Allocator = std::allocator<T>> + template<typename T, typename Allocator = kstd::allocator<T>> struct vector { using value_type = T; ///< Type of the elements contained in the container. @@ -37,20 +42,51 @@ namespace kstd using const_reverse_iterator = std::reverse_iterator<const_iterator>; //! Construct a new, empty vector. - vector() = default; + 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, with the given number of element and initialize them to the given value. + //! Construct a new vector and fill it with the given number of copy constructed elements. //! - //! @param n The number of elements to construct the container with. - //! @param initial The value to initialize each element to. - explicit vector(size_type n, value_type initial = value_type{}) - : m_size{n} - , m_capacity{n} + //! @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 < n; ++i) + for (auto i = 0uz; i < count; ++i) { - std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i, initial); + std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i, value); } } @@ -59,66 +95,103 @@ namespace kstd //! @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<typename InputIterator> - explicit vector(InputIterator first, InputIterator last) - : m_allocator{} + 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{std::ranges::distance(first, last)} + , m_capacity{m_size} , m_data{allocate_n(m_capacity)} { - for (auto destination = begin(); first != last; ++first, ++destination) + 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 initializer list. + //! Construct a new vector and initialize it's content by copying all elements in the given range. //! - //! @param list The initializer list containing the source objects. - explicit vector(std::initializer_list<value_type> list) - : vector{std::ranges::begin(list), std::ranges::end(list)} - {} + //! + 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. - vector(vector const & other) + 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)) { - auto first = std::ranges::begin(other); - auto last = std::ranges::end(other); - for (auto destination = begin(); first != last; ++first, ++destination) - { - std::allocator_traits<allocator_type>::construct(m_allocator, destination, *first); - } + 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. - vector(vector && other) noexcept - : m_allocator{std::exchange(other.m_allocator, allocator_type{})} + 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. - ~vector() + constexpr ~vector() { - std::ranges::for_each(std::views::reverse(*this), [&](auto & element) { - std::allocator_traits<allocator_type>::destroy(m_allocator, std::addressof(element)); - }); - std::allocator_traits<allocator_type>::deallocate(m_allocator, m_data, m_capacity); + clear_and_deallocate(); } //! Replace the contents of this vector by the copying from the given source vector. //! //! @param other The source vector. - auto operator=(vector const & other) -> vector<value_type> & + constexpr auto operator=(vector const & other) -> vector<value_type> & { if (this == std::addressof(other)) { @@ -127,58 +200,49 @@ namespace kstd if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value) { - if (m_allocator != other.m_allocator) + if (get_allocator() != other.get_allocator()) { clear_and_deallocate(); } - m_allocator = other.m_allocator; + m_allocator = other.get_allocator(); } - if (m_capacity >= other.m_size) + if (capacity() >= other.size()) { auto const overlap = std::min(m_size, other.m_size); - auto first = std::ranges::begin(other); - for (auto i = 0uz; i < overlap; ++first, ++i) - { - m_data[i] = *first; - } + copy_elements(other.begin(), begin(), overlap); if (m_size < other.m_size) { - for (auto i = m_size; i < other.m_size; ++first, ++i) - { - std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i, *first); - } + uninitialized_copy_with_allocator(other.begin() + size(), begin() + size(), other.m_size - size()); } else if (m_size > other.m_size) { - for (auto i = other.m_size; i < m_size; ++i) - { - std::allocator_traits<allocator_type>::destroy(m_allocator, m_data + i); - } + destroy_n(begin() + other.size(), size() - other.size()); } } else { - auto new_data = std::allocator_traits<allocator_type>::allocate(m_allocator, other.m_size); - for (auto i = 0uz; i < other.m_size; ++i) - { - std::allocator_traits<allocator_type>::construct(m_allocator, new_data + i, other.m_data[i]); - } + 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(); - std::exchange(m_data, new_data); - m_capacity = other.m_size; + m_data = new_data; + m_capacity = other.size(); } - m_size = other.m_size; + m_size = other.size(); return *this; } //! Replace the contents fo this vector by moving from the given source. //! //! @param other The source vector. - auto operator=(vector && other) noexcept -> vector<value_type> & + 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> & { + using std::swap; + if (this == std::addressof(other)) { return *this; @@ -187,478 +251,338 @@ namespace kstd if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value) { clear_and_deallocate(); - m_allocator = std::move(other.m_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); + 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) + else if (m_allocator == other.m_allocator) { clear_and_deallocate(); - 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); + swap(m_size, other.m_size); + swap(m_capacity, other.m_capacity); + swap(m_data, other.m_data); } else { - if (m_capacity >= other.m_size) + if (capacity() >= other.size()) { - auto const overlap = std::min(m_size, other.m_size); - auto first = std::ranges::begin(other); - for (auto i = 0uz; i < overlap; ++first, ++i) - { - m_data[i] = std::move(*first); - } + auto const overlap = std::min(size(), other.size()); + move_elements(other.begin(), begin(), overlap); - if (m_size < other.m_size) + if (size() < other.size()) { - for (auto i = m_size; i < other.m_size; ++first, ++i) - { - std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i, std::move(*first)); - } + uninitialized_move_with_allocator(other.begin() + size(), begin() + size(), other.size() - size()); } - else if (m_size > other.m_size) + else if (size() > other.size()) { - for (auto i = other.m_size; i < m_size; ++i) - { - std::allocator_traits<allocator_type>::destroy(m_allocator, m_data + i); - } + destroy_n(begin() + other.size(), size() - other.size()); } } else { - auto new_data = std::allocator_traits<allocator_type>::allocate(m_allocator, other.m_size); - for (auto i = 0uz; i < other.m_size; ++i) - { - std::allocator_traits<allocator_type>::destroy(m_allocator, m_data + i); - } + 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(); - std::exchange(m_data, new_data); + m_data = new_data; m_capacity = other.m_size; } - for (auto i = 0uz; i < other.m_size; ++i) - { - std::allocator_traits<allocator_type>::destroy(other.m_allocator, other.m_data + i); - } + + other.destroy_n(other.begin(), other.size()); m_size = std::exchange(other.m_size, size_type{}); } return *this; } - /** - * @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 + //! 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 { - return m_size; + return m_allocator; } - /** - * @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 + //! 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 { - return m_capacity; + panic_if_out_of_bounds(index); + return (*this)[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) -> reference - { - return m_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 m_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); + //! 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 + { + panic_if_out_of_bounds(index); return (*this)[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 + //! 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 { - throw_if_out_of_range(index); - return (*this)[index]; + return data()[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 + //! 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 { - increase_capacity_if_full(); - std::allocator_traits<allocator_type>::construct(m_allocator, &(*this)[m_size], std::forward<U>(value)); - ++m_size; + return data()[index]; } - /** - * @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 & + //! Get a reference to the first element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] constexpr auto front() -> reference { - increase_capacity_if_full(); - std::allocator_traits<allocator_type>::construct(m_allocator, &(*this)[m_size], std::forward<Args>(args)...); - ++m_size; - return this->back(); + return *begin(); } - /** - * @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 first element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] auto front() const -> const_reference { - throw_if_empty(); - (void)m_size--; + return *begin(); } - /** - * @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 last element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] constexpr auto back() -> reference { - return m_data; + 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(); } - /** - * @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 pointer to the beginning of the underlying contiguous storage. + [[nodiscard]] constexpr auto data() noexcept -> pointer { return m_data; } - /** - * @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 pointer to the beginning of the underlying contiguous storage. + [[nodiscard]] constexpr auto data() const noexcept -> const_pointer { - return begin(); + return m_data; } - /** - * @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 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 m_data + m_size - 1; + return empty() ? end() : data(); } - /** - * @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 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 m_data + m_size - 1; + return empty() ? end() : data(); } - /** - * @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 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 rbegin(); + 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 m_data + m_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 m_data + m_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 m_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 m_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 m_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 m_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 -> bool { - throw_if_empty(); - return *begin(); + return !size(); } - /** - * @brief Returns a reference to the last element in the container. Calling back on an empty container causes - * undefined behavior. - * - * @return Reference to the last element. - */ - auto back() -> reference + //! Get the number of elements present in this vector. + [[nodiscard]] constexpr auto size() const noexcept -> size_type { - throw_if_empty(); - return *rbegin(); + return m_size; } - /** - * @brief Returns a reference to the last element in the container. Calling back on an empty container causes - * undefined behavior. - * - * @return Reference to the last element. - */ - [[nodiscard]] auto back() const -> const_reference + //! Get the maximum possible number of element for this vector. + [[nodiscard]] constexpr auto max_size() const noexcept -> size_type { - throw_if_empty(); - return *rbegin(); + return std::allocator_traits<allocator_type>::max_size(m_allocator); } - /** - * @brief Increase the capacity of the vector (the total number of elements that the vector can hold without - * requiring reallocation) to a value that's greater or equal to new_cap. If new_cap is greater than the current - * capacity(), new storage is allocated, otherwise the function does nothing. - * - * reserve() does not change the size of the vector. - * - * If new_cap is greater than capacity(), all iterators (including the end() iterator) and all references to the - * elements are invalidated. Otherwise, no iterators or references are invalidated. - * - * After a call to reserve(), insertions will not trigger reallocation unless the insertion would make the size of - * the vector greater than the value of capacity(). - * - * @note Correctly using reserve() can prevent unnecessary reallocations, but inappropriate uses of reserve() (for - * instance, calling it before every push_back() call) may actually increase the number of reallocations (by - * causing the capacity to grow linearly rather than exponentially) and result in increased computational - * complexity and decreased performance. For example, a function that receives an arbitrary vector by reference - * and appends elements to it should usually not call reserve() on the vector, since it does not know of the - * vector's usage characteristics. - * - * When inserting a range, the range version of insert() is generally preferable as it preserves the correct - * capacity growth behavior, unlike reserve() followed by a series of push_back()s. - * - * reserve() cannot be used to reduce the ca |
