diff --git a/libs/util/include/psemek/util/array.hpp b/libs/util/include/psemek/util/array.hpp new file mode 100644 index 00000000..46897985 --- /dev/null +++ b/libs/util/include/psemek/util/array.hpp @@ -0,0 +1,279 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +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 + 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 + array(Iterator begin, Iterator end); + + array(std::initializer_list 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 copy() const; + + private: + std::unique_ptr data_ = nullptr; + std::size_t size_ = 0; + + static std::unique_ptr allocate(std::size_t size); + void check_index(std::size_t index); + }; + + template + array::array(std::size_t size) + : data_(allocate(size)) + , size_(size) + {} + + template + array::array(std::size_t size, T const & value) + : data_(allocate(size)) + , size_(size) + { + fill(value); + } + + template + template + array::array(Iterator begin, Iterator end) + { + size_ = std::distance(begin, end); + data_ = allocate(size_); + std::copy(begin, end, data_.get()); + } + + template + array::array(std::initializer_list const & list) + : data_(allocate(list.size())) + , size_(list.size()) + { + std::copy(list.begin(), list.end(), begin()); + } + + template + array::array(array && other) + : data_(std::move(other.data_)) + , size_(other.size_) + { + other.size_ = 0; + } + + template + array & array::operator = (array && other) + { + data_ = std::move(other.data_); + size_ = other.size_; + return *this; + } + + template + std::size_t array::size() const + { + return size_; + } + + template + bool array::empty() + { + return size_ == 0; + } + + template + T * array::data() + { + return data_.get(); + } + + template + T const * array::data() const + { + return data_.get(); + } + + template + void array::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 + void array::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 + void array::clear() + { + data_.reset(); + size_ = 0; + } + + template + void array::fill(T const & value) + { + std::fill(begin(), end(), value); + } + + template + T & array::operator[] (std::size_t index) + { + return data_[index]; + } + + template + T const & array::operator[] (std::size_t index) const + { + return data_[index]; + } + + template + T & array::at(std::size_t index) + { + check_index(index); + return data_[index]; + } + + template + T const & array::at(std::size_t index) const + { + check_index(index); + return data_[index]; + } + + template + T * array::begin() + { + return data_.get(); + } + + template + T * array::end() + { + return data_.get() + size_; + } + + template + T const * array::begin() const + { + return data_.get(); + } + + template + T const * array::end() const + { + return data_.get() + size_; + } + + template + T const * array::cbegin() const + { + return begin(); + } + + template + T const * array::cend() const + { + return end(); + } + + template + array array::copy() const + { + return array(begin(), end()); + } + + template + std::unique_ptr array::allocate(std::size_t size) + { + return size == 0 ? nullptr : std::make_unique_for_overwrite(size); + } + + template + void array::check_index(std::size_t index) + { + if (index >= size_) + throw array_index_out_of_bounds(index, size_); + } + +} diff --git a/libs/util/tests/array.cpp b/libs/util/tests/array.cpp new file mode 100644 index 00000000..f4843a23 --- /dev/null +++ b/libs/util/tests/array.cpp @@ -0,0 +1,165 @@ +#include + +#include + +using namespace psemek::util; + +test_case(util_array_empty) +{ + array a; + expect(a.empty()); + expect_equal(a.size(), 0); + expect_equal_ptr(a.data(), nullptr); +} + +test_case(util_array_ctor_size) +{ + array 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 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 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 a(16); + for (int i = 0; i < a.size(); ++i) + a[i] = i; + + array 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 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 a(16); + a.clear(); + + expect(a.empty()); + expect_equal(a.size(), 0); + expect_equal_ptr(a.data(), nullptr); +} + +test_case(util_array_fill) +{ + array a(16); + a.fill(42); + + for (int i = 0; i < a.size(); ++i) + expect_equal(a[i], 42); +} + +test_case(util_array_index) +{ + array a(16); + + for (int i = 0; i < a.size(); ++i) + expect_equal_ptr(&a[i], a.data() + i); +} + +test_case(util_array_at) +{ + array 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 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 const b(a.begin(), a.end()); + i = 0; + for (auto v : b) + expect_equal(a[i++], v); +} + +test_case(util_array_copy) +{ + array 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]); +}