Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ystdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_subdirectory(container)
add_subdirectory(error_handling)
122 changes: 122 additions & 0 deletions src/ystdlib/container/Array.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#ifndef YSTDLIB_CONTAINER_ARRAY
#define YSTDLIB_CONTAINER_ARRAY

#include <algorithm>
#include <concepts>
#include <cstddef>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <stdexcept>
#include <type_traits>

namespace ystdlib::container {
/**
* Class for a runtime fix-sized array.
* @tparam T The type of elements in the array. The type must be default initializable so that this
* class doesn't need to implement special memory allocation procedure for std::unique_ptr<T[]>.
* Note that when using constructor that takes an initializer list, T must be copy constructable.
*/
template <typename T>
requires(std::is_fundamental_v<T> || std::default_initializable<T>)
class Array {
public:
// Types
using Iterator = T*;
using ConstIterator = T const*;

// Constructors
// NOLINTNEXTLINE(*-avoid-c-arrays)
explicit Array(size_t size) : m_data{std::make_unique<T[]>(size)}, m_size{size} {
if constexpr (std::is_fundamental_v<T>) {
std::fill_n(m_data.get(), m_size, T{});
}
}

Array(std::initializer_list<T> list)
// NOLINTNEXTLINE(*-avoid-c-arrays)
: m_data{std::make_unique<T[]>(list.size())},
m_size{list.size()} {
std::ranges::copy(list, m_data.get());
}

// Disable copy constructor and assignment operator
Array(Array const&) = delete;
auto operator=(Array const&) -> Array& = delete;

// Default move constructor and assignment operator
Array(Array&&) = default;
auto operator=(Array&&) -> Array& = default;

// Destructor
~Array() = default;

// Methods
/**
* @return Whether the array is empty.
*/
[[nodiscard]] auto empty() const -> bool { return 0 == size(); }

/**
* @return The size of the array.
*/
[[nodiscard]] auto size() const -> size_t { return m_size; }

/**
* @return A pointer to the underlying data buffer.
*/
[[nodiscard]] auto data() -> T* { return m_data.get(); }

/**
* @return A pointer to the underlying data buffer.
*/
[[nodiscard]] auto data() const -> T const* { return m_data.get(); }

/**
* @param idx
* @return The element at the given index.
* @throw `std::out_of_range` if the given index is out of bound.
*/
[[nodiscard]] auto at(size_t idx) -> T& {
assert_is_in_range(idx);
return m_data[idx];
}

/**
* @param idx
* @return The element at the given index.
* @throw `std::out_of_range` if the given index is out of bound.
*/
[[nodiscard]] auto at(size_t idx) const -> T const& {
assert_is_in_range(idx);
return m_data[idx];
}

[[nodiscard]] auto begin() -> Iterator { return m_data.get(); }

[[nodiscard]] auto end() -> Iterator { return m_data.get() + m_size; }

[[nodiscard]] auto begin() const -> ConstIterator { return m_data.get(); }

[[nodiscard]] auto end() const -> ConstIterator { return m_data.get() + m_size; }

private:
/**
* @param idx
* @throw `std::out_of_range` if the given index is out of bound.
*/
auto assert_is_in_range(size_t idx) const -> void {
if (idx >= m_size) {
// TODO: Add ErrorCode class for `stdexcept` errors and switch to use TraceableException
throw std::out_of_range("ystdlib::container::Array out-of-range access.");
}
}

// Variables
// NOLINTNEXTLINE(*-avoid-c-arrays)
std::unique_ptr<T[]> m_data;
size_t m_size;
};
} // namespace ystdlib::container

#endif // YSTDLIB_CONTAINER_ARRAY
1 change: 1 addition & 0 deletions src/ystdlib/container/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cpp_library(NAME container NAMESPACE ystdlib TESTS_SOURCES test/test_Array.cpp)
135 changes: 135 additions & 0 deletions src/ystdlib/container/test/test_Array.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include <algorithm>
#include <cstddef>
#include <ranges>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>

#include <ystdlib/container/Array.hpp>

#include <catch2/catch_test_macros.hpp>

namespace {
constexpr size_t cBufferSize{1024};

class NoDefault {
public:
NoDefault(size_t n) : m_n{n} {}

[[nodiscard]] auto get() const -> size_t { return m_n; }

private:
size_t m_n;
};

class VarietyConstructors {
public:
static constexpr size_t cDefault = 3190;

VarietyConstructors() = default;

VarietyConstructors(size_t n) : m_n{n} {}

VarietyConstructors(std::string const& s) : m_n{s.size()} {}

VarietyConstructors(NoDefault const& nd) : m_n{nd.get()} {}

[[nodiscard]] auto get() const -> size_t { return m_n; }

private:
size_t m_n{cDefault};
};

void test_function_argument_list_initialization(
ystdlib::container::Array<VarietyConstructors> const& arr,
std::vector<size_t> const& data
) {
REQUIRE(std::ranges::equal(
arr | std::views::transform([](auto const& obj) { return obj.get(); }),
data
));
}
} // namespace

namespace ystdlib::container::test {
TEST_CASE("test_empty_array", "[container][Array]") {
Array<int> arr(0);
REQUIRE(arr.empty());
// NOLINTNEXTLINE(readability-container-size-empty)
REQUIRE((0 == arr.size()));
REQUIRE((arr.begin() == arr.end()));
}

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
TEST_CASE("test_array_basic", "[container][Array]") {
std::vector<int> vec;
for (int i{0}; i < cBufferSize; ++i) {
vec.push_back(i);
}

Array<int> arr(cBufferSize);
auto const& arr_const_ref = arr;
std::ranges::copy(vec, arr.begin());

REQUIRE(std::ranges::equal(vec, arr));
REQUIRE(std::ranges::equal(vec, arr_const_ref));

REQUIRE_THROWS(arr.at(cBufferSize));
REQUIRE_THROWS(arr_const_ref.at(cBufferSize));

auto const arr_moved{std::move(arr)};
REQUIRE(std::ranges::equal(vec, arr_moved));
REQUIRE_THROWS(arr_moved.at(cBufferSize));
}

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
TEST_CASE("test_array_default_initialization", "[container][Array]") {
Array<int> int_arr(cBufferSize);
auto const& int_arr_const_ref = int_arr;

REQUIRE(std::is_fundamental_v<int>);
std::ranges::for_each(int_arr, [](auto const& i) -> void { REQUIRE((0 == i)); });
std::ranges::for_each(int_arr_const_ref, [](auto const& i) -> void { REQUIRE((0 == i)); });

Array<std::nullptr_t> ptr_arr(cBufferSize);
auto const& ptr_arr_const_ref = ptr_arr;

REQUIRE(std::is_fundamental_v<std::nullptr_t>);
std::ranges::for_each(ptr_arr, [](auto const& p) -> void { REQUIRE((nullptr == p)); });
std::ranges::for_each(ptr_arr_const_ref, [](auto const& p) -> void {
REQUIRE((nullptr == p));
});
}

TEST_CASE("test_array_list_initialization_basic", "[container][Array]") {
std::vector<std::string> const
vec{"yscope", "clp", "ystdlib", "ystdlib::container::Array", "default_initializable"};
Array<std::string> const
arr0{"yscope", "clp", "ystdlib", "ystdlib::container::Array", "default_initializable"};
Array<std::string> const arr1
= {"yscope", "clp", "ystdlib", "ystdlib::container::Array", "default_initializable"};
REQUIRE(std::ranges::equal(vec, arr0));
REQUIRE(std::ranges::equal(vec, arr1));
}

TEST_CASE("test_array_list_initialization_variety_constructors", "[container][Array]") {
constexpr size_t cTestNum0{344};
constexpr size_t cTestNum1{454};
constexpr std::string_view cTestStr{"yscope"};

Array<VarietyConstructors> const arr
= {VarietyConstructors{}, cTestNum0, std::string{cTestStr}, NoDefault{cTestNum1}};
std::vector<size_t> const data
= {VarietyConstructors::cDefault, cTestNum0, cTestStr.size(), cTestNum1};
REQUIRE(std::ranges::equal(
arr | std::views::transform([](auto const& obj) { return obj.get(); }),
data
));
test_function_argument_list_initialization(
{VarietyConstructors{}, cTestNum0, std::string{cTestStr}, NoDefault{cTestNum1}},
data
);
}
} // namespace ystdlib::container::test
1 change: 1 addition & 0 deletions taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ vars:
G_TEST_BIN_DIR: "{{.G_BUILD_DIR}}/testbin"
G_TEST_TARGET_SUFFIXES:
- "all"
- "container"
- "error_handling"

tasks:
Expand Down