aboutsummaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-03-19 13:00:08 +0100
committerFelix Morgner <felix.morgner@ost.ch>2026-03-19 13:00:08 +0100
commitae2a264b117ecf556f742d8e9c357f906cb3fd83 (patch)
tree46d655184fb672d461126968efc920b296ed8e42 /libs
parentcc55324b0f3a988befaecff15e0ff87e3c6a4dee (diff)
downloadteachos-ae2a264b117ecf556f742d8e9c357f906cb3fd83.tar.xz
teachos-ae2a264b117ecf556f742d8e9c357f906cb3fd83.zip
kstd: finish preliminary vector implementation
Diffstat (limited to 'libs')
-rw-r--r--libs/kstd/include/kstd/ranges21
-rw-r--r--libs/kstd/include/kstd/vector810
2 files changed, 401 insertions, 430 deletions
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 capacity of the container; to that end shrink_to_fit() is provided.
- *
- * @param new_capacity New capacity of the vector, in number of elements
- */
- auto reserve(size_type new_capacity) -> void
- {
- if (new_capacity <= m_capacity)
+ //! Reserve storage for at list the given number of elements.
+ constexpr auto reserve(size_type new_capacity) -> void
+ {
+ if (new_capacity <= capacity())
+ {
+ return;
+ }
+
+ if (new_capacity > max_size())
{
+ kstd::os::panic("[kstd:vector] Tried to reserve more space than theoretically possible.");
return;
}
+ auto new_data = allocate_n(new_capacity);
+ auto old_size = size();
+ for (auto i = 0uz; i < old_size; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, new_data + i, std::move((*this)[i