Add util::array - a dynamic array without auto-expansion
This commit is contained in:
parent
b99a4da18f
commit
e40467a8d1
2 changed files with 444 additions and 0 deletions
279
libs/util/include/psemek/util/array.hpp
Normal file
279
libs/util/include/psemek/util/array.hpp
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/util/exception.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include <format>
|
||||
#include <initializer_list>
|
||||
#include <concepts>
|
||||
|
||||
namespace psemek::util
|
||||
{
|
||||
|
||||
struct array_index_out_of_bounds
|
||||
: util::exception
|
||||
{
|
||||
array_index_out_of_bounds(std::size_t index, std::size_t size, util::stacktrace stacktrace = {})
|
||||
: util::exception(std::format("Array index ({}) out of bounds ({})", index, size), std::move(stacktrace))
|
||||
{}
|
||||
};
|
||||
|
||||
// Like std::vector, but without auto-reallocation and implicit copying
|
||||
template <typename T>
|
||||
struct array
|
||||
{
|
||||
using value_type = T;
|
||||
using reference_type = T &;
|
||||
using pointer_type = T *;
|
||||
using iterator = T *;
|
||||
using const_iterator = T const *;
|
||||
using size_type = std::size_t;
|
||||
|
||||
array() = default;
|
||||
|
||||
explicit array(std::size_t size);
|
||||
array(std::size_t size, T const & value);
|
||||
|
||||
template <std::input_iterator Iterator>
|
||||
array(Iterator begin, Iterator end);
|
||||
|
||||
array(std::initializer_list<T> const & list);
|
||||
|
||||
array(array const & other) = delete;
|
||||
array(array && other);
|
||||
|
||||
array & operator = (array const & other) = delete;
|
||||
array & operator = (array && other);
|
||||
|
||||
std::size_t size() const;
|
||||
bool empty();
|
||||
|
||||
T * data();
|
||||
T const * data() const;
|
||||
|
||||
void resize(std::size_t size);
|
||||
void resize(std::size_t size, T const & value);
|
||||
void clear();
|
||||
|
||||
void fill(T const & value);
|
||||
|
||||
T & operator[] (std::size_t index);
|
||||
T const & operator[] (std::size_t index) const;
|
||||
|
||||
T & at(std::size_t index);
|
||||
T const & at(std::size_t index) const;
|
||||
|
||||
T * begin();
|
||||
T * end();
|
||||
|
||||
T const * begin() const;
|
||||
T const * end() const;
|
||||
|
||||
T const * cbegin() const;
|
||||
T const * cend() const;
|
||||
|
||||
array<T> copy() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<T[]> data_ = nullptr;
|
||||
std::size_t size_ = 0;
|
||||
|
||||
static std::unique_ptr<T[]> allocate(std::size_t size);
|
||||
void check_index(std::size_t index);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
array<T>::array(std::size_t size)
|
||||
: data_(allocate(size))
|
||||
, size_(size)
|
||||
{}
|
||||
|
||||
template <typename T>
|
||||
array<T>::array(std::size_t size, T const & value)
|
||||
: data_(allocate(size))
|
||||
, size_(size)
|
||||
{
|
||||
fill(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <std::input_iterator Iterator>
|
||||
array<T>::array(Iterator begin, Iterator end)
|
||||
{
|
||||
size_ = std::distance(begin, end);
|
||||
data_ = allocate(size_);
|
||||
std::copy(begin, end, data_.get());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
array<T>::array(std::initializer_list<T> const & list)
|
||||
: data_(allocate(list.size()))
|
||||
, size_(list.size())
|
||||
{
|
||||
std::copy(list.begin(), list.end(), begin());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
array<T>::array(array && other)
|
||||
: data_(std::move(other.data_))
|
||||
, size_(other.size_)
|
||||
{
|
||||
other.size_ = 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
array<T> & array<T>::operator = (array<T> && other)
|
||||
{
|
||||
data_ = std::move(other.data_);
|
||||
size_ = other.size_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t array<T>::size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool array<T>::empty()
|
||||
{
|
||||
return size_ == 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T * array<T>::data()
|
||||
{
|
||||
return data_.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T const * array<T>::data() const
|
||||
{
|
||||
return data_.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void array<T>::resize(std::size_t size)
|
||||
{
|
||||
if (size == size_) return;
|
||||
|
||||
auto new_data = allocate(size);
|
||||
std::copy_n(data_.get(), std::min(size, size_), new_data.get());
|
||||
|
||||
data_ = std::move(new_data);
|
||||
size_ = size;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void array<T>::resize(std::size_t size, T const & value)
|
||||
{
|
||||
if (size == size_) return;
|
||||
|
||||
auto min_size = std::min(size, size_);
|
||||
|
||||
auto new_data = allocate(size);
|
||||
std::copy_n(data_.get(), min_size, new_data.get());
|
||||
std::fill_n(new_data.get() + min_size, size - min_size, value);
|
||||
|
||||
data_ = std::move(new_data);
|
||||
size_ = size;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void array<T>::clear()
|
||||
{
|
||||
data_.reset();
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void array<T>::fill(T const & value)
|
||||
{
|
||||
std::fill(begin(), end(), value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T & array<T>::operator[] (std::size_t index)
|
||||
{
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T const & array<T>::operator[] (std::size_t index) const
|
||||
{
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T & array<T>::at(std::size_t index)
|
||||
{
|
||||
check_index(index);
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T const & array<T>::at(std::size_t index) const
|
||||
{
|
||||
check_index(index);
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T * array<T>::begin()
|
||||
{
|
||||
return data_.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T * array<T>::end()
|
||||
{
|
||||
return data_.get() + size_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T const * array<T>::begin() const
|
||||
{
|
||||
return data_.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T const * array<T>::end() const
|
||||
{
|
||||
return data_.get() + size_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T const * array<T>::cbegin() const
|
||||
{
|
||||
return begin();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T const * array<T>::cend() const
|
||||
{
|
||||
return end();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
array<T> array<T>::copy() const
|
||||
{
|
||||
return array<T>(begin(), end());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::unique_ptr<T[]> array<T>::allocate(std::size_t size)
|
||||
{
|
||||
return size == 0 ? nullptr : std::make_unique_for_overwrite<T[]>(size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void array<T>::check_index(std::size_t index)
|
||||
{
|
||||
if (index >= size_)
|
||||
throw array_index_out_of_bounds(index, size_);
|
||||
}
|
||||
|
||||
}
|
||||
165
libs/util/tests/array.cpp
Normal file
165
libs/util/tests/array.cpp
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
#include <psemek/test/test.hpp>
|
||||
|
||||
#include <psemek/util/array.hpp>
|
||||
|
||||
using namespace psemek::util;
|
||||
|
||||
test_case(util_array_empty)
|
||||
{
|
||||
array<int> a;
|
||||
expect(a.empty());
|
||||
expect_equal(a.size(), 0);
|
||||
expect_equal_ptr(a.data(), nullptr);
|
||||
}
|
||||
|
||||
test_case(util_array_ctor_size)
|
||||
{
|
||||
array<int> a(16, 42);
|
||||
expect(!a.empty());
|
||||
expect_equal(a.size(), 16);
|
||||
expect_different_ptr(a.data(), nullptr);
|
||||
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
expect_equal(a[i], 42);
|
||||
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
a[i] = i;
|
||||
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
expect_equal(a[i], i);
|
||||
}
|
||||
|
||||
test_case(util_array_ctor_range)
|
||||
{
|
||||
int data[8] = {2, 3, 5, 7, 11, 13, 17, 19};
|
||||
|
||||
array<int> a(std::begin(data), std::end(data));
|
||||
expect(!a.empty());
|
||||
expect_equal(a.size(), std::size(data));
|
||||
expect_different_ptr(a.data(), nullptr);
|
||||
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
expect_equal(a[i], data[i]);
|
||||
}
|
||||
|
||||
test_case(util_array_ctor_initializer__list)
|
||||
{
|
||||
int data[8] = {2, 3, 5, 7, 11, 13, 17, 19};
|
||||
|
||||
array<int> a{2, 3, 5, 7, 11, 13, 17, 19};
|
||||
expect(!a.empty());
|
||||
expect_equal(a.size(), std::size(data));
|
||||
expect_different_ptr(a.data(), nullptr);
|
||||
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
expect_equal(a[i], data[i]);
|
||||
}
|
||||
|
||||
test_case(util_array_ctor_move)
|
||||
{
|
||||
array<int> a(16);
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
a[i] = i;
|
||||
|
||||
array<int> b = std::move(a);
|
||||
|
||||
expect(a.empty());
|
||||
expect_equal(a.size(), 0);
|
||||
expect_equal_ptr(a.data(), nullptr);
|
||||
expect(!b.empty());
|
||||
expect_equal(b.size(), 16);
|
||||
expect_different_ptr(b.data(), nullptr);
|
||||
|
||||
for (int i = 0; i < b.size(); ++i)
|
||||
expect_equal(b[i], i);
|
||||
}
|
||||
|
||||
test_case(util_array_resize)
|
||||
{
|
||||
array<int> a(16);
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
a[i] = i;
|
||||
|
||||
a.resize(32);
|
||||
for (int i = 0; i < 16; ++i)
|
||||
expect_equal(a[i], i);
|
||||
|
||||
a.resize(8);
|
||||
for (int i = 0; i < 8; ++i)
|
||||
expect_equal(a[i], i);
|
||||
}
|
||||
|
||||
test_case(util_array_clear)
|
||||
{
|
||||
array<int> a(16);
|
||||
a.clear();
|
||||
|
||||
expect(a.empty());
|
||||
expect_equal(a.size(), 0);
|
||||
expect_equal_ptr(a.data(), nullptr);
|
||||
}
|
||||
|
||||
test_case(util_array_fill)
|
||||
{
|
||||
array<int> a(16);
|
||||
a.fill(42);
|
||||
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
expect_equal(a[i], 42);
|
||||
}
|
||||
|
||||
test_case(util_array_index)
|
||||
{
|
||||
array<int> a(16);
|
||||
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
expect_equal_ptr(&a[i], a.data() + i);
|
||||
}
|
||||
|
||||
test_case(util_array_at)
|
||||
{
|
||||
array<int> a(16);
|
||||
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
expect_equal_ptr(&a.at(i), a.data() + i);
|
||||
|
||||
for (int i = a.size(); i < a.size() + 16; ++i)
|
||||
expect_throw(a.at(i), array_index_out_of_bounds);
|
||||
}
|
||||
|
||||
test_case(util_array_iterator)
|
||||
{
|
||||
array<int> a(16);
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
a[i] = i;
|
||||
|
||||
int i = 0;
|
||||
for (auto v : a)
|
||||
expect_equal(a[i++], v);
|
||||
|
||||
i = 0;
|
||||
for (auto & v : a)
|
||||
v = 42 * (i++);
|
||||
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
expect_equal(a[i], 42 * i);
|
||||
|
||||
array<int> const b(a.begin(), a.end());
|
||||
i = 0;
|
||||
for (auto v : b)
|
||||
expect_equal(a[i++], v);
|
||||
}
|
||||
|
||||
test_case(util_array_copy)
|
||||
{
|
||||
array<int> a(16);
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
a[i] = i;
|
||||
|
||||
auto b = a.copy();
|
||||
expect_equal(a.size(), b.size());
|
||||
expect_different_ptr(a.data(), b.data());
|
||||
|
||||
for (int i = 0; i < b.size(); ++i)
|
||||
expect_equal(a[i], b[i]);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue