From 3ab0a15fb6aba0ad9516da69589b9da8dbd63a8e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sat, 2 May 2026 17:12:21 +0200 Subject: libs: adopt p1204 layout for kstd --- libs/kstd/CMakeLists.txt | 34 +- libs/kstd/include/kstd/allocator | 64 - libs/kstd/include/kstd/asm_ptr | 78 - libs/kstd/include/kstd/bits/concepts.hpp | 15 - libs/kstd/include/kstd/bits/flat_map.hpp | 186 -- libs/kstd/include/kstd/bits/format/arg.hpp | 75 - libs/kstd/include/kstd/bits/format/args.hpp | 160 -- libs/kstd/include/kstd/bits/format/context.hpp | 65 - libs/kstd/include/kstd/bits/format/error.hpp | 24 - libs/kstd/include/kstd/bits/format/formatter.hpp | 92 - .../include/kstd/bits/format/formatter/bool.hpp | 83 - .../include/kstd/bits/format/formatter/byte.hpp | 23 - .../include/kstd/bits/format/formatter/char.hpp | 94 - .../include/kstd/bits/format/formatter/cstring.hpp | 29 - .../kstd/bits/format/formatter/integral.hpp | 204 -- .../kstd/bits/format/formatter/ordering.hpp | 111 -- .../include/kstd/bits/format/formatter/pointer.hpp | 42 - .../include/kstd/bits/format/formatter/range.hpp | 32 - .../kstd/bits/format/formatter/string_view.hpp | 41 - libs/kstd/include/kstd/bits/format/fwd.hpp | 23 - .../include/kstd/bits/format/output_buffer.hpp | 32 - .../include/kstd/bits/format/parse_context.hpp | 109 -- libs/kstd/include/kstd/bits/format/specifiers.hpp | 205 -- libs/kstd/include/kstd/bits/format/string.hpp | 148 -- libs/kstd/include/kstd/bits/format/vformat.hpp | 99 - libs/kstd/include/kstd/bits/observer_ptr.hpp | 163 -- libs/kstd/include/kstd/bits/print_sink.hpp | 17 - libs/kstd/include/kstd/bits/shared_ptr.hpp | 648 ------- libs/kstd/include/kstd/bits/unique_ptr.hpp | 199 -- libs/kstd/include/kstd/cstring | 21 - libs/kstd/include/kstd/ext/bitfield_enum | 65 - libs/kstd/include/kstd/flat_map | 365 ---- libs/kstd/include/kstd/format | 22 - libs/kstd/include/kstd/memory | 8 - libs/kstd/include/kstd/mutex | 101 - libs/kstd/include/kstd/os/error.hpp | 34 - libs/kstd/include/kstd/os/print.hpp | 14 - libs/kstd/include/kstd/print | 69 - libs/kstd/include/kstd/ranges | 21 - libs/kstd/include/kstd/stack | 192 -- libs/kstd/include/kstd/string | 380 ---- libs/kstd/include/kstd/units | 149 -- libs/kstd/include/kstd/vector | 1154 ----------- libs/kstd/kstd/allocator | 64 + libs/kstd/kstd/asm_ptr | 78 + libs/kstd/kstd/bits/concepts.hpp | 15 + libs/kstd/kstd/bits/flat_map.hpp | 186 ++ libs/kstd/kstd/bits/format/arg.hpp | 75 + libs/kstd/kstd/bits/format/args.hpp | 160 ++ libs/kstd/kstd/bits/format/context.hpp | 65 + libs/kstd/kstd/bits/format/error.hpp | 24 + libs/kstd/kstd/bits/format/formatter.hpp | 92 + libs/kstd/kstd/bits/format/formatter/bool.hpp | 83 + libs/kstd/kstd/bits/format/formatter/byte.hpp | 23 + libs/kstd/kstd/bits/format/formatter/char.hpp | 94 + libs/kstd/kstd/bits/format/formatter/cstring.hpp | 29 + libs/kstd/kstd/bits/format/formatter/integral.hpp | 204 ++ libs/kstd/kstd/bits/format/formatter/ordering.hpp | 111 ++ libs/kstd/kstd/bits/format/formatter/pointer.hpp | 42 + libs/kstd/kstd/bits/format/formatter/range.hpp | 32 + .../kstd/bits/format/formatter/string_view.hpp | 41 + libs/kstd/kstd/bits/format/fwd.hpp | 23 + libs/kstd/kstd/bits/format/output_buffer.hpp | 32 + libs/kstd/kstd/bits/format/parse_context.hpp | 109 ++ libs/kstd/kstd/bits/format/specifiers.hpp | 205 ++ libs/kstd/kstd/bits/format/string.hpp | 148 ++ libs/kstd/kstd/bits/format/vformat.hpp | 99 + libs/kstd/kstd/bits/observer_ptr.hpp | 163 ++ libs/kstd/kstd/bits/observer_ptr.test.cpp | 360 ++++ libs/kstd/kstd/bits/print_sink.hpp | 17 + libs/kstd/kstd/bits/shared_ptr.hpp | 648 +++++++ libs/kstd/kstd/bits/unique_ptr.hpp | 199 ++ libs/kstd/kstd/cstring | 21 + libs/kstd/kstd/ext/bitfield_enum | 65 + libs/kstd/kstd/flat_map | 365 ++++ libs/kstd/kstd/flat_map.test.cpp | 314 +++ libs/kstd/kstd/format | 22 + libs/kstd/kstd/format.test.cpp | 114 ++ libs/kstd/kstd/libc/stdlib.cpp | 19 + libs/kstd/kstd/libc/string.cpp | 78 + libs/kstd/kstd/memory | 8 + libs/kstd/kstd/mutex | 101 + libs/kstd/kstd/mutex.cpp | 38 + libs/kstd/kstd/os/error.cpp | 12 + libs/kstd/kstd/os/error.hpp | 34 + libs/kstd/kstd/os/print.hpp | 14 + libs/kstd/kstd/print | 69 + libs/kstd/kstd/ranges | 21 + libs/kstd/kstd/stack | 192 ++ libs/kstd/kstd/string | 380 ++++ libs/kstd/kstd/string.test.cpp | 445 +++++ libs/kstd/kstd/test_support/os_panic.hpp | 23 + libs/kstd/kstd/test_support/os_panic.test.cpp | 15 + libs/kstd/kstd/test_support/test_types.hpp | 313 +++ libs/kstd/kstd/units | 149 ++ libs/kstd/kstd/vector | 1154 +++++++++++ libs/kstd/kstd/vector.test.cpp | 2003 ++++++++++++++++++++ libs/kstd/kstd/vformat.cpp | 209 ++ libs/kstd/src/libc/stdlib.cpp | 19 - libs/kstd/src/libc/string.cpp | 78 - libs/kstd/src/mutex.cpp | 38 - libs/kstd/src/os/error.cpp | 12 - libs/kstd/src/vformat.cpp | 209 -- libs/kstd/tests/include/kstd/tests/os_panic.hpp | 23 - libs/kstd/tests/include/kstd/tests/test_types.hpp | 313 --- libs/kstd/tests/src/flat_map.cpp | 314 --- libs/kstd/tests/src/format.cpp | 114 -- libs/kstd/tests/src/observer_ptr.cpp | 360 ---- libs/kstd/tests/src/os_panic.cpp | 15 - libs/kstd/tests/src/string.cpp | 445 ----- libs/kstd/tests/src/vector.cpp | 2003 -------------------- 111 files changed, 9615 insertions(+), 9617 deletions(-) delete mode 100644 libs/kstd/include/kstd/allocator delete mode 100644 libs/kstd/include/kstd/asm_ptr delete mode 100644 libs/kstd/include/kstd/bits/concepts.hpp delete mode 100644 libs/kstd/include/kstd/bits/flat_map.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/arg.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/args.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/context.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/error.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/bool.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/byte.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/char.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/cstring.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/integral.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/ordering.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/pointer.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/range.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/formatter/string_view.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/fwd.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/output_buffer.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/parse_context.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/specifiers.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/string.hpp delete mode 100644 libs/kstd/include/kstd/bits/format/vformat.hpp delete mode 100644 libs/kstd/include/kstd/bits/observer_ptr.hpp delete mode 100644 libs/kstd/include/kstd/bits/print_sink.hpp delete mode 100644 libs/kstd/include/kstd/bits/shared_ptr.hpp delete mode 100644 libs/kstd/include/kstd/bits/unique_ptr.hpp delete mode 100644 libs/kstd/include/kstd/cstring delete mode 100644 libs/kstd/include/kstd/ext/bitfield_enum delete mode 100644 libs/kstd/include/kstd/flat_map delete mode 100644 libs/kstd/include/kstd/format delete mode 100644 libs/kstd/include/kstd/memory delete mode 100644 libs/kstd/include/kstd/mutex delete mode 100644 libs/kstd/include/kstd/os/error.hpp delete mode 100644 libs/kstd/include/kstd/os/print.hpp delete mode 100644 libs/kstd/include/kstd/print delete mode 100644 libs/kstd/include/kstd/ranges delete mode 100644 libs/kstd/include/kstd/stack delete mode 100644 libs/kstd/include/kstd/string delete mode 100644 libs/kstd/include/kstd/units delete mode 100644 libs/kstd/include/kstd/vector create mode 100644 libs/kstd/kstd/allocator create mode 100644 libs/kstd/kstd/asm_ptr create mode 100644 libs/kstd/kstd/bits/concepts.hpp create mode 100644 libs/kstd/kstd/bits/flat_map.hpp create mode 100644 libs/kstd/kstd/bits/format/arg.hpp create mode 100644 libs/kstd/kstd/bits/format/args.hpp create mode 100644 libs/kstd/kstd/bits/format/context.hpp create mode 100644 libs/kstd/kstd/bits/format/error.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/bool.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/byte.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/char.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/cstring.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/integral.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/ordering.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/pointer.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/range.hpp create mode 100644 libs/kstd/kstd/bits/format/formatter/string_view.hpp create mode 100644 libs/kstd/kstd/bits/format/fwd.hpp create mode 100644 libs/kstd/kstd/bits/format/output_buffer.hpp create mode 100644 libs/kstd/kstd/bits/format/parse_context.hpp create mode 100644 libs/kstd/kstd/bits/format/specifiers.hpp create mode 100644 libs/kstd/kstd/bits/format/string.hpp create mode 100644 libs/kstd/kstd/bits/format/vformat.hpp create mode 100644 libs/kstd/kstd/bits/observer_ptr.hpp create mode 100644 libs/kstd/kstd/bits/observer_ptr.test.cpp create mode 100644 libs/kstd/kstd/bits/print_sink.hpp create mode 100644 libs/kstd/kstd/bits/shared_ptr.hpp create mode 100644 libs/kstd/kstd/bits/unique_ptr.hpp create mode 100644 libs/kstd/kstd/cstring create mode 100644 libs/kstd/kstd/ext/bitfield_enum create mode 100644 libs/kstd/kstd/flat_map create mode 100644 libs/kstd/kstd/flat_map.test.cpp create mode 100644 libs/kstd/kstd/format create mode 100644 libs/kstd/kstd/format.test.cpp create mode 100644 libs/kstd/kstd/libc/stdlib.cpp create mode 100644 libs/kstd/kstd/libc/string.cpp create mode 100644 libs/kstd/kstd/memory create mode 100644 libs/kstd/kstd/mutex create mode 100644 libs/kstd/kstd/mutex.cpp create mode 100644 libs/kstd/kstd/os/error.cpp create mode 100644 libs/kstd/kstd/os/error.hpp create mode 100644 libs/kstd/kstd/os/print.hpp create mode 100644 libs/kstd/kstd/print create mode 100644 libs/kstd/kstd/ranges create mode 100644 libs/kstd/kstd/stack create mode 100644 libs/kstd/kstd/string create mode 100644 libs/kstd/kstd/string.test.cpp create mode 100644 libs/kstd/kstd/test_support/os_panic.hpp create mode 100644 libs/kstd/kstd/test_support/os_panic.test.cpp create mode 100644 libs/kstd/kstd/test_support/test_types.hpp create mode 100644 libs/kstd/kstd/units create mode 100644 libs/kstd/kstd/vector create mode 100644 libs/kstd/kstd/vector.test.cpp create mode 100644 libs/kstd/kstd/vformat.cpp delete mode 100644 libs/kstd/src/libc/stdlib.cpp delete mode 100644 libs/kstd/src/libc/string.cpp delete mode 100644 libs/kstd/src/mutex.cpp delete mode 100644 libs/kstd/src/os/error.cpp delete mode 100644 libs/kstd/src/vformat.cpp delete mode 100644 libs/kstd/tests/include/kstd/tests/os_panic.hpp delete mode 100644 libs/kstd/tests/include/kstd/tests/test_types.hpp delete mode 100644 libs/kstd/tests/src/flat_map.cpp delete mode 100644 libs/kstd/tests/src/format.cpp delete mode 100644 libs/kstd/tests/src/observer_ptr.cpp delete mode 100644 libs/kstd/tests/src/os_panic.cpp delete mode 100644 libs/kstd/tests/src/string.cpp delete mode 100644 libs/kstd/tests/src/vector.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 2b5ee12..6902891 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -37,26 +37,28 @@ add_library("kstd" STATIC) add_library("kstd::lib" ALIAS "kstd") target_sources("kstd" PRIVATE - "src/os/error.cpp" - "src/mutex.cpp" - "src/vformat.cpp" + "kstd/os/error.cpp" + "kstd/mutex.cpp" + "kstd/vformat.cpp" ) file(GLOB_RECURSE KSTD_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS - "include/kstd/*" + "kstd/*" ) +list(FILTER KSTD_HEADERS EXCLUDE REGEX ".*\.cpp") + target_sources("kstd" PUBLIC FILE_SET HEADERS - BASE_DIRS "include" + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${KSTD_HEADERS} ) target_include_directories("kstd" PUBLIC - "include" + "${CMAKE_CURRENT_SOURCE_DIR}" ) set_target_properties("kstd" PROPERTIES @@ -65,8 +67,8 @@ set_target_properties("kstd" PROPERTIES if(NOT BUILD_TESTING) target_sources("kstd" PRIVATE - "src/libc/stdlib.cpp" - "src/libc/string.cpp" + "kstd/libc/stdlib.cpp" + "kstd/libc/string.cpp" ) set(KSTD_LIBC_SYMBOLS @@ -90,16 +92,12 @@ if(BUILD_TESTING) add_executable("kstd::tests" ALIAS "kstd_tests") target_sources("kstd_tests" PRIVATE - "tests/src/flat_map.cpp" - "tests/src/format.cpp" - "tests/src/vector.cpp" - "tests/src/observer_ptr.cpp" - "tests/src/os_panic.cpp" - "tests/src/string.cpp" - ) - - target_include_directories("kstd_tests" PRIVATE - "tests/include" + "kstd/flat_map.test.cpp" + "kstd/format.test.cpp" + "kstd/vector.test.cpp" + "kstd/bits/observer_ptr.test.cpp" + "kstd/test_support/os_panic.test.cpp" + "kstd/string.test.cpp" ) target_link_libraries("kstd_tests" PRIVATE diff --git a/libs/kstd/include/kstd/allocator b/libs/kstd/include/kstd/allocator deleted file mode 100644 index 0de0e10..0000000 --- a/libs/kstd/include/kstd/allocator +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef KSTD_ALLOCATOR_HPP -#define KSTD_ALLOCATOR_HPP - -#include -#include -#include - -#if __has_builtin(__builtin_operator_new) >= 201'802L -#define KSTD_OPERATOR_NEW __builtin_operator_new -#define KSTD_OPERATOR_DELETE __builtin_operator_delete -#else -#define KSTD_OPERATOR_NEW ::operator new -#define KSTD_OPERATOR_DELETE ::operator delete -#endif - -namespace kstd -{ - - template - struct allocator - { - using value_type = T; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using propagate_on_container_move_assignment = std::true_type; - using is_always_equal = std::true_type; - - constexpr allocator() noexcept = default; - - template - constexpr allocator(allocator const &) noexcept - {} - - constexpr auto allocate(std::size_t n) -> T * - { - if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) - { - return static_cast(KSTD_OPERATOR_NEW(n * sizeof(T), std::align_val_t{alignof(T)})); - } - return static_cast(KSTD_OPERATOR_NEW(n * sizeof(T))); - } - - constexpr void deallocate(T * p, std::size_t n) noexcept - { - if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) - { - KSTD_OPERATOR_DELETE(p, n * sizeof(T), std::align_val_t{alignof(T)}); - } - KSTD_OPERATOR_DELETE(p, n * sizeof(T)); - } - }; - - template - constexpr auto operator==(allocator const &, allocator const &) noexcept -> bool - { - return true; - } - -} // namespace kstd - -#undef KSTD_OPERATOR_NEW -#undef KSTD_OPERATOR_DELETE - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/asm_ptr b/libs/kstd/include/kstd/asm_ptr deleted file mode 100644 index c06a8b5..0000000 --- a/libs/kstd/include/kstd/asm_ptr +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef KSTD_ASM_POINTER_HPP -#define KSTD_ASM_POINTER_HPP - -#include -#include - -namespace kstd -{ - - /** - * @brief A pointer that is defined in some assembly source file. - * - * @tparam Type The type of the pointer - */ - template - struct asm_ptr - { - using value_type = Type; - using pointer = value_type *; - using const_pointer = value_type const *; - using reference = value_type &; - using const_reference = value_type const &; - - asm_ptr() = delete; - asm_ptr(asm_ptr const &) = delete; - asm_ptr(asm_ptr &&) = delete; - ~asm_ptr() = delete; - - constexpr auto operator=(asm_ptr const &) = delete; - constexpr auto operator=(asm_ptr &&) = delete; - - auto get() const noexcept -> pointer - { - return m_ptr; - } - - constexpr auto operator+(std::ptrdiff_t offset) const noexcept -> pointer - { - return std::bit_cast(m_ptr) + offset; - } - - constexpr auto operator*() noexcept -> reference - { - return *(std::bit_cast(m_ptr)); - } - - constexpr auto operator*() const noexcept -> const_reference - { - return *(std::bit_cast(m_ptr)); - } - - constexpr auto operator[](std::ptrdiff_t offset) noexcept -> reference - { - return *(*this + offset); - } - - constexpr auto operator[](std::ptrdiff_t offset) const noexcept -> const_reference - { - return *(*this + offset); - } - - constexpr auto operator->() noexcept -> pointer - { - return m_ptr; - } - - constexpr auto operator->() const noexcept -> const_pointer - { - return m_ptr; - } - - private: - pointer m_ptr; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/concepts.hpp b/libs/kstd/include/kstd/bits/concepts.hpp deleted file mode 100644 index 74c25cb..0000000 --- a/libs/kstd/include/kstd/bits/concepts.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef KSTD_BITS_CONCEPTS_HPP -#define KSTD_BITS_CONCEPTS_HPP - -#include -#include -namespace kstd::bits -{ - - template - concept container_compatible_range = - std::ranges::input_range && std::convertible_to, ValueType>; - -} - -#endif diff --git a/libs/kstd/include/kstd/bits/flat_map.hpp b/libs/kstd/include/kstd/bits/flat_map.hpp deleted file mode 100644 index fe46203..0000000 --- a/libs/kstd/include/kstd/bits/flat_map.hpp +++ /dev/null @@ -1,186 +0,0 @@ -#ifndef KSTD_BITS_FLAT_MAP_HPP -#define KSTD_BITS_FLAT_MAP_HPP - -#include -#include -#include -#include -#include -#include -#include - -namespace kstd::bits -{ - - template - struct flat_map_reference - { - using key_type = KeyType; - using mapped_type = MappedType; - - constexpr flat_map_reference(key_type const & key, mapped_type & mapped) - : first{key} - , second{mapped} - {} - - constexpr auto operator=(flat_map_reference const & other) const -> flat_map_reference const & - { - second = other.second; - return *this; - } - - constexpr auto operator=(flat_map_reference && other) const -> flat_map_reference const & - { - second = std::move(other.second); - return *this; - } - - template - requires(std::tuple_size_v> == 2) - constexpr auto operator=(TupleLikeType && tuple) const -> flat_map_reference const & - { - second = std::forward(tuple).second; - return *this; - } - - template - requires(Index >= 0 && Index <= 1) - [[nodiscard]] constexpr auto get() const noexcept -> decltype(auto) - { - if constexpr (Index == 0) - { - return (first); - } - else - { - return (second); - } - } - - key_type const & first; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - mapped_type & second; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - }; - - template - struct flat_map_pointer - { - Reference reference; - - [[nodiscard]] constexpr auto operator->() noexcept -> Reference * - { - return std::addressof(reference); - } - - [[nodiscard]] constexpr auto operator->() const noexcept -> Reference const * - { - return std::addressof(reference); - } - }; - - template - struct flat_map_iterator - { - using iterator_category = std::random_access_iterator_tag; - using value_type = std::pair; - using difference_type = std::ptrdiff_t; - using reference = flat_map_reference; - using pointer = flat_map_pointer; - - constexpr flat_map_iterator() = default; - - constexpr flat_map_iterator(KeyIterator key_iterator, MappedIterator mapped_iterator) - : m_key_iterator{key_iterator} - , m_mapped_iterator{mapped_iterator} - {} - - template - requires(std::convertible_to && - std::convertible_to) - constexpr flat_map_iterator( - flat_map_iterator const & other) noexcept - : m_key_iterator{other.m_key_iterator} - , m_mapped_iterator{other.m_mapped_iterator} - {} - - [[nodiscard]] auto key_iterator() const noexcept -> KeyIterator - { - return m_key_iterator; - } - - [[nodiscard]] constexpr auto operator*() const noexcept -> reference - { - return {*m_key_iterator, *m_mapped_iterator}; - } - - [[nodiscard]] constexpr auto operator->() const noexcept -> pointer - { - return { - {*m_key_iterator, *m_mapped_iterator} - }; - } - - constexpr auto operator++() noexcept -> flat_map_iterator & - { - ++m_key_iterator; - ++m_mapped_iterator; - return *this; - } - - constexpr auto operator++(int) noexcept -> flat_map_iterator - { - auto copy = *this; - ++(*this); - return copy; - } - - constexpr auto operator--() noexcept -> flat_map_iterator & - { - --m_key_iterator; - --m_mapped_iterator; - return *this; - } - - constexpr auto operator--(int) noexcept -> flat_map_iterator - { - auto copy = *this; - --(*this); - return copy; - } - - [[nodiscard]] constexpr auto operator+(difference_type offset) const noexcept -> flat_map_iterator - { - return {m_key_iterator + offset, m_mapped_iterator + offset}; - } - - [[nodiscard]] constexpr auto operator-(flat_map_iterator const & other) const noexcept -> difference_type - { - return m_key_iterator - other.m_key_iterator; - } - - [[nodiscard]] constexpr auto operator<=>(flat_map_iterator const & other) const noexcept = default; - - private: - KeyIterator m_key_iterator{}; - MappedIterator m_mapped_iterator{}; - }; - -} // namespace kstd::bits - -template -struct std::tuple_size> : std::integral_constant -{ -}; - -template -struct std::tuple_element<0, kstd::bits::flat_map_reference> -{ - using type = K const &; -}; - -template -struct std::tuple_element<1, kstd::bits::flat_map_reference> -{ - using type = M &; -}; - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/arg.hpp b/libs/kstd/include/kstd/bits/format/arg.hpp deleted file mode 100644 index e65b26f..0000000 --- a/libs/kstd/include/kstd/bits/format/arg.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_ARG_HPP -#define KSTD_BITS_FORMAT_ARG_HPP - -// IWYU pragma: private, include - -#include -#include - -#include -#include -#include - -namespace kstd -{ - - namespace bits::format - { - enum struct arg_type : std::uint8_t - { - none, - boolean, - character, - integer, - unsigned_integer, - string_view, - c_string, - pointer, - user_defined, - }; - } // namespace bits::format - - struct format_arg - { - bits::format::arg_type type{}; - union - { - bool boolean; - char character; - std::int64_t integer; - std::uint64_t unsigned_integer; - std::string_view string_view; - char const * c_string; - void const * pointer; - struct - { - void const * pointer; - auto (*format)(void const * value, format_parse_context & parse_context, format_context & context) -> void; - } user_defined; - } value{}; - }; - - namespace bits::format - { - constexpr auto extrat_dynamic_width(format_arg const & arg) -> std::size_t - { - if (arg.type == arg_type::unsigned_integer) - { - return static_cast(arg.value.unsigned_integer); - } - else if (arg.type == arg_type::integer) - { - if (arg.value.integer < 0) - { - error("Dynamic width cannont be negative."); - } - return static_cast(arg.value.integer); - } - error("Dynamic width argument is not an integral value."); - return 0; - } - } // namespace bits::format - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/args.hpp b/libs/kstd/include/kstd/bits/format/args.hpp deleted file mode 100644 index e8e3114..0000000 --- a/libs/kstd/include/kstd/bits/format/args.hpp +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_ARGS_HPP -#define KSTD_BITS_FORMAT_ARGS_HPP - -// IWYU pragma: private, include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace kstd -{ - - namespace bits::format - { - - template - auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context) - -> void - { - auto typed_value_pointer = static_cast(value_pointer); - auto fmt = formatter>{}; - auto const it = fmt.parse(parse_context); - parse_context.advance_to(it); - fmt.format(*typed_value_pointer, context); - } - - template - constexpr auto determine_arg_type() -> arg_type - { - using decay_type = std::remove_cvref_t; - if constexpr (std::same_as) - { - return arg_type::boolean; - } - else if constexpr (std::same_as) - { - return arg_type::character; - } - else if constexpr (std::integral && std::is_signed_v) - { - return arg_type::integer; - } - else if constexpr (std::integral && std::is_unsigned_v) - { - return arg_type::unsigned_integer; - } - else if constexpr (std::same_as) - { - return arg_type::string_view; - } - else if constexpr (std::same_as, char *> || - std::same_as, char const *>) - { - return arg_type::c_string; - } - else if constexpr (std::is_pointer_v || std::same_as) - { - if constexpr (std::same_as || std::same_as) - { - return arg_type::user_defined; - } - else - { - return arg_type::pointer; - } - } - else - { - return arg_type::user_defined; - } - } - - template - constexpr auto make_single_arg(ValueType const & value) -> format_arg - { - auto result = format_arg{}; - constexpr auto type = determine_arg_type(); - result.type = type; - - if constexpr (type == arg_type::boolean) - { - result.value.boolean = value; - } - else if constexpr (type == arg_type::character) - { - result.value.character = value; - } - else if constexpr (type == arg_type::integer) - { - result.value.integer = static_cast(value); - } - else if constexpr (type == arg_type::unsigned_integer) - { - result.value.unsigned_integer = static_cast(value); - } - else if constexpr (type == arg_type::string_view) - { - result.value.string_view = value; - } - else if constexpr (type == arg_type::c_string) - { - result.value.c_string = value; - } - else if constexpr (type == arg_type::pointer) - { - if constexpr (std::same_as, std::nullptr_t>) - { - result.value.pointer = nullptr; - } - else - { - result.value.pointer = static_cast(value); - } - } - else - { - result.value.user_defined.pointer = &value; - result.value.user_defined.format = format_trampoline; - } - return result; - } - - } // namespace bits::format - - using format_args = std::span; - - template - struct format_arg_store - { - std::array args{}; - }; - - template - [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store - { - using namespace bits::format; - - if constexpr (sizeof...(Arguments) == 0) - { - return format_arg_store<0>{}; - } - else - { - return format_arg_store{ - std::array{make_single_arg(args)...}}; - } - } - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp deleted file mode 100644 index c166ba9..0000000 --- a/libs/kstd/include/kstd/bits/format/context.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP -#define KSTD_BITS_FORMAT_CONTEXT_HPP - -// IWYU pragma: private, include - -#include -#include -#include - -#include -#include -#include -#include - -namespace kstd -{ - - namespace bits::format - { - template - constexpr auto inline is_width_v = std::integral && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as; - } - - struct format_context - { - using format_args = std::span; - format_args args{}; - - format_context(bits::format::output_buffer & buffer, format_args args) - : args{args} - , m_buffer{&buffer} - {} - - [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & - { - if (id >= args.size()) - { - kstd::os::panic("[kstd:format] argument index out of range!"); - } - return args[id]; - } - - constexpr auto push(std::string_view string) -> void - { - m_buffer->push(string); - } - - constexpr auto push(char character) -> void - { - m_buffer->push(character); - } - - private: - bits::format::output_buffer * m_buffer; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/error.hpp b/libs/kstd/include/kstd/bits/format/error.hpp deleted file mode 100644 index c0cb53d..0000000 --- a/libs/kstd/include/kstd/bits/format/error.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_ERROR_HPP -#define KSTD_BITS_FORMAT_ERROR_HPP - -#include - -namespace kstd::bits::format -{ - - constexpr auto error(char const * message) -> void - { - if consteval - { - extern void compile_time_format_error_triggered(char const *); - compile_time_format_error_triggered(message); - } - else - { - kstd::os::panic("Error while formatting a string."); - } - } - -} // namespace kstd::bits::format - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter.hpp b/libs/kstd/include/kstd/bits/format/formatter.hpp deleted file mode 100644 index eb28829..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef KSTD_BITS_FORMATTER_HPP -#define KSTD_BITS_FORMATTER_HPP - -// IWYU pragma: private, include - -#include -#include -#include - -#include -#include -#include - -namespace kstd -{ - - template - struct formatter - { - formatter() = delete; - formatter(formatter const &) = delete; - auto operator=(formatter const &) -> formatter & = delete; - }; - - template - struct range_formatter - { - constexpr auto set_separator(std::string_view sep) -> void - { - m_separator = sep; - } - - constexpr auto set_brackets(std::string_view opening, std::string_view closing) - { - m_prefix = opening; - m_suffix = closing; - } - - constexpr auto parse(format_parse_context & context) - { - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it == 'n') - { - set_brackets("", ""); - std::advance(it, 1); - } - - if (it != end && *it == ':') - { - std::advance(it, 1); - context.advance_to(it); - it = m_inner_formatter.parse(context); - } - - if (it != end && *it != '}') - { - bits::format::error("Invalid formate specifier for range"); - } - - return it; - } - - template - auto format(Range const & range, format_context & context) - { - context.push(m_prefix); - - auto is_first = true; - std::ranges::for_each(range, [&](auto const & element) { - if (!is_first) - { - context.push(m_separator); - } - m_inner_formatter.format(element, context); - is_first = false; - }); - - context.push(m_suffix); - } - - private: - kstd::formatter m_inner_formatter{}; - std::string_view m_separator{", "}; - std::string_view m_prefix{"["}; - std::string_view m_suffix{"]"}; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp deleted file mode 100644 index cc8d190..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP -#define KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP - -#include -#include -#include -#include -#include - -#include -#include - -namespace kstd -{ - - template<> - struct formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it != '}') - { - if (*it == 's') - { - specifiers.type = *it; - std::advance(it, 1); - } - else - { - bits::format::error("Invalid type specifier for bool."); - } - } - - if (it != end && *it != '}') - { - bits::format::error("Missing terminating '}' in format string."); - } - - return it; - } - - auto format(bool value, format_context & context) const -> void - { - auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; - auto final_width = 0uz; - - if (specifiers.mode == bits::format::width_mode::static_value) - { - final_width = specifiers.width_value; - } - else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) - { - auto const & arg = context.arg(specifiers.width_value); - final_width = bits::format::extrat_dynamic_width(arg); - } - - auto padding = bits::format::calculate_format_padding(final_width, text.size(), specifiers.align, - bits::format::alignment::left); - - for (auto i = 0uz; i < padding.left; ++i) - { - context.push(specifiers.fill); - } - - context.push(text); - - for (auto i = 0uz; i < padding.right; ++i) - { - context.push(specifiers.fill); - } - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/byte.hpp b/libs/kstd/include/kstd/bits/format/formatter/byte.hpp deleted file mode 100644 index cc8aece..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/byte.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP -#define KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP - -#include -#include -#include - -#include - -namespace kstd -{ - - template<> - struct formatter : formatter - { - auto format(std::byte value, format_context & context) const -> void - { - formatter::format(static_cast(value), context); - } - }; - -} // namespace kstd -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/char.hpp b/libs/kstd/include/kstd/bits/format/formatter/char.hpp deleted file mode 100644 index 92489a1..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/char.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP -#define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP - -#include -#include -#include -#include -#include -#include - -#include - -namespace kstd -{ - - template<> - struct formatter : formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - - auto it = context.begin(); - auto const end = context.end(); - - if (specifiers.alternative_form || specifiers.zero_pad || specifiers.sign != bits::format::sign_mode::none) - { - bits::format::error("Invalid format specifiers for 'char'"); - } - - if (it != end && *it != '}') - { - if (*it == 'c' || *it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X') - { - specifiers.type = *it; - std::advance(it, 1); - } - else - { - bits::format::error("Invalid type specifier for char."); - } - } - - if (it != end && *it != '}') - { - bits::format::error("Missing terminating '}' in format string."); - } - - return it; - } - - auto format(char value, format_context & context) const -> void - { - if (specifiers.type == '\0' || specifiers.type == 'c') - { - auto final_width = 0uz; - - if (specifiers.mode == bits::format::width_mode::static_value) - { - final_width = specifiers.width_value; - } - else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) - { - auto const & arg = context.arg(specifiers.width_value); - final_width = bits::format::extrat_dynamic_width(arg); - } - - auto padding = - bits::format::calculate_format_padding(final_width, 1, specifiers.align, bits::format::alignment::left); - - for (auto i = 0uz; i < padding.left; ++i) - { - context.push(specifiers.fill); - } - - context.push(value); - - for (auto i = 0uz; i < padding.right; ++i) - { - context.push(specifiers.fill); - } - } - else - { - formatter::format(static_cast(value), context); - } - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp deleted file mode 100644 index 553c8ca..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP -#define KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP - -#include -#include -#include - -#include - -namespace kstd -{ - - template<> - struct formatter : formatter - { - auto format(char const * string, format_context & context) const -> void - { - formatter::format(string ? std::string_view{string} : "(null)", context); - } - }; - - template<> - struct formatter : formatter - { - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp deleted file mode 100644 index d17dc95..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp +++ /dev/null @@ -1,204 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP -#define KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace kstd -{ - - template - struct formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto static maximum_digits = 80; - - enum struct base - { - bin = 2, - oct = 8, - dec = 10, - hex = 16, - }; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it != '}') - { - if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p') - { - specifiers.type = *it; - std::advance(it, 1); - } - else - { - bits::format::error("Invalid type specifier for integral type."); - } - } - - if (it != end && *it != '}') - { - bits::format::error("Missing terminating '}' in format string."); - } - - return it; - } - - auto format(T value, format_context & context) const -> void - { - auto final_width = 0uz; - if (specifiers.mode == bits::format::width_mode::static_value) - { - final_width = specifiers.width_value; - } - else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) - { - auto const & arg = context.arg(specifiers.width_value); - final_width = bits::format::extrat_dynamic_width(arg); - } - - using unsigned_T = std::make_unsigned_t; - auto absolute_value = static_cast(value); - auto is_negative = false; - - if constexpr (std::is_signed_v) - { - if (value < 0) - { - is_negative = true; - absolute_value = 0 - static_cast(value); - } - } - - auto const base = [type = specifiers.type] -> auto { - switch (type) - { - case 'x': - case 'X': - case 'p': - return base::hex; - case 'b': - case 'B': - return base::bin; - case 'o': - return base::oct; - default: - return base::dec; - } - }(); - - auto buffer = std::array{}; - auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; - auto current = buffer.rbegin(); - - if (absolute_value == 0) - { - *current = '0'; - std::advance(current, 1); - } - else - { - while (absolute_value != 0) - { - *current = digits[absolute_value % std::to_underlying(base)]; - std::advance(current, 1); - absolute_value /= std::to_underlying(base); - } - } - - auto content_length = static_cast(std::distance(buffer.rbegin(), current)); - auto prefix = std::array{'0', '\0'}; - auto prefix_length = 0uz; - if (specifiers.alternative_form) - { - switch (base) - { - case base::bin: - prefix[1] = specifiers.type == 'B' ? 'B' : 'b'; - prefix_length = 2; - break; - case base::oct: - prefix_length = 1; - break; - case base::hex: - prefix[1] = specifiers.type == 'X' ? 'X' : 'x'; - prefix_length = 2; - break; - default: - break; - } - } - - auto sign_character = '\0'; - if (is_negative) - { - sign_character = '-'; - } - else if (specifiers.sign == bits::format::sign_mode::plus) - { - sign_character = '+'; - } - else if (specifiers.sign == bits::format::sign_mode::space) - { - sign_character = ' '; - } - - auto const total_length = content_length + prefix_length + (sign_character != '\0'); - auto const padding = bits::format::calculate_format_padding(final_width, total_length, specifiers.align, - bits::format::alignment::right); - auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::format::alignment::none); - - if (!effective_zero_pad) - { - for (auto i = 0uz; i < padding.left; ++i) - { - context.push(specifiers.fill); - } - } - - if (sign_character != '\0') - { - context.push(sign_character); - } - if (prefix_length > 0) - { - context.push(std::string_view{prefix.data(), prefix_length}); - } - - if (effective_zero_pad) - { - for (auto i = 0uz; i < padding.left; ++i) - { - context.push('0'); - } - } - - context.push(std::string_view{current.base(), content_length}); - - for (auto i = 0uz; i < padding.right; ++i) - { - context.push(specifiers.fill); - } - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp b/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp deleted file mode 100644 index 7832226..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP -#define KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP - -#include -#include -#include -#include - -#include - -namespace kstd -{ - - template<> - struct formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - return context.begin(); - } - - auto format(std::strong_ordering value, format_context & context) const -> void - { - if (value == std::strong_ordering::equal) - { - return context.push(specifiers.alternative_form ? "==" : "equal"); - } - else if (value == std::strong_ordering::equivalent) - { - return context.push(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::strong_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::strong_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - kstd::os::panic("[kstd:format] Invalid strong ordering value!"); - } - }; - - template<> - struct formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - return context.begin(); - } - - auto format(std::weak_ordering value, format_context & context) const -> void - { - if (value == std::weak_ordering::equivalent) - { - return context.push(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::weak_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::weak_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - kstd::os::panic("[kstd:format] Invalid weak ordering value!"); - } - }; - - template<> - struct formatter - { - bits::format::specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - specifiers = bits::format::parse_format_specifiers(context); - return context.begin(); - } - - auto format(std::partial_ordering value, format_context & context) const -> void - { - if (value == std::partial_ordering::equivalent) - { - return context.push(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::partial_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::partial_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - else if (value == std::partial_ordering::unordered) - { - return context.push(specifiers.alternative_form ? "<=>" : "unordered"); - } - kstd::os::panic("[kstd:format] Invalid partial ordering value!"); - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp b/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp deleted file mode 100644 index 15f9a5b..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP -#define KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP - -#include -#include -#include -#include -#include - -#include -#include - -namespace kstd -{ - - template - struct formatter : formatter - { - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - auto result = formatter::parse(context); - if (!this->specifiers.type) - { - this->specifiers.type = 'p'; - this->specifiers.alternative_form = true; - } - return result; - } - - auto format(T const * pointer, format_context & context) const -> void - { - formatter::format(std::bit_cast(pointer), context); - } - }; - - template - struct formatter : formatter - { - }; - -} // namespace kstd -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/range.hpp b/libs/kstd/include/kstd/bits/format/formatter/range.hpp deleted file mode 100644 index 05af06f..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/range.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP -#define KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP - -#include - -#include -#include -#include -#include - -namespace kstd -{ - namespace bits::format - { - template - concept iterable = requires(T const & t) { - t.begin(); - t.end(); - }; - - template - concept formattable_range = iterable && !std::same_as, std::string_view> && - !std::same_as, char *> && !std::same_as, char const *>; - } // namespace bits::format - - template - struct formatter : range_formatter> - { - }; -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp b/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp deleted file mode 100644 index 7d74579..0000000 --- a/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP -#define KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP - -#include -#include -#include -#include - -#include - -namespace kstd -{ - - template<> - struct formatter - { - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - auto it = context.begin(); - - if (it != context.end() && *it == 's') - { - ++it; - } - - if (it != context.end() && *it != '}') - { - bits::format::error("Invalid specifier for string_view."); - } - return it; - } - - auto format(std::string_view const & string, format_context & context) const -> void - { - context.push(string); - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/fwd.hpp b/libs/kstd/include/kstd/bits/format/fwd.hpp deleted file mode 100644 index 6caedae..0000000 --- a/libs/kstd/include/kstd/bits/format/fwd.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_FWD_HPP -#define KSTD_BITS_FORMAT_FWD_HPP - -// IWYU pragma: private - -#include - -namespace kstd -{ - - struct format_parse_context; - struct format_context; - struct format_arg; - - template - struct formatter; - - template - struct format_arg_store; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/output_buffer.hpp b/libs/kstd/include/kstd/bits/format/output_buffer.hpp deleted file mode 100644 index fd7a2b4..0000000 --- a/libs/kstd/include/kstd/bits/format/output_buffer.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP -#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP - -// IWYU pragma: private, include - -#include - -namespace kstd::bits::format -{ - - //! An abstract interface for formatted output buffers. - //! - //! This interface is intended to be use for functions dealing with string formatting, like the print and the format - //! family. - struct output_buffer - { - virtual ~output_buffer() = default; - - //! Push a text segment into the buffer. - //! - //! @param text The text segment to push. - virtual auto push(std::string_view text) -> void = 0; - - //! Push a single character into the buffer. - //! - //! @param character The character to push into the buffer. - virtual auto push(char character) -> void = 0; - }; - -} // namespace kstd::bits::format - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/parse_context.hpp b/libs/kstd/include/kstd/bits/format/parse_context.hpp deleted file mode 100644 index cab8d72..0000000 --- a/libs/kstd/include/kstd/bits/format/parse_context.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP -#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP - -// IWYU pragma: private, include - -#include - -#include -#include - -namespace kstd -{ - - struct format_parse_context - { - using iterator = std::string_view::const_iterator; - - constexpr format_parse_context(std::string_view format, std::size_t argument_count, - bool const * is_integral = nullptr) - : m_current{format.begin()} - , m_end{format.end()} - , m_argument_count{argument_count} - , m_is_integral{is_integral} - {} - - [[nodiscard]] constexpr auto begin() const -> iterator - { - return m_current; - } - - [[nodiscard]] constexpr auto end() const -> iterator - { - return m_end; - } - - constexpr auto advance_to(iterator position) -> void - { - m_current = position; - } - - constexpr auto next_arg_id() -> std::size_t - { - if (m_mode == index_mode::manual) - { - bits::format::error("Cannot mix automatic and manual indexing."); - } - - m_mode = index_mode::automatic; - - if (m_next_argument_id >= m_argument_count) - { - bits::format::error("Argument index out of bounds."); - } - return m_next_argument_id++; - } - - constexpr auto check_arg_id(std::size_t index) -> void - { - if (m_mode == index_mode::automatic) - { - bits::format::error("Cannot mix automatic and manual indexing."); - } - - m_mode = index_mode::manual; - - if (index >= m_argument_count) - { - bits::format::error("Argument index out of bounds."); - } - } - - constexpr auto check_dynamic_width_id(std::size_t id) -> void - { - check_arg_id(id); - if (m_is_integral && !m_is_integral[id]) - { - bits::format::error("Dynamic width argument must be an integral object."); - } - } - - constexpr auto next_dynamic_width_id() -> std::size_t - { - auto const id = next_arg_id(); - if (m_is_integral && !m_is_integral[id]) - { - bits::format::error("Dynamic width argument must be an integral object."); - } - return id; - } - - private: - enum class index_mode - { - unknown, - automatic, - manual, - }; - - iterator m_current{}; - iterator m_end{}; - index_mode m_mode{}; - std::size_t m_next_argument_id{}; - std::size_t m_argument_count{}; - bool const * m_is_integral{}; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/specifiers.hpp b/libs/kstd/include/kstd/bits/format/specifiers.hpp deleted file mode 100644 index 211c95d..0000000 --- a/libs/kstd/include/kstd/bits/format/specifiers.hpp +++ /dev/null @@ -1,205 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_SPECS_HPP -#define KSTD_BITS_FORMAT_SPECS_HPP - -// IWYU pragma: private - -#include -#include - -#include -#include -#include - -namespace kstd::bits::format -{ - enum struct alignment : std::uint8_t - { - none, - left, - right, - center, - }; - - enum struct sign_mode : std::uint8_t - { - none, - plus, - minus, - space, - }; - - enum struct width_mode : std::uint8_t - { - none, - static_value, - dynamic_argument_id - }; - - struct specifiers - { - char fill{' '}; - alignment align{}; - sign_mode sign{}; - bool alternative_form{}; - bool zero_pad{}; - - width_mode mode{}; - std::size_t width_value{}; - char type{}; - }; - - struct padding - { - std::size_t left{}; - std::size_t right{}; - }; - - constexpr auto parse_format_specifiers(format_parse_context & context) -> specifiers - { - auto specs = specifiers{}; - auto it = context.begin(); - auto const end = context.end(); - - if (it != end && *it == '}') - { - return specs; - } - - if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^')) - { - specs.fill = *it; - switch (*std::next(it)) - { - case '<': - specs.align = alignment::left; - break; - case '>': - specs.align = alignment::right; - break; - case '^': - default: - specs.align = alignment::center; - break; - } - std::advance(it, 2); - } - else if (*it == '<' || *it == '>' || *it == '^') - { - switch (*it) - { - case '<': - specs.align = alignment::left; - break; - case '>': - specs.align = alignment::right; - break; - case '^': - default: - specs.align = alignment::center; - break; - } - std::advance(it, 1); - } - - if (it != end && (*it == '+' || *it == '-' || *it == ' ')) - { - switch (*it) - { - case '+': - specs.sign = sign_mode::plus; - break; - case '-': - specs.sign = sign_mode::minus; - break; - case ' ': - default: - specs.sign = sign_mode::space; - break; - } - std::advance(it, 1); - } - - if (it != end && *it == '#') - { - specs.alternative_form = true; - std::advance(it, 1); - } - - if (it != end && *it == '0') - { - specs.zero_pad = true; - std::advance(it, 1); - } - - if (it != end && *it == '{') - { - specs.mode = width_mode::dynamic_argument_id; - std::advance(it, 1); - auto argument_id = 0uz; - - if (it != end && *it >= '0' && *it <= '9') - { - while (it != end && *it >= '0' && *it <= '9') - { - argument_id = argument_id * 10 + static_cast(*it - '0'); - std::advance(it, 1); - } - context.check_dynamic_width_id(argument_id); - } - else - { - argument_id = context.next_dynamic_width_id(); - } - - if (it == end || *it != '}') - { - error("Expected '}' for dynamic width."); - } - std::advance(it, 1); - specs.width_value = argument_id; - } - else if (it != end && *it >= '0' && *it <= '9') - { - specs.mode = width_mode::static_value; - while (it != end && *it >= '0' && *it <= '9') - { - specs.width_value = specs.width_value * 10 + static_cast(*it - '0'); - std::advance(it, 1); - } - } - - context.advance_to(it); - return specs; - } - - constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length, - alignment requested_alignment, alignment default_alignment) -> padding - { - if (target_width <= content_length) - { - return {}; - } - - auto total_padding = target_width - content_length; - auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment; - - switch (effective_alignment) - { - case alignment::center: - { - auto left = total_padding / 2; - auto right = total_padding - left; - return {left, right}; - } - case alignment::left: - return {0, total_padding}; - case alignment::right: - default: - return {total_padding, 0}; - break; - } - } - -} // namespace kstd::bits::format - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/string.hpp b/libs/kstd/include/kstd/bits/format/string.hpp deleted file mode 100644 index e7e4088..0000000 --- a/libs/kstd/include/kstd/bits/format/string.hpp +++ /dev/null @@ -1,148 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_STRING_HPP -#define KSTD_BITS_FORMAT_STRING_HPP - -// IWYU pragma: private, include - -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace kstd -{ - - namespace bits::format - { - template - constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void - { - auto found = false; - auto current_index = 0uz; - - (..., [&] { - if (current_index == target_index && !found) - { - using decay_type = std::remove_cvref_t; - static_assert(std::is_default_constructible_v>, "Missing formatter specialization."); - auto fmt = formatter{}; - - auto it = fmt.parse(context); - context.advance_to(it); - found = true; - } - ++current_index; - }()); - - if (!found) - { - error("Argument index out of bounds."); - } - } - - template - constexpr auto validate_dynamic_width(std::size_t target_index) -> void - { - auto is_valid_integer = false; - auto current_index = 0uz; - - (..., [&] { - if (current_index == target_index) - { - using decay_type = std::remove_cvref_t; - if constexpr (std::is_integral_v) - { - is_valid_integer = true; - } - } - ++current_index; - }()); - - if (!is_valid_integer) - { - error("Dynamic width argument must be an integral object."); - } - } - } // namespace bits::format - - template - struct format_string - { - template - consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT - : str_view{str} - { - using namespace bits::format; - - auto const is_width_compatible = - std::array 0 ? sizeof...(Args) : 1)>{is_width_v>...}; - auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; - auto it = context.begin(); - - while (it != context.end()) - { - if (*it == '{') - { - ++it; - if (it != context.end() && *it == '{') - { - ++it; - context.advance_to(it); - continue; - } - - context.advance_to(it); - auto argument_id = 0uz; - - if (it != context.end() && *it >= '0' && *it <= '9') - { - while (it != context.end() && *it >= '0' && *it <= '9') - { - argument_id = argument_id * 10 + static_cast(*it - '0'); - ++it; - } - context.check_arg_id(argument_id); - context.advance_to(it); - } - else - { - argument_id = context.next_arg_id(); - } - - if (it != context.end() && *it == ':') - { - ++it; - context.advance_to(it); - } - - validate_argument(argument_id, context); - - it = context.begin(); - if (it == context.end() || *it != '}') - { - bits::format::error("Missing closing '}' in format string."); - } - } - else if (*it == '}') - { - ++it; - if (it != context.end() && *it == '}') - { - bits::format::error("Unescaped '}' in format string."); - } - } - ++it; - context.advance_to(it); - } - } - - std::string_view str_view; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp deleted file mode 100644 index 994fae5..0000000 --- a/libs/kstd/include/kstd/bits/format/vformat.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP -#define KSTD_BITS_FORMAT_VFORMAT_HPP - -// IWYU pragma: private, include - -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace kstd -{ - - namespace bits::format - { - //! Format a string with the given arguments into the given buffer. - //! - //! External implementations of `vprint` may call this function. - //! - //! @param buffer The buffer to format into. - //! @param format The format string to use. - //! @param args The arguments for the format string. - auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void; - - struct string_writer : output_buffer - { - auto push(std::string_view text) -> void override; - auto push(char character) -> void override; - - auto release() -> string &&; - - private: - string m_result{}; - }; - - template Output> - struct iterator_writer : output_buffer - { - explicit iterator_writer(Output iterator) - : m_output{iterator} - {} - - [[nodiscard]] auto iterator() const -> Output - { - return m_output; - } - - auto push(std::string_view text) -> void override - { - m_output = std::ranges::copy(text, m_output).out; - } - - auto push(char character) -> void override - { - *m_output++ = character; - } - - private: - Output m_output{}; - }; - - } // namespace bits::format - - //! Format a given string with the provided arguments. - //! - //! @param format The format string. - //! @param args The arguments for the format string. - //! @return A new string containing the result of the format operation. - template - auto format(format_string...> format, ArgumentTypes &&... args) -> string - { - auto buffer = bits::format::string_writer{}; - bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); - return buffer.release(); - } - - //! Format a given string with the provided arguments. - //! - //! @param iterator The iterator to write to. - //! @param format The format string. - //! @param args The arguments for the format string. - //! @return An iterator past the last element written. - template Output, typename... ArgumentTypes> - auto format_to(Output iterator, format_string...> format, - ArgumentTypes &&... args) -> Output - { - auto buffer = bits::format::iterator_writer{iterator}; - bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); - return buffer.iterator(); - } - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/observer_ptr.hpp b/libs/kstd/include/kstd/bits/observer_ptr.hpp deleted file mode 100644 index 2593d7a..0000000 --- a/libs/kstd/include/kstd/bits/observer_ptr.hpp +++ /dev/null @@ -1,163 +0,0 @@ -#ifndef KSTD_OBSERVER_PTR_HPP -#define KSTD_OBSERVER_PTR_HPP - -// IWYU pragma: private, include - -#include - -#include -#include -#include -#include -#include - -namespace kstd -{ - - template - struct observer_ptr - { - //! The type of the element being pointed to. - using element_type = ElementType; - - //! Construct an empty observer pointer. - constexpr observer_ptr() noexcept = default; - - //! Construct an empty observer pointer from a null pointer. - constexpr observer_ptr(std::nullptr_t) noexcept {} - - //! Construct an observer pointer from a raw pointer. - constexpr explicit observer_ptr(element_type * pointer) - : m_ptr{pointer} - {} - - //! Construct an observer pointer from another observer pointer. - template - requires std::convertible_to - constexpr observer_ptr(observer_ptr other) noexcept - : m_ptr{other.get()} - {} - - //! Copy construct an observer pointer. - constexpr observer_ptr(observer_ptr const & other) noexcept = default; - - //! Move construct an observer pointer. - constexpr observer_ptr(observer_ptr && other) noexcept = default; - - //! Copy assign an observer pointer. - constexpr auto operator=(observer_ptr const & other) noexcept -> observer_ptr & = default; - - //! Move assign an observer pointer. - constexpr auto operator=(observer_ptr && other) noexcept -> observer_ptr & = default; - - //! Stop watching the the watched object. - //! - //! @return The currently watched object, or nullptr if no object is being watched. - [[nodiscard]] constexpr auto release() noexcept -> element_type * - { - return std::exchange(m_ptr, nullptr); - } - - //! Reset the observer pointer. - //! - //! @param pointer The new object to watch. - constexpr auto reset(element_type * pointer = nullptr) noexcept -> void - { - m_ptr = pointer; - } - - //! Swap the observer pointer with another observer pointer. - //! - //! @param other The other observer pointer to swap with. - constexpr auto swap(observer_ptr & other) noexcept -> void - { - std::swap(m_ptr, other.m_ptr); - } - - //! Get the currently watched object. - //! - //! @return The currently watched object, or nullptr if no object is being watched. - [[nodiscard]] constexpr auto get() const noexcept -> element_type * - { - return m_ptr; - } - - //! Check if the observer pointer is watching an object. - //! - //! @return True if the observer pointer is watching an object, false otherwise. - [[nodiscard]] constexpr explicit operator bool() const noexcept - { - return m_ptr != nullptr; - } - - //! Get the currently watched object. - //! - //! @return A reference to the currently watched object. - [[nodiscard]] constexpr auto operator*() const -> std::add_lvalue_reference_t - { - throw_on_null(); - return *m_ptr; - } - - //! Get the currently watched object. - //! - //! @return A pointer to the currently watched object. - [[nodiscard]] constexpr auto operator->() const -> element_type * - { - throw_on_null(); - return m_ptr; - } - - //! Convert the observer pointer to a raw pointer. - //! - //! @return A pointer to the currently watched object. - constexpr explicit operator element_type *() const noexcept - { - return m_ptr; - } - - //! Compare the observer pointer with another observer pointer. - //!> - //! @param other The other observer pointer to compare with. - //! @return The result of the comparison. - constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default; - - private: - //! Throw an exception if the observer pointer is null. - //! - //! @throws std::runtime_error if the observer pointer is null. - constexpr auto throw_on_null() const -> void - { - if (m_ptr == nullptr) - { - os::panic("[kstd:observer_ptr] Dereferencing a null observer pointer"); - } - } - - //! The raw pointer to the watched object. - ElementType * m_ptr{}; - }; - - //! Swap two observer pointers. - //! - //! @param lhs The first observer pointer to swap. - //! @param rhs The second observer pointer to swap. - template - constexpr auto swap(observer_ptr & lhs, observer_ptr & rhs) noexcept -> void - { - lhs.swap(rhs); - } - - //! Create an observer pointer from a raw pointer. - //! - //! @param pointer The raw pointer to create an observer pointer from. - //! @return An observer pointer to the given raw pointer. - template - constexpr auto make_observer(ElementType * pointer) noexcept -> observer_ptr - { - return observer_ptr{pointer}; - } - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/print_sink.hpp b/libs/kstd/include/kstd/bits/print_sink.hpp deleted file mode 100644 index af765e0..0000000 --- a/libs/kstd/include/kstd/bits/print_sink.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef KSTD_BITS_PRINT_SINK_HPP -#define KSTD_BITS_PRINT_SINK_HPP - -// IWYU pragma: private, include - -namespace kstd -{ - - enum struct print_sink - { - stdout, - stderr, - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/shared_ptr.hpp b/libs/kstd/include/kstd/bits/shared_ptr.hpp deleted file mode 100644 index 8930095..0000000 --- a/libs/kstd/include/kstd/bits/shared_ptr.hpp +++ /dev/null @@ -1,648 +0,0 @@ -#ifndef KSTD_BITS_SHARED_PTR_HPP -#define KSTD_BITS_SHARED_PTR_HPP - -#include -#include -#include -#include - -// IWYU pragma: private, include - -namespace kstd -{ - /** - * @brief Control block for shared_ptr and weak_ptr. This control block contains the reference counts for shared - * ownership and weak ownership. The shared_count tracks the number of shared_ptr instances that own the managed - * object, while the weak_count tracks the number of weak_ptr instances that reference the managed object. - * The weak_count is needed to determine when it is safe to delete the shared_control_block itself - */ - struct shared_control_block - { - std::atomic shared_count; - std::atomic weak_count; - - explicit shared_control_block(std::size_t shared = 1, std::size_t weak = 0) - : shared_count(shared) - , weak_count(weak) - {} - }; - - template - struct shared_ptr; - - /** - * @brief weak_ptr is a smart pointer that holds a non-owning weak reference to an object that is managed by - * shared_ptr. It must be converted to shared_ptr in to be able to access the referenced object. A weak_ptr is created - * as a copy of a shared_ptr, or as a copy of another weak_ptr. A weak_ptr is typically used to break circular - * references between shared_ptr to avoid memory leaks. A weak_ptr does not contribute to the reference count of the - * object, and it does not manage the lifetime of the object. If the object managed by shared_ptr is destroyed, the - * weak_ptr becomes expired and cannot be used to access the object anymore. - */ - template - struct weak_ptr - { - template - friend struct shared_ptr; - - /** - * @brief Constructs a null weak_ptr. - */ - weak_ptr() noexcept - : pointer(nullptr) - , control(nullptr) - {} - - template - requires(std::is_convertible_v) - weak_ptr(shared_ptr const & other) - : pointer(other.pointer) - , control(other.control) - { - if (control != nullptr) - { - ++(control->weak_count); - } - } - - /** - * @brief Copy constructor. Constructs a weak_ptr which shares ownership of the object managed by other. - */ - weak_ptr(weak_ptr const & other) - : pointer(other.pointer) - , control(other.control) - { - if (control != nullptr) - { - ++(control->weak_count); - } - } - - /** - * @brief Move constructor. Constructs a weak_ptr which takes ownership of the object managed by other. - */ - weak_ptr(weak_ptr && other) noexcept - : pointer(other.pointer) - , control(other.control) - { - other.pointer = nullptr; - other.control = nullptr; - } - - /** - * @brief Assignment operator. Assigns the weak_ptr to another weak_ptr. - */ - auto operator=(weak_ptr const & other) -> weak_ptr & - { - if (this != &other) - { - cleanup(); - pointer = other.pointer; - control = other.control; - if (control != nullptr) - { - ++(control->weak_count); - } - } - - return *this; - } - - /** - * @brief Move assignment operator. Move-assigns a weak_ptr from other. - */ - auto operator=(weak_ptr && other) noexcept -> weak_ptr & - { - if (this != &other) - { - cleanup(); - pointer = other.pointer; - control = other.control; - other.pointer = nullptr; - other.control = nullptr; - } - - return *this; - } - - /** - * @brief Destructor. Cleans up resources if necessary. - */ - ~weak_ptr() - { - cleanup(); - } - - /** - * @brief Returns a shared_ptr that shares ownership of the managed object if the managed object still exists, or an - * empty shared_ptr otherwise. - */ - [[nodiscard]] auto lock() const -> shared_ptr - { - return shared_ptr(*this); - } - - private: - auto cleanup() -> void - { - if (control != nullptr) - { - if (--(control->weak_count) == 0 && control->shared_count == 0) - { - delete control; - } - } - } - - T * pointer; - shared_control_block * control; - }; - - /** - * @brief enable_shared_from_this is a base class that allows an object that is currently managed by a shared_ptr to - * create additional shared_ptr instances that share ownership of the same object. This is usefl when you want to - * create shared_ptr instances in a member function of the object. - * - * @tparam T The type of the managed object. - */ - template - struct enable_shared_from_this - { - template - friend struct shared_ptr; - - friend T; - - public: - /** - * @brief Returns a shared_ptr that shares ownership of *this. - */ - auto shared_from_this() -> shared_ptr - { - return shared_ptr(weak_this); - } - - /** - * @brief Returns a shared_ptr that shares ownership of *this. - */ - auto shared_from_this() const -> shared_ptr - { - return shared_ptr(weak_this); - } - - private: - enable_shared_from_this() = default; - enable_shared_from_this(enable_shared_from_this const &) = default; - auto operator=(enable_shared_from_this const &) -> enable_shared_from_this & = default; - ~enable_shared_from_this() = default; - - void internal_assign_ptr(shared_ptr const & ptr) const - { - weak_this = ptr; - } - - mutable weak_ptr weak_this{}; ///< Weak pointer to the object, used for shared_from_this functionality. - }; - - /** - * @brief Shared_pointer is a smart pointer that retains shared ownership of an object through a pointer. Several - * shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of - * the following happens: the last remaining shared_ptr owning the object is destroyed; the last remaining - * shared_ptr owning the object is assigned another pointer via operator= or reset(). A - * shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used - * to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(), - * the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count - * reaches zero. - * - * @tparam T The type of the managed object. - */ - template - struct shared_ptr - { - template - friend struct shared_ptr; - - template - friend struct weak_ptr; - - /** - * @brief Construct an empty shared_ptr. - */ - shared_ptr() noexcept - : pointer(nullptr) - , control(nullptr) - {} - - /** - * @brief Construct an empty shared_ptr from nullptr. - */ - shared_ptr(std::nullptr_t) noexcept - : pointer(nullptr) - , control(nullptr) - {} - - /** - * @brief Constructor. - * - * @param pointer A pointer to an object to manage (default is nullptr). - */ - template - requires(std::is_convertible_v) - explicit shared_ptr(U * pointer = nullptr) - : pointer(pointer) - , control(pointer != nullptr ? new shared_control_block() : nullptr) - { - assign_enable_shared_from_this(pointer); - } - - /** - * @brief Constructor a shared_ptr from a weak_ptr. If other is not expired, constructs a shared_ptr which shares - * ownership of the object managed by other. Otherwise, constructs an empty shared_ptr. - * - * @param other The weak_ptr to construct from. - */ - template - requires(std::is_convertible_v) - explicit shared_ptr(weak_ptr const & other) - : pointer(nullptr) - , control(nullptr) - { - if (other.control != nullptr && other.control->shared_count != 0) - { - pointer = other.pointer; - control = other.control; - ++(control->shared_count); - } - } - - /** - * @brief Copy constructor. - * - * @param other The shared_ptr to copy from. - */ - shared_ptr(shared_ptr const & other) - : pointer(other.pointer) - , control(other.control) - { - if (control != nullptr) - { - ++(control->shared_count); - } - } - - /** - * @brief Converting copy constructor for compatible shared_ptr types. - * - * @tparam U Source pointer element type. - * @param other The shared_ptr to copy from. - */ - template - requires(std::is_convertible_v) - shared_ptr(shared_ptr const & other) - : pointer(other.pointer) - , control(other.control) - { - if (control != nullptr) - { - ++(control->shared_count); - } - } - - /** - * @brief Move constructor. - * - * @param other The shared_ptr to move from. - */ - shared_ptr(shared_ptr && other) noexcept - : pointer(other.pointer) - , control(other.control) - { - other.pointer = nullptr; - other.control = nullptr; - } - - /** - * @brief Converting move constructor for compatible shared_ptr types. - * - * @tparam U Source pointer element type. - * @param other The shared_ptr to move from. - */ - template - requires(std::is_convertible_v) - shared_ptr(shared_ptr && other) noexcept - : pointer(other.pointer) - , control(other.control) - { - other.pointer = nullptr; - other.control = nullptr; - } - - /** - * @brief Copy assignment operator. Replaces the managed object with the one managed by r. Shares ownership of the - * object managed by r. If r manages no object, *this manages no object too. Equivalent to - * shared_ptr(r).swap(*this). - * - * @param other Another smart pointer to share the ownership with. - * @return Reference to this shared pointer. - */ - auto operator=(shared_ptr const & other) -> shared_ptr & - { - if (this != &other) - { - cleanup(); - pointer = other.pointer; - control = other.control; - - if (control != nullptr) - { - ++(control->shared_count); - } - } - - return *this; - } - - /** - * @brief Converting copy assignment for compatible shared_ptr types. - * - * @tparam U Source pointer element type. - * @param other Another smart pointer to share ownership with. - * @return Reference to this shared pointer. - */ - template - requires(std::is_convertible_v) - auto operator=(shared_ptr const & other) -> shared_ptr & - { - cleanup(); - pointer = other.pointer; - control = other.control; - - if (control != nullptr) - { - ++(control->shared_count); - } - - return *this; - } - - /** - * @brief Move assignment operator. Move-assigns a shared_ptr from r. After the assignment, *this contains a copy of - * the previous state of r, and r is empty. Equivalent to shared_ptr(std::move(r)).swap(*this). - * - * @param other Another smart pointer to acquire the ownership from. - * @return Reference to this shared pointer. - */ - auto operator=(shared_ptr && other) noexcept -> shared_ptr & - { - if (this != &other) - { - cleanup(); - pointer = other.pointer; - control = other.control; - other.pointer = nullptr; - other.control = nullptr; - } - - return *this; - } - - /** - * @brief Converting move assignment for compatible shared_ptr types. - * - * @tparam U Source pointer element type. - * @param other Another smart pointer to acquire ownership from. - * @return Reference to this shared pointer. - */ - template - requires(std::is_convertible_v) - auto operator=(shared_ptr && other) noexcept -> shared_ptr & - { - cleanup(); - pointer = other.pointer; - control = other.control; - other.pointer = nullptr; - other.control = nullptr; - - return *this; - } - - /** - * @brief Reset this shared_ptr to empty via nullptr assignment. - */ - auto operator=(std::nullptr_t) noexcept -> shared_ptr & - { - cleanup(); - pointer = nullptr; - control = nullptr; - return *this; - } - - /** - * @brief Destructor. Cleans up resources if necessary. - */ - ~shared_ptr() - { - cleanup(); - } - - /** - * @brief Replaces the managed object. - * - * @param ptr Pointer to a new object to manage (default = nullptr). - */ - void reset(T * ptr = nullptr) - { - cleanup(); - pointer = ptr; - control = ptr != nullptr ? new shared_control_block() : nullptr; - assign_enable_shared_from_this(ptr); - } - - /** - * @brief Exchanges the stored pointer values and the ownerships of *this and r. Reference counts, if any, are not - * adjusted. - * - * @param other The shared_ptr to swap with. - */ - void swap(shared_ptr & other) - { - std::swap(pointer, other.pointer); - std::swap(control, other.control); - } - - /** - * @brief Dereference operator. If get() is a null pointer, the behavior is undefined. - * - * @return Returns the object owned by *this, equivalent to *get(). - */ - [[nodiscard]] auto operator*() const -> T & - { - return *pointer; - } - - /** - * @brief Member access operator. - * - * @return Returns a pointer to the object owned by *this, i.e. get(). - */ - [[nodiscard]] auto operator->() const -> T * - { - return pointer; - } - - /** - * @brief Returns a pointer to the managed object or nullptr if no object is owned. - * - * @return Pointer to the managed object or nullptr if no object is owned. - */ - [[nodiscard]] auto get() const -> T * - { - return pointer; - } - - /** - * @brief Returns the number of different shared_ptr instances (*this included) managing the current object. If - * there is no managed object, ​0​ is returned. - * - * @note Common use cases include comparison with ​0​. If use_count returns zero, the shared pointer is empty - * and manages no objects (whether or not its stored pointer is nullptr). Comparison with 1. If use_count returns 1, - * there are no other owners. - * - * @return The number of Shared_pointer instances managing the current object or ​0​ if there is no managed - * object. - */ - [[nodiscard]] auto use_count() const -> std::size_t - { - if (control != nullptr) - { - return control->shared_count; - } - - return 0; - } - - /** - * @brief Checks whether *this owns an object, i.e. whether get() != nullptr. - * - * @return true if *this owns an object, false otherwise. - */ - [[nodiscard]] explicit operator bool() const - { - return pointer != nullptr; - } - - /** - * @brief Compare shared_ptr with nullptr. - */ - [[nodiscard]] auto operator==(std::nullptr_t) const -> bool - { - return pointer == nullptr; - } - - /** - * @brief Compare nullptr with shared_ptr. - */ - [[nodiscard]] friend auto operator==(std::nullptr_t, shared_ptr const & ptr) -> bool - { - return ptr.pointer == nullptr; - } - - private: - /** - * @brief If the candidate type inherits from enable_shared_from_this, assigns the internal weak pointer to this - * shared_ptr. This weak_ptr is used to implement shared_from_this functionality for the candidate type. If the - * candidate type does not inherit from enable_shared_from_this, this function does nothing. - * - * @tparam U The candidate type to check for enable_shared_from_this inheritance. - * @param candidate The candidate object to assign the internal weak pointer for. - */ - template - auto assign_enable_shared_from_this(U * candidate) -> void - { - if constexpr (requires(U * p, shared_ptr const & sp) { p->internal_assign_ptr(sp); }) - { - if (candidate != nullptr) - { - candidate->internal_assign_ptr(*this); - } - } - } - - /** - * @brief Releases ownership and deletes the object if this was the last reference to the owned managed object. - */ - auto cleanup() -> void - { - if (control != nullptr) - { - if (--(control->shared_count) == 0) - { - delete pointer; - pointer = nullptr; - - if (control->weak_count == 0) - { - delete control; - } - } - } - } - - T * pointer; ///< The managed object. - shared_control_block * control; ///< Shared control block. - }; - - /** - * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls - * lhs.swap(rhs). - * - * @tparam T Type of the managed object. - * @param lhs, rhs Smart pointers whose contents to swap. - */ - template - auto swap(shared_ptr & lhs, shared_ptr & rhs) -> void - { - lhs.swap(rhs); - } - - /** - * @brief Constructs an object of type T and wraps it in a shared_ptr. Constructs a non-array type T. The - * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is - * not an array type. The function is equivalent to: shared_ptr(new T(std::forward(args)...)). - * - * @tparam T Type of the managed object. - * @tparam Args Argument types for T's constructor. - * @param args List of arguments with which an instance of T will be constructed. - * @returns Shared_pointer of an instance of type T. - */ - template - auto make_shared(Args &&... args) -> shared_ptr - { - return shared_ptr(new T(std::forward(args)...)); - } - - /** - * @brief Equality operator for shared_ptr. Two shared_ptr instances are equal if they point to the same object - * @tparam T, U Types of the managed objects of the shared_ptr instances being compared. - * @param lhs, rhs The shared_ptr instances to compare. - * @return true if lhs and rhs point to the same object, false otherwise. - */ - template - [[nodiscard]] auto inline operator==(shared_ptr const & lhs, shared_ptr const & rhs) -> bool - { - return lhs.get() == rhs.get(); - } - - /** - * @brief Three-way comparison operator for shared_ptr. Compares the stored pointers of lhs and rhs using operator<=>. - * @tparam T, U Types of the managed objects of the shared_ptr instances being compared. - * @param lhs, rhs The shared_ptr instances to compare. - * @return The result of comparing the stored pointers of lhs and rhs using operator<=> - */ - template - [[nodiscard]] auto inline operator<=>(shared_ptr const & lhs, shared_ptr const & rhs) - { - return lhs.get() <=> rhs.get(); - } -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/unique_ptr.hpp b/libs/kstd/include/kstd/bits/unique_ptr.hpp deleted file mode 100644 index 3d803b4..0000000 --- a/libs/kstd/include/kstd/bits/unique_ptr.hpp +++ /dev/null @@ -1,199 +0,0 @@ -#ifndef KSTD_BITS_UNIQUE_POINTER_HPP -#define KSTD_BITS_UNIQUE_POINTER_HPP - -#include - -// IWYU pragma: private, include - -namespace kstd -{ - /** - * @brief Unique_pointer is a smart pointer that owns (is responsible for) and manages another object via a pointer - * and subsequently disposes of that object when the unique_ptr goes out of scope. - * - * @tparam T Type of the managed object. - */ - template - struct unique_ptr - { - template - friend struct unique_ptr; - - /** - * @brief Constructor. - * - * @param ptr A pointer to an object to manage (default is nullptr). - */ - explicit unique_ptr(T * ptr = nullptr) - : pointer(ptr) - { - // Nothing to do. - } - - /** - * @brief Destructor that deletes the managed object. - */ - ~unique_ptr() - { - delete pointer; - } - - /** - * @brief Deleted copy constructor to enforce unique ownership. - */ - unique_ptr(unique_ptr const &) = delete; - - template - requires(std::is_convertible_v) - unique_ptr(unique_ptr && other) noexcept - : pointer{std::exchange(other.pointer, nullptr)} - {} - - /** - * @brief Deleted copy assignment operator to enforce unique ownership. - */ - auto operator=(unique_ptr const &) -> unique_ptr & = delete; - - /** - * @brief Move constructor. - * - * @param other Unique pointer to move from. - */ - unique_ptr(unique_ptr && other) noexcept - : pointer{std::exchange(other.pointer, nullptr)} - {} - - /** - * @brief Move assignment operator. Transfers ownership from other to *this as if by calling reset(r.release()). - * - * @param other Smart pointer from which ownership will be transferred. - * @return Reference to this unique pointer. - */ - auto operator=(unique_ptr && other) noexcept -> unique_ptr & - { - if (this != &other) - { - delete pointer; - pointer = std::exchange(other.pointer, nullptr); - } - return *this; - } - - /** - * @brief Dereference operator. If get() is a null pointer, the behavior is undefined. - * - * @return Returns the object owned by *this, equivalent to *get(). - */ - auto operator*() const -> T & - { - return *pointer; - } - - /** - * @brief Member access operator. - * - * @return Returns a pointer to the object owned by *this, i.e. get(). - */ - auto operator->() const -> T * - { - return pointer; - } - - /** - * @brief Returns a pointer to the managed object or nullptr if no object is owned. - * - * @return Pointer to the managed object or nullptr if no object is owned. - */ - [[nodiscard]] auto get() const -> T * - { - return pointer; - } - - /** - * @brief Checks whether *this owns an object, i.e. whether get() != nullptr. - * - * @return true if *this owns an object, false otherwise. - */ - explicit operator bool() const noexcept - { - return pointer != nullptr; - } - - /** - * @brief Releases the ownership of the managed object, if any. - * get() returns nullptr after the call. - * The caller is responsible for cleaning up the object (e.g. by use of get_deleter()). - * - * @return Pointer to the managed object or nullptr if there was no managed object, i.e. the value which would be - * returned by get() before the call. - */ - auto release() -> T * - { - return std::exchange(pointer, nullptr); - } - - /** - * @brief Replaces the managed object. - * - * @note A test for self-reset, i.e. whether ptr points to an object already managed by *this, is not performed, - * except where provided as a compiler extension or as a debugging assert. Note that code such as - * p.reset(p.release()) does not involve self-reset, only code like p.reset(p.get()) does. - * - * @param ptr Pointer to a new object to manage (default = nullptr). - */ - auto reset(T * ptr = nullptr) -> void - { - delete std::exchange(pointer, ptr); - } - - /** - * @brief Swaps the managed objects and associated deleters of *this and another unique_ptr object other. - * - * @param other Another unique_ptr object to swap the managed object and the deleter with. - */ - auto swap(unique_ptr & other) -> void - { - using std::swap; - swap(pointer, other.pointer); - } - - /** - * @brief Defaulted three-way comparator operator. - */ - auto operator<=>(unique_ptr const & other) const = default; - - private: - T * pointer; ///< The managed pointer. - }; - - /** - * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls - * lhs.swap(rhs). - * - * @tparam T Type of the managed object. - * @param lhs, rhs Smart pointers whose contents to swap. - */ - template - auto swap(unique_ptr & lhs, unique_ptr & rhs) -> void - { - lhs.swap(rhs); - } - - /** - * @brief Constructs an object of type T and wraps it in a unique_ptr. Constructs a non-array type T. The - * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is - * not an array type. The function is equivalent to: unique_ptr(new T(std::forward(args)...)). - * - * @tparam T Type of the managed object. - * @tparam Args Argument types for T's constructor. - * @param args List of arguments with which an instance of T will be constructed. - * @returns Unique_pointer of an instance of type T. - */ - template - auto make_unique(Args &&... args) -> unique_ptr - { - return unique_ptr(new T(std::forward(args)...)); - } -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/cstring b/libs/kstd/include/kstd/cstring deleted file mode 100644 index bd8b28d..0000000 --- a/libs/kstd/include/kstd/cstring +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef KSTD_CSTRING -#define KSTD_CSTRING - -#include - -namespace kstd::libc -{ - - extern "C" - { - auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void *; - auto memset(void * dest, int value, std::size_t size) noexcept -> void *; - auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void *; - auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int; - - auto strlen(char const * string) noexcept -> std::size_t; - } - -} // namespace kstd::libc - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/ext/bitfield_enum b/libs/kstd/include/kstd/ext/bitfield_enum deleted file mode 100644 index 80fe9d2..0000000 --- a/libs/kstd/include/kstd/ext/bitfield_enum +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef KSTD_EXT_BITFIELD_ENUM_HPP -#define KSTD_EXT_BITFIELD_ENUM_HPP - -#include -#include -#include - -namespace kstd::ext -{ - - template - requires std::is_enum_v - struct is_bitfield_enum : std::false_type - { - }; - - //! @concept Specifies that an enum is to be used to define bits in a bitfield. - template - concept bitfield_enum = is_bitfield_enum::value; - -}; // namespace kstd::ext - -template -constexpr auto operator|(EnumType lhs, EnumType rhs) -> EnumType -{ - return std::bit_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); -} - -template -constexpr auto operator|=(EnumType & lhs, EnumType rhs) -> EnumType & -{ - return lhs = lhs | rhs; -} - -template -constexpr auto operator&(EnumType lhs, EnumType rhs) -> EnumType -{ - return std::bit_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); -} - -template -constexpr auto operator&=(EnumType & lhs, EnumType rhs) -> EnumType & -{ - return lhs = lhs & rhs; -} - -template -constexpr auto operator^(EnumType lhs, EnumType rhs) -> EnumType -{ - return std::bit_cast(std::to_underlying(lhs) ^ std::to_underlying(rhs)); -} - -template -constexpr auto operator^=(EnumType & lhs, EnumType rhs) -> EnumType & -{ - return lhs = lhs ^ rhs; -} - -template -constexpr auto operator~(EnumType lhs) -> EnumType -{ - return std::bit_cast(~std::to_underlying(lhs)); -} - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/flat_map b/libs/kstd/include/kstd/flat_map deleted file mode 100644 index f12b1b5..0000000 --- a/libs/kstd/include/kstd/flat_map +++ /dev/null @@ -1,365 +0,0 @@ -#ifndef KSTD_FLAT_MAP_HPP -#define KSTD_FLAT_MAP_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace kstd -{ - - template, - typename KeyContainerType = kstd::vector, typename MappedContainerType = kstd::vector> - struct flat_map - { - using key_container_type = KeyContainerType; - using mapped_container_type = MappedContainerType; - using key_type = KeyType; - using mapped_type = MappedType; - using value_type = std::pair; - using key_compare = KeyCompare; - using reference = std::pair; - using const_reference = std::pair; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using iterator = bits::flat_map_iterator; - using const_iterator = - bits::flat_map_iterator; - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; - using containers = struct - { - key_container_type keys; - mapped_container_type values; - }; - - //! Compare two object of type value_type. - struct value_compare - { - constexpr auto operator()(const_reference lhs, const_reference rhs) const -> bool - { - return lhs.first < rhs.first; - } - }; - - //! Construct an empty flat map. - constexpr flat_map() - : flat_map{key_compare{}} - {} - - //! Construct an empty flat map using the given custom comparator. - //! - //! @param comparator The comparator to use for comparing keys. - constexpr explicit flat_map(key_compare const & comparator) - : m_containers{} - , m_comparator{comparator} - {} - - //! Get a reference to the mapped value associated with the given key. - //! - //! @warning This function will panic if the key is not found. - //! @param key The key to look up. - //! @return A reference to the mapped value. - [[nodiscard]] constexpr auto at(key_type const & key) -> mapped_type & - { - auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) - { - auto offset = std::distance(m_containers.keys.begin(), found); - return *(m_containers.values.begin() + offset); - } - os::panic("[kstd::flat_map] Key not found"); - } - - //! Get a reference to the mapped value associated with the given key. - //! - //! @warning This function will panic if the key is not found. - //! @param key The key to look up. - //! @return A const reference to the mapped value. - [[nodiscard]] constexpr auto at(key_type const & key) const -> mapped_type const & - { - auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) - { - auto offset = std::distance(m_containers.keys.cbegin(), found); - return *(m_containers.values.cbegin() + offset); - } - os::panic("[kstd::flat_map] Key not found"); - } - - //! Get a reference to the mapped value associated with the given key. - //! - //! @note This overload only participates in overload resolution if the key compare type is transparent. - //! @warning This function will panic if the key is not found. - //! @param x The key to look up. - //! @return A reference to the mapped value. - template - requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } - [[nodiscard]] constexpr auto at(K const & x) -> mapped_type & - { - auto found = find(x); - if (found != end()) - { - auto offset = std::distance(m_containers.keys.begin(), found.key_iterator()); - return *(m_containers.values.begin() + offset); - } - os::panic("[kstd::flat_map] Key not found"); - } - - //! Get a reference to the mapped value associated with the given key. - //! @note This overload only participates in overload resolution if the key compare type is transparent. - //! @warning This function will panic if the key is not found. - //! @param x The key to look up. - //! @return A const reference to the mapped value. - template - requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } - [[nodiscard]] auto at(K const & x) const -> mapped_type const & - { - auto found = find(x); - if (found != end()) - { - auto offset = std::distance(m_containers.keys.cbegin(), found.key_iterator()); - return *(m_containers.values.cbegin() + offset); - } - os::panic("[kstd::flat_map] Key not found"); - } - - //! Get an iterator to the first element. - [[nodiscard]] auto begin() noexcept -> iterator - { - return iterator{m_containers.keys.begin(), m_containers.values.begin()}; - } - - //! Get an iterator to the first element. - [[nodiscard]] auto begin() const noexcept -> const_iterator - { - return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()}; - } - - //! Get an iterator to the first element. - [[nodiscard]] auto cbegin() const noexcept -> const_iterator - { - return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto end() noexcept -> iterator - { - return iterator{m_containers.keys.end(), m_containers.values.end()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto end() const noexcept -> const_iterator - { - return const_iterator{m_containers.keys.cend(), m_containers.values.cend()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto cend() const noexcept -> const_iterator - { - return const_iterator{m_containers.keys.cend(), m_containers.values.cend()}; - } - - //! Get an iterator to the first element. - [[nodiscard]] auto rbegin() noexcept -> reverse_iterator - { - return reverse_iterator{end()}; - } - - //! Get an iterator to the first element. - [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator - { - return const_reverse_iterator{cend()}; - } - - //! Get an iterator to the first element. - [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator - { - return const_reverse_iterator{cend()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto rend() noexcept -> reverse_iterator - { - return reverse_iterator{begin()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator - { - return const_reverse_iterator{cbegin()}; - } - - //! Get an iterator to the element past the last element. - [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator - { - return const_reverse_iterator{cbegin()}; - } - - //! Check whether this flat map is empty or not - [[nodiscard]] auto empty() const noexcept -> bool - { - return m_containers.keys.empty(); - } - - //! Get the number of elements in this flat map. - [[nodiscard]] auto size() const noexcept -> size_type - { - return m_containers.keys.size(); - } - - //! Get the maximum number of elements possible in this flat map - [[nodiscard]] auto max_size() const noexcept -> size_type - { - return std::min(m_containers.keys.max_size(), m_containers.values.max_size()); - } - - //! Try to insert a new key-value pair into the map. - //! - //! @param args Arguments to use for constructing the key-value pair. - //! @return A pair of an iterator to the inserted element and a boolean indicating whether the insertion took place. - template - auto emplace(Args &&... args) -> std::pair - requires std::constructible_from - { - auto value = value_type{std::forward(args)...}; - auto found = std::ranges::lower_bound(m_containers.keys, value.first, m_comparator); - - if (found != m_containers.keys.cend() && !m_comparator(value.first, *found) && !m_comparator(*found, value.first)) - { - auto offset = std::distance(m_containers.keys.begin(), found); - return { - iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}, - false - }; - } - - auto offset = std::distance(m_containers.keys.begin(), found); - auto key_iterator = m_containers.keys.begin() + offset; - auto mapped_iterator = m_containers.values.begin() + offset; - - auto inserted_key = m_containers.keys.insert(key_iterator, std::move(value.first)); - auto inserted_mapped = m_containers.values.insert(mapped_iterator, std::move(value.second)); - - return { - iterator{inserted_key, inserted_mapped}, - true - }; - } - - //! Try to insert a element for the given key into this map. - //! - //! This function does nothing if the key is already present. - //! - //! @param key The key to insert a value for. - //! @param args The arguments to use to construct the mapped value. - template - auto try_emplace(key_type const & key, Args &&... args) -> std::pair - { - auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(*found, key) && !m_comparator(key, *found)) - { - return {found, false}; - } - - auto offset = std::distance(m_containers.keys.cbegin(), found); - auto intersertion_point = m_containers.value.begin() + offset; - - auto inserted_key = m_containers.keys.emplace(key); - auto inserted_mapped = m_containers.values.emplace(std::forward(args)...); - - return { - iterator{inserted_key, inserted_mapped}, - true - }; - } - - //! Find an element with an equivalent key. - //! - //! @param key The key to look up. - //! @return An iterator to the element with the equivalent key, or end() if no such element is found. - [[nodiscard]] auto find(key_type const & key) noexcept -> iterator - { - auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) - { - auto offset = std::distance(m_containers.keys.begin(), found); - return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}; - } - return end(); - } - - //! Find an element with an equivalent key. - //! - //! @param key The key to look up. - //! @return An iterator to the element with the equivalent key, or end() if no such element is found. - [[nodiscard]] auto find(key_type const & key) const noexcept -> const_iterator - { - auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) - { - auto offset = std::distance(m_containers.keys.cbegin(), found); - return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset}; - } - return cend(); - } - - //! Find an element with an equivalent key. - //! @note This overload only participates in overload resolution if the key compare type is transparent. - //! @param x The key to look up. - //! @return An iterator to the element with the equivalent key, or end() if no such element is found. - template - requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } - [[nodiscard]] auto find(K const & x) noexcept -> iterator - { - auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x)) - { - auto offset = std::distance(m_containers.keys.begin(), found); - return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}; - } - return end(); - } - - //! Find an element with an equivalent key. - //! @note This overload only participates in overload resolution if the key compare type is transparent. - //! @param x The key to look up. - //! @return An iterator to the element with the equivalent key, or end() if no such element is found. - template - requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } - [[nodiscard]] auto find(K const & x) const noexcept -> const_iterator - { - auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator); - if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x)) - { - auto offset = std::distance(m_containers.keys.cbegin(), found); - return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset}; - } - return cend(); - } - - //! Check if the map contains the given key. - //! - //! @param key The key to check. - //! @return true iff. the key is found, false otherwise. - [[nodiscard]] constexpr auto contains(key_type const & key) const noexcept -> bool - { - return find(key) != cend(); - } - - private: - containers m_containers; - key_compare m_comparator; - }; -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format deleted file mode 100644 index e04b79a..0000000 --- a/libs/kstd/include/kstd/format +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef KSTD_FORMAT_HPP -#define KSTD_FORMAT_HPP - -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/memory b/libs/kstd/include/kstd/memory deleted file mode 100644 index f108c6d..0000000 --- a/libs/kstd/include/kstd/memory +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef KSTD_MEMORY_HPP -#define KSTD_MEMORY_HPP - -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/mutex b/libs/kstd/include/kstd/mutex deleted file mode 100644 index b2a31aa..0000000 --- a/libs/kstd/include/kstd/mutex +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef KSTD_MUTEX_HPP -#define KSTD_MUTEX_HPP - -#include - -namespace kstd -{ - - //! A non-recursive mutex. - struct mutex - { - mutex(); - ~mutex(); - - mutex(mutex const &) = delete; - mutex(mutex &&) = delete; - - auto operator=(mutex const &) -> mutex & = delete; - auto operator=(mutex &&) -> mutex & = delete; - - //! Lock the mutex. - //! - //! @note This function blocks for as long as the mutex is not available. - auto lock() -> void; - - //! Try to lock the mutex. - //! - //! @note This function never blocks. - //! @return @p true iff. the mutex was successfully locked, @p false otherwise. - auto try_lock() -> bool; - - //! Unlock the mutex. - //! - //! @note The behavior is undefined if the mutex is not currently held by the thread unlocking it. - auto unlock() -> void; - - private: - std::atomic_flag m_locked{}; - }; - - //! A tag type to specify that a given @p lockable wrapper should adopt ownership of the @p lockable. - struct adopt_lock_t - { - explicit adopt_lock_t() = default; - } constexpr inline adopt_lock{}; - - //! A tag type to specify that a given @p lockable wrapper should defer locking the @p lockable. - struct defer_lock_t - { - explicit defer_lock_t() = default; - } constexpr inline defer_lock{}; - - //! A tag type to specify that a given @p lockable wrapper should attempt to lock the @p lockable. - struct try_to_lock_t - { - explicit try_to_lock_t() = default; - } constexpr inline try_to_lock{}; - - //! An RAII wrapper for a single @p lockable like a kstd::mutex. - template - struct lock_guard - { - using mutex_type = MutexType; - - //! Construct a new lock_guard and immediately lock the given mutex. - //! - //! @note This function will block until the mutex was successfully locked. - //! @param mutex The mutex to lock. - lock_guard(MutexType & mutex) noexcept - : m_mutex(mutex) - { - m_mutex.lock(); - } - - //! Construct a new lock_guard and take ownership of the given mutex. - //! - //! @note The behavior is undefined if the mutex is not owned by the current thread. - //! @param mutex The mutex to take ownership of. - lock_guard(MutexType & mutex, adopt_lock_t) noexcept - : m_mutex(mutex) - {} - - //! Destroy this lock_guard and release the owned mutex. - ~lock_guard() - { - m_mutex.unlock(); - } - - lock_guard(lock_guard const &) noexcept = delete; - lock_guard(lock_guard &&) noexcept = delete; - - auto operator=(lock_guard const &) noexcept -> lock_guard & = delete; - auto operator=(lock_guard &&) noexcept -> lock_guard & = delete; - - private: - mutex_type & m_mutex; - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/os/error.hpp b/libs/kstd/include/kstd/os/error.hpp deleted file mode 100644 index 9d43fb1..0000000 --- a/libs/kstd/include/kstd/os/error.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef KSTD_OS_ERROR_HPP -#define KSTD_OS_ERROR_HPP - -#include -#include - -namespace kstd::os -{ - /** - * @brief Handle an unrecoverable library error. - * - * The operating system kernel may choose to implement this function in order to try to perform additional cleanup - * before terminating execution. If the kernel does not implement this function, the default implementation will be - * chosen. This default implementation doest nothing. - */ - [[noreturn]] - auto abort() -> void; - - /** - * @brief Terminate execution of the operating system. - * - * The operating system must implement this function. This function must terminate the execution of the operating - * system kernel as is. It may choose to restart the kernel or to halt execution entirely. The implementation must - * guarantee that execution never return from this function. - * - * @param message A message describing the reason for termination of execution. - * @param where The source code location at which the panic was triggered. In general, no argument shall be provided - * for this parameter, thus implicitly capturing the location at which the call originates. - */ - [[noreturn]] - auto panic(std::string_view message, std::source_location where = std::source_location::current()) -> void; -} // namespace kstd::os - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/os/print.hpp b/libs/kstd/include/kstd/os/print.hpp deleted file mode 100644 index 36cb43d..0000000 --- a/libs/kstd/include/kstd/os/print.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef KSTD_OS_PRINT_HPP -#define KSTD_OS_PRINT_HPP - -#include -#include - -#include - -namespace kstd::os -{ - auto vprint(print_sink sink, std::string_view format, kstd::format_args args) -> void; -} // namespace kstd::os - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/print b/libs/kstd/include/kstd/print deleted file mode 100644 index 1033f72..0000000 --- a/libs/kstd/include/kstd/print +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef KSTD_PRINT -#define KSTD_PRINT - -#include // IWYU pragma: export -#include -#include - -#include - -namespace kstd -{ - - //! @qualifier kernel-defined - //! Format the given string using the given arguments and print it to the currently active output device. - //! - //! @param format The format string - //! @param args The arguments to use to place in the format string's placeholders. - template - // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) - auto print(kstd::format_string...> format, Args const &... args) -> void - { - auto const arg_store = kstd::make_format_args(args...); - os::vprint(print_sink::stdout, format.str_view, arg_store.args); - } - - //! @qualifier kernel-defined - //! Format the given error string using the given arguments and print it to the currently active output device. - //! - //! @param format The format string - //! @param args The arguments to use to place in the format string's placeholders. - template - // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) - auto print(print_sink sink, kstd::format_string...> format, Args &&... args) -> void - { - auto const arg_store = kstd::make_format_args(args...); - os::vprint(sink, format.str_view, arg_store.args); - } - - //! @qualifier kernel-defined - //! Format the given string using the given arguments and print it, including a newline, to the currently active - //! output device. - //! - //! @param format The format string - //! @param args The arguments to use to place in the format string's placeholders. - template - // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) - auto println(kstd::format_string...> format, Args &&... args) -> void - { - print(print_sink::stdout, format, std::forward(args)...); - print(print_sink::stdout, "\n"); - } - - //! @qualifier kernel-defined - //! Format the given error string using the given arguments and print it, including a newline, to the currently active - //! output device. - //! - //! @param format The format string - //! @param args The arguments - template - // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) - auto println(print_sink sink, kstd::format_string...> format, Args &&... args) -> void - { - print(sink, format, std::forward(args)...); - print(sink, "\n"); - } - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/ranges b/libs/kstd/include/kstd/ranges deleted file mode 100644 index 78c3adb..0000000 --- a/libs/kstd/include/kstd/ranges +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef KSTD_RANGES -#define KSTD_RANGES - -#include // 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/stack b/libs/kstd/include/kstd/stack deleted file mode 100644 index 02e44ea..0000000 --- a/libs/kstd/include/kstd/stack +++ /dev/null @@ -1,192 +0,0 @@ -#ifndef KSTD_STACK_HPP -#define KSTD_STACK_HPP - -#include - -#include -#include - -namespace kstd -{ - /** - * @brief Custom stack implementation mirroring the std::stack to allow for the usage of STL functionality with our - * custom memory management. - * - * @tparam T Element the stack instance should contain. - * @tparam Container Actual underlying container that should be wrapped to provide stack functionality. Requires - * access to pop_back(), push_back(), back(), size(), empty() and emplace_back() - */ - template> - struct stack - { - using container_type = Container; ///< Type of the underlying container used to implement stack-like interface. - using value_type = Container::value_type; ///< Type of the elements contained in the underlying container. - using size_type = Container::size_type; ///< Type of the size in the underlying container. - using reference = Container::reference; ///< Type of reference to the elements. - using const_reference = Container::const_reference; ///< Type of constant reference to the elements. - - /** - * @brief Default Constructor. - */ - stack() = default; - - stack(stack const &) = delete; - stack(stack &&) = delete; - auto operator=(stack const &) -> stack & = delete; - auto operator=(stack &&) -> stack & = delete; - /** - * @brief Constructs data with the given amount of elements containing the given value or alternatively the default - * constructed value. - * - * @param n Amount of elements we want to create and set the given value for. - * @param initial Inital value of all elements in the underlying data array. - */ - explicit stack(size_type n, value_type initial = value_type{}) - : _container(n, initial) - { - // Nothing to do. - } - - /** - * @brief Constructs data by copying all element from the given exclusive range. - * - * @tparam InputIterator Template that should have atleast input iterator characteristics. - * @param first Input iterator to the first element in the range we want to copy from. - * @param last Input iterator to one past the last element in the range we want to copy from. - */ - template - explicit stack(InputIterator first, InputIterator last) - : _container(first, last) - { - // Nothing to do. - } - - /** - * @brief Construct data by copying all elements from the initializer list. - * - * @param elements List we want to copy all elements from. - */ - explicit stack(std::initializer_list elements) - : _container(elements) - { - // Nothing to do. - } - - /** - * @brief Copy constructor. - * - * @note Allocates underlying data container with the same capacity as stack we are copying from and copies all - * elements from it. - * - * @param other Other instance of stack we want to copy the data from. - */ - stack(stack const & other) - : _container(other) - { - // Nothing to do. - } - - /** - * @brief Destructor. - */ - ~stack() = default; - - /** - * @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. - */ - auto size() const -> size_type - { - return _container.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 top() -> reference - { - return _container.back(); - } - - /** - * @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 top() const -> const_reference - { - return _container.back(); - } - - /** - * @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(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 - auto push(U && value) -> void - { - _container.push_back(std::forward(value)); - } - - /** - * @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).... - * - * 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 - auto emplace(Args &&... args) -> reference - { - _container.emplace_back(std::forward(args)...); - } - - /** - * @brief Removes the last element of the container. - * - * @note 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() -> void - { - _container.pop_back(); - } - - /** - * @brief Whether there are currently any items this container or not. - * - * @return True if there are no elements, false if there are. - */ - auto empty() const -> bool - { - return _container.empty(); - } - - private: - container_type _container = {}; ///< Underlying container used by the stack to actually save the data. - }; - -} // namespace kstd - -#endif diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string deleted file mode 100644 index e228a04..0000000 --- a/libs/kstd/include/kstd/string +++ /dev/null @@ -1,380 +0,0 @@ -#ifndef KSTD_STRING_HPP -#define KSTD_STRING_HPP - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace kstd -{ - /** - * @brief A simple string implementation that owns its data and provides basic operations. - */ - struct string - { - //! The type of the characters contained in this string. - using value_type = char; - //! The type of the underlying storage used by this string. - using storage_type = kstd::vector; - //! The type of all sizes used in and with this string. - using size_type = std::size_t; - //! The type of the difference between two iterators. - using difference_type = std::ptrdiff_t; - //! The type of references to single values in this string. - using reference = value_type &; - //! The type of references to constant single values in this string. - using const_reference = value_type const &; - //! The type of pointers to single values in this string. - using pointer = value_type *; - //! The type of pointers to constant single values in this string. - using const_pointer = value_type const *; - //! The type of iterators into this string. - using iterator = pointer; - //! The type of constant iterators into this string. - using const_iterator = const_pointer; - - /** - * @brief Constructs an empty null-terminated string. - */ - string() - : m_storage{value_type{'\0'}} - {} - - /** - * @brief Constructs a string from a string view by copying the characters into owned storage. - * @param view The string view to copy the characters from. - */ - string(std::string_view view) - : string() - { - append(view); - } - - /** - * @brief Constructs a string from a null-terminated C-style string by copying the characters into owned storage. - * @param c_str The null-terminated C-style string to copy. - */ - string(char const * c_str) - : string() - { - if (c_str != nullptr) - { - append(std::string_view{c_str}); - } - } - - /** - * @brief Constructs a string containing a single character. - * @param c The character to copy. - */ - string(value_type c) - : string() - { - push_back(c); - } - - /** - * @brief Constructs a string by copying another string. - * @param other The string to copy. - */ - constexpr string(string const & other) - : m_storage{other.m_storage} - {} - - /** - * @brief Destructs the string. - */ - constexpr ~string() = default; - - /** - * @brief Assigns the value of another string to this string. - * @param other The string to assign from. - * @return A reference to this string. - */ - constexpr auto operator=(string const & other) -> string & = default; - - /** - * @brief Returns the number of characters in this string, not including the null terminator. - */ - [[nodiscard]] constexpr auto size() const noexcept -> size_type - { - return m_storage.empty() ? 0 : m_storage.size() - 1; - } - - /** - * @brief Checks if this string is empty, not including the null terminator. - */ - [[nodiscard]] constexpr auto empty() const noexcept -> bool - { - return size() == 0; - } - - /** - * @brief Clears the content of the string, resulting in an empty string. - * The string remains null-terminated after this operation. - */ - constexpr auto clear() -> void - { - m_storage.clear(); - m_storage.push_back(value_type{'\0'}); - } - - //! Get a pointer to the underlying storage of the string - [[nodiscard]] constexpr auto data() noexcept -> pointer - { - return m_storage.data(); - } - - //! Get a const pointer to the underlying storage of the string - [[nodiscard]] constexpr auto data() const noexcept -> const_pointer - { - return m_storage.data(); - } - - //! Get a const pointer to the underlying storage of the string - [[nodiscard]] constexpr auto c_str() const noexcept -> const_pointer - { - return data(); - } - - //! Get an iterator to the beginning of the string - [[nodiscard]] constexpr auto begin() noexcept -> iterator - { - return data(); - } - - //! Get an const iterator to the beginning of the string - [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator - { - return data(); - } - - //! Get an const iterator to the beginning of the string - [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator - { - return begin(); - } - - //! Get an iterator to the end of the string - [[nodiscard]] constexpr auto end() noexcept -> iterator - { - return data() + size(); - } - - //! Get an const iterator to the end of the string - [[nodiscard]] constexpr auto end() const noexcept -> const_iterator - { - return data() + size(); - } - - //! Get an const iterator to the end of the string - [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator - { - return end(); - } - - //! Get a reference to the first character of the string - [[nodiscard]] constexpr auto front() -> reference - { - return m_storage.front(); - } - - //! Get a const reference to the first character of the string - [[nodiscard]] constexpr auto front() const -> const_reference - { - return m_storage.front(); - } - - //! Get a reference to the last character of the string - [[nodiscard]] constexpr auto back() -> reference - { - return m_storage[size() - 1]; - } - - //! Get a const reference to the last character of the string - [[nodiscard]] constexpr auto back() const -> const_reference - { - return m_storage[size() - 1]; - } - - /** - * @brief Appends a character to the end of the string. - * @param ch The character to append. - */ - constexpr auto push_back(value_type ch) -> void - { - m_storage.back() = ch; - m_storage.push_back(value_type{'\0'}); - } - - /** - * @brief Appends a string view to the end of the string by copying the characters into owned storage. - * @param view The string view to append. - * @return A reference to this string. - */ - constexpr auto append(std::string_view view) -> string & - { - if (!view.empty()) - { - std::ranges::for_each(view, [this](auto const ch) { push_back(ch); }); - } - - return *this; - } - - /** - * @brief Appends another string to the end of this string by copying the characters into owned storage. - * @param other The string to append. - * @return A reference to this string. - */ - constexpr auto append(string const & other) -> string & - { - return append(other.view()); - } - - /** - * @brief Appends another string to the end of this string by copying the characters into owned storage. - * @param other The string to append. - * @return A reference to this string. - */ - constexpr auto operator+=(string const & other) -> string & - { - return append(other); - } - - /** - * @brief Appends a character to the end of the string. - * @param ch The character to append. - * @return A reference to this string. - */ - constexpr auto operator+=(value_type ch) -> string & - { - push_back(ch); - return *this; - } - - /** - * @brief Returns a string view of this string, which is a non-owning view into the characters of this string. - */ - [[nodiscard]] constexpr auto view() const noexcept -> std::string_view - { - return std::string_view{data(), size()}; - } - - private: - //! The underlying storage of the string, which owns the characters and ensures null-termination. - storage_type m_storage{}; - }; - - /** - * @brief Concatenates a strings and a character and returns the result as a new string. - * @param lhs The string to concatenate. - * @param rhs The string to concatenate. - * @return A new string that is the result of concatenating @p lhs and @p rhs. - */ - [[nodiscard]] constexpr auto inline operator+(string const & lhs, string const & rhs) -> string - { - string result{lhs}; - result += rhs; - return result; - } - - /** - * @brief Converts an unsigned integer to a string by converting each digit to the corresponding character and - * concatenating them. - * @tparam N The type of the unsigned integer to convert. - * @param value The unsigned integer to convert. - * @return A string representation of the given unsigned integer. - */ - template - requires std::unsigned_integral - [[nodiscard]] constexpr auto inline to_string(N value) -> string - { - if (value == 0) - { - return "0"; - } - - string result; - - while (value > 0) - { - char const digit = '0' + (value % 10); - result.push_back(digit); - value /= 10; - } - - std::reverse(result.begin(), result.end()); - return result; - } - - [[nodiscard]] constexpr auto inline operator==(string const & lhs, string const & rhs) -> bool - { - return lhs.view() == rhs.view(); - } - - [[nodiscard]] constexpr auto inline operator!=(string const & lhs, string const & rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(string const & lhs, std::string_view rhs) -> bool - { - return lhs.view() == rhs; - } - - [[nodiscard]] constexpr auto inline operator!=(string const & lhs, std::string_view rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(std::string_view lhs, string const & rhs) -> bool - { - return lhs == rhs.view(); - } - - [[nodiscard]] constexpr auto inline operator!=(std::string_view lhs, string const & rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(string const & lhs, char const * rhs) -> bool - { - return lhs.view() == std::string_view{rhs}; - } - - [[nodiscard]] constexpr auto inline operator!=(string const & lhs, char const * rhs) -> bool - { - return !(lhs == rhs); - } - - [[nodiscard]] constexpr auto inline operator==(char const * lhs, string const & rhs) -> bool - { - return std::string_view{lhs} == rhs.view(); - } - - [[nodiscard]] constexpr auto inline operator!=(char const * lhs, string const & rhs) -> bool - { - return !(lhs == rhs); - } - - template<> - struct formatter : formatter - { - auto format(string const & str, format_context & context) const -> void - { - formatter::format(str.view(), context); - } - }; - -} // namespace kstd - -#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/units b/libs/kstd/include/kstd/units deleted file mode 100644 index df5eb37..0000000 --- a/libs/kstd/include/kstd/units +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef KSTD_UNITS_HPP -#define KSTD_UNITS_HPP - -#include -#include -#include - -namespace kstd -{ - - //! A basic template for strongly typed units. - template - struct basic_unit - { - using value_type = ValueType; - - constexpr basic_unit() noexcept - : value{} - {} - - explicit constexpr basic_unit(value_type value) noexcept - : value{value} - {} - - explicit constexpr operator value_type() const noexcept - { - return value; - } - - constexpr auto operator+(basic_unit const & other) const noexcept -> basic_unit - { - return basic_unit{value + other.value}; - } - - constexpr auto operator+=(basic_unit const & other) noexcept -> basic_unit & - { - return *this = *this + other; - } - - constexpr auto operator-(basic_unit const & other) const noexcept -> basic_unit - { - return basic_unit{value - other.value}; - } - - constexpr auto operator-=(basic_unit const & other) noexcept -> basic_unit & - { - return *this = *this - other; - } - - constexpr auto operator*(std::integral auto factor) noexcept -> basic_unit - { - return basic_unit{value * factor}; - } - - constexpr auto operator*=(std::integral auto factor) noexcept -> basic_unit - { - return *this = *this * factor; - } - - constexpr auto operator/(std::integral auto divisor) noexcept -> basic_unit - { - return basic_unit{value / divisor}; - } - - constexpr auto operator/=(std::integral auto divisor) noexcept -> basic_unit - { - return *this = *this / divisor; - } - - constexpr auto operator/(basic_unit const & other) const noexcept - { - return value / other.value; - } - - constexpr auto operator<=>(basic_unit const & other) const noexcept -> std::strong_ordering = default; - - value_type value; - }; - - template - constexpr auto operator*(Factor factor, basic_unit const & unit) noexcept - -> basic_unit - { - return basic_unit{unit.value * factor}; - } - - namespace units - { - using bytes = basic_unit; - - constexpr auto KiB(std::size_t value) noexcept -> bytes - { - return bytes{value * 1024}; - } - - constexpr auto MiB(std::size_t value) noexcept -> bytes - { - return bytes{value * 1024 * 1024}; - } - - constexpr auto GiB(std::size_t value) noexcept -> bytes - { - return bytes{value * 1024 * 1024 * 1024}; - } - - template - constexpr auto operator+(ValueType * pointer, bytes offset) -> ValueType * - { - return pointer + offset.value; - } - - } // namespace units - - namespace units_literals - { - constexpr auto operator""_B(unsigned long long value) noexcept -> units::bytes - { - return units::bytes{value}; - } - - constexpr auto operator""_KiB(unsigned long long value) noexcept -> units::bytes - { - return units::KiB(value); - } - - constexpr auto operator""_MiB(unsigned long long value) noexcept -> units::bytes - { - return units::MiB(value); - } - - constexpr auto operator""_GiB(unsigned long long value) noexcept -> units::bytes - { - return units::GiB(value); - } - - } // namespace units_literals - - template - constexpr auto object_size(ValueType const &) -> units::bytes - { - return units::bytes{sizeof(ValueType)}; - } - - template - constexpr auto type_size = units::bytes{sizeof(T)}; - -} // namespace kstd - -#endif diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector deleted file mode 100644 index c714957..0000000 --- a/libs/kstd/include/kstd/vector +++ /dev/null @@ -1,1154 +0,0 @@ -#ifndef KSTD_VECTOR_HPP -#define KSTD_VECTOR_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace kstd -{ - //! A resizable, contiguous container. - //! - //! @tparam ValueType The type of values contained in this vector. - //! @tparam Allocator The type of allocator used for memory management by this container. - template> - struct vector - { - //! The type of the elements contained in this vector. - using value_type = ValueType; - //! The allocator used by this vector for memory management. - using allocator_type = Allocator; - //! The type of all sizes used in and with this vector. - using size_type = std::size_t; - //! The type of the difference between two iterators. - using difference_type = std::ptrdiff_t; - //! The type of references to elements in this vector. - using reference = value_type &; - //! The type of references to constant elements in this vector. - using const_reference = value_type const &; - //! The type of pointers to elements in this vector. - using pointer = std::allocator_traits::pointer; - //! The type of pointers to constant elements in this vector. - using const_pointer = std::allocator_traits::const_pointer; - //! The type of iterators into this container. - using iterator = pointer; - //! The type of constant iterators into this container. - using const_iterator = const_pointer; - //! The type of reverse iterators into this container. - using reverse_iterator = std::reverse_iterator; - //! The type of constant reverse iterators into this container. - using const_reverse_iterator = std::reverse_iterator; - - //! Construct a new, empty vector. - constexpr vector() noexcept(std::is_nothrow_default_constructible_v) - : 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) - : m_allocator{allocator} - , m_size{0} - , m_capacity{0} - , m_data{allocate_n(m_capacity)} - {} - - //! 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) - : 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::construct(m_allocator, m_data + i); - } - } - - //! Construct a new vector and fill it with the given number of copy constructed elements. - //! - //! @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) - : m_allocator{allocator} - , m_size{count} - , m_capacity{m_size} - , m_data{allocate_n(m_capacity)} - { - for (auto i = 0uz; i < count; ++i) - { - std::allocator_traits::construct(m_allocator, m_data + i, value); - } - } - - //! Construct a new vector and initialize it's content by copying all elements in the given range. - //! - //! @tparam ForwardIterator 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 - constexpr vector(ForwardIterator first, ForwardIterator last, - allocator_type const & allocator = - allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) - : m_allocator{allocator} - , m_size{static_cast(std::ranges::distance(first, last))} - , m_capacity{m_size} - , m_data{allocate_n(m_capacity)} - { - for (auto destination = m_data; first != last; ++first, ++destination) - { - std::allocator_traits::construct(m_allocator, destination, *first); - } - } - - //! Construct a new vector and initialize it's content by copying all elements in the given range. - //! - //! @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 - constexpr vector(InputIterator first, InputIterator last, - allocator_type const & allocator = - allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) - : m_allocator{allocator} - , m_size{0} - , m_capacity{0} - , m_data{} - { - while (first != last) - { - emplace_back(*first); - ++first; - } - } - - //! Construct a new vector and initialize it's content by copying all elements in the given range. - //! - //! - template - requires((std::ranges::forward_range || std::ranges::sized_range) && - kstd::bits::container_compatible_range) - 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)) - { - std::allocator_traits::construct(m_allocator, destination++, - std::forward>(element)); - } - } - - //! Construct a new vector and initialize it's content by copying all elements from a given vector. - //! - //! @param other The source vector. - 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)) - { - 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. - constexpr vector(vector && other) noexcept - : 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)) - {} - - //! 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 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 const & allocator) - : m_allocator{allocator} - , m_size{} - , m_capacity{} - , m_data{} - { - if constexpr (!std::allocator_traits::is_always_equal::value) - { - if (m_allocator != other.m_allocator) - { - m_capacity = other.size(); - m_data = allocate_n(capacity()); - m_size = other.size(); - uninitialized_move_with_allocator(other.begin(), begin(), other.size()); - other.clear(); - return; - } - } - 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. - vector(std::initializer_list list, allocator_type const & allocator = allocator_type{}) - : vector{std::ranges::begin(list), std::ranges::end(list), allocator} - {} - - //! Destroy this vector. - constexpr ~vector() - { - clear_and_deallocate(); - } - - //! Replace the contents of this vector by the copying from the given source vector. - //! - //! @param other The source vector. - constexpr auto operator=(vector const & other) -> vector & - { - if (this == std::addressof(other)) - { - return *this; - } - - if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value) - { - if (get_allocator() != other.get_allocator()) - { - clear_and_deallocate(); - } - m_allocator = other.get_allocator(); - } - - if (capacity() >= other.size()) - { - auto const overlap = std::min(m_size, other.m_size); - std::ranges::copy(other.begin(), other.begin() + overlap, begin()); - - if (m_size < other.m_size) - { - uninitialized_copy_with_allocator(other.begin() + size(), begin() + size(), other.m_size - size()); - } - else if (m_size > other.m_size) - { - destroy_n(begin() + other.size(), size() - other.size()); - } - } - else - { - auto new_data = allocate_n(other.size()); - uninitialized_copy_with_allocator(other.begin(), new_data, other.size()); - clear_and_deallocate(); - m_data = new_data; - m_capacity = other.size(); - } - - m_size = other.size(); - return *this; - } - - //! Replace the contents fo this vector by moving from the given source. - //! - //! @param other The source vector. - constexpr auto operator=(vector && other) noexcept( - std::allocator_traits::propagate_on_container_move_assignment::value || - std::allocator_traits::is_always_equal::value) -> vector & - { - using std::swap; - - if (this == std::addressof(other)) - { - return *this; - } - - if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) - { - clear_and_deallocate(); - 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) - { - clear_and_deallocate(); - swap(m_size, other.m_size); - swap(m_capacity, other.m_capacity); - swap(m_data, other.m_data); - } - else - { - if (capacity() >= other.size()) - { - auto const overlap = std::min(size(), other.size()); - std::ranges::move(other.begin(), other.begin() + overlap, begin()); - - if (size() < other.size()) - { - uninitialized_move_with_allocator(other.begin() + size(), begin() + size(), other.size() - size()); - } - else if (size() > other.size()) - { - destroy_n(begin() + other.size(), size() - other.size()); - } - } - else - { - auto new_data = allocate_n(other.size()); - uninitialized_move_with_allocator(other.begin(), new_data, other.size()); - clear_and_deallocate(); - m_data = new_data; - m_capacity = other.m_size; - } - - other.destroy_n(other.begin(), other.size()); - m_size = std::exchange(other.m_size, size_type{}); - } - - return *this; - } - - //! 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 - { - return m_allocator; - } - - //! 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 - { - panic_if_out_of_bounds(index); - return (*this)[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]; - } - - //! 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 - { - return data()[index]; - } - - //! 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 - { - return data()[index]; - } - - //! Get a reference to the first element of this vector. - //! - //! The behavior is undefined if this vector is empty. - [[nodiscard]] constexpr auto front() -> reference - { - return *begin(); - } - - //! Get a reference to the first element of this vector. - //! - //! The behavior is undefined if this vector is empty. - [[nodiscard]] constexpr auto front() const -> const_reference - { - return *begin(); - } - - //! 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 *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(); - } - - //! Get a pointer to the beginning of the underlying contiguous storage. - [[nodiscard]] constexpr auto data() noexcept -> pointer - { - return m_data; - } - - //! Get a pointer to the beginning of the underlying contiguous storage. - [[nodiscard]] constexpr auto data() const noexcept -> const_pointer - { - return m_data; - } - - //! 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 empty() ? end() : data(); - } - - //! 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 empty() ? end() : data(); - } - - //! 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 begin(); - } - - //! Get an iterator past the last element of this vector. - [[nodiscard]] constexpr auto end() noexcept -> pointer - { - return capacity() ? data() + size() : nullptr; - } - - //! Get an iterator past the last element of this vector. - [[nodiscard]] constexpr auto end() const noexcept -> const_pointer - { - return capacity() ? data() + size() : nullptr; - } - - //! Get an iterator past the last element of this vector. - [[nodiscard]] constexpr auto cend() const noexcept -> const_pointer - { - return end(); - } - - //! Get a reverse iterator to the reverse beginning. - [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator - { - return empty() ? rend() : reverse_iterator{end()}; - } - - //! Get a reverse iterator to the reverse beginning. - [[nodiscard]] constexpr auto rbegin() const noexcept -> const_reverse_iterator - { - return empty() ? rend() : const_reverse_iterator{end()}; - } - - //! Get a reverse iterator to the reverse beginning. - [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator - { - return rbegin(); - } - - //! Get a reverse iterator to the reverse end. - [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator - { - return reverse_iterator{begin()}; - } - - //! Get a reverse iterator to the reverse end. - [[nodiscard]] constexpr auto rend() const noexcept -> const_reverse_iterator - { - return const_reverse_iterator{begin()}; - } - - //! Get a reverse iterator to the reverse end. - [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator - { - return rend(); - } - - //! Check whether this vector is empty. - [[nodiscard]] constexpr auto empty() const noexcept -> bool - { - return !size(); - } - - //! Get the number of elements present in this vector. - [[nodiscard]] constexpr auto size() const noexcept -> size_type - { - return m_size; - } - - //! Get the maximum possible number of element for this vector. - [[nodiscard]] constexpr auto max_size() const noexcept -> size_type - { - return std::allocator_traits::max_size(m_allocator); - } - - //! 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."); - } - - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - uninitialized_move_with_allocator(begin(), new_data, size()); - clear_and_deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - - //! Resize this vector to contain @p new_size elements. - constexpr auto resize(size_type new_size) -> void - { - resize(new_size, value_type{}); - } - - //! Resize this vector to contain @p new_size elements, filling new elements with @p value. - constexpr auto resize(size_type new_size, const_reference value) -> void - { - if (new_size < size()) - { - destroy_n(begin() + new_size, size() - new_size); - m_size = new_size; - return; - } - - if (new_size == size()) - { - return; - } - - if (new_size > max_size()) - { - kstd::os::panic("[kstd:vector] Tried to resize more space than theoretically possible."); - } - - if (new_size > capacity()) - { - reserve(new_size); - } - - for (auto i = size(); i < new_size; ++i) - { - std::allocator_traits::construct(m_allocator, m_data + i, value); - } - - m_size = new_size; - } - - //! Get the number of element this vector has currently space for, including elements currently in this vector. - [[nodiscard]] constexpr auto capacity() const noexcept -> size_type - { - return m_capacity; - } - - //! Try to release unused storage space. - constexpr auto shrink_to_fit() -> void - { - if (m_size == m_capacity) - { - return; - } - - auto new_data = allocate_n(m_size); - auto old_size = size(); - uninitialized_move_with_allocator(begin(), new_data, old_size); - clear_and_deallocate(); - std::exchange(m_data, new_data); - m_capacity = old_size; - m_size = old_size; - } - - //! Clear the contents of this vector. - constexpr auto clear() noexcept -> void - { - destroy_n(begin(), size()); - m_size = 0; - } - - //! Insert an element at a given position. - //! - //! @param position The position to insert the element at. - //! @param value The value to insert. - //! @return An iterator to the inserted element. - constexpr auto insert(const_iterator position, value_type const & value) -> iterator - { - auto prefix_size = std::ranges::distance(begin(), position); - auto suffix_size = std::ranges::distance(position, end()); - - if (position == end()) - { - push_back(value); - return begin() + prefix_size; - } - - if (m_capacity == m_size) - { - auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - std::allocator_traits::construct(m_allocator, new_data + prefix_size, value); - uninitialized_move_with_allocator(begin(), new_data, prefix_size); - uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size); - destroy_n(begin(), old_size); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - else if (&value >= begin() && &value < end()) - { - auto value_copy = value; - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = std::move(value_copy); - } - else - { - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = value; - } - - ++m_size; - return begin() + prefix_size; - } - - //! Insert an element at a given position. - //! - //! @param position The position to insert the element at. - //! @param value The value to insert. - //! @return An iterator to the inserted element. - constexpr auto insert(const_iterator position, value_type && value) -> iterator - { - auto prefix_size = std::ranges::distance(begin(), position); - auto suffix_size = std::ranges::distance(position, end()); - - if (position == end()) - { - push_back(std::move(value)); - return begin() + prefix_size; - } - - if (m_capacity == m_size) - { - auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - std::allocator_traits::construct(m_allocator, new_data + prefix_size, std::move(value)); - uninitialized_move_with_allocator(begin(), new_data, prefix_size); - uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size); - destroy_n(begin(), old_size); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - else if (&value >= begin() && &value < end()) - { - auto value_copy = std::move(value); - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = std::move(value_copy); - } - else - { - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = std::move(value); - } - - ++m_size; - return begin() + prefix_size; - } - - //! Insert the element of a given range into the vector at a given position. - //! - //! @param range The source range to insert elements from. - //! @tparam SourceRange A container compatible range type. - template - requires requires(allocator_type allocator, pointer destination, SourceRange range) { - requires kstd::bits::container_compatible_range; - requires std::move_constructible; - requires std::is_move_assignable_v; - requires std::swappable; - std::allocator_traits::construct(allocator, destination, *std::ranges::begin(range)); - } - // NOLINTNEXTLINE(misc-no-recursion) - constexpr auto insert_range(const_iterator position, SourceRange && range) -> iterator - { - auto prefix_size = std::ranges::distance(begin(), position); - - if (position == end()) - { - append_range(std::forward(range)); - return begin() + prefix_size; - } - - if constexpr (std::ranges::forward_range || std::ranges::sized_range) - { - auto number_of_elements = static_cast(std::ranges::distance(range)); - if (!number_of_elements) - { - return begin() + prefix_size; - } - - if (capacity() - size() < number_of_elements) - { - reserve(size() + std::max(size(), number_of_elements)); - } - - auto insert_position = begin() + prefix_size; - auto suffix_size = static_cast(std::ranges::distance(insert_position, end())); - - if (number_of_elements >= suffix_size) - { - uninitialized_move_with_allocator(insert_position, insert_position + number_of_elements, suffix_size); - auto result = std::ranges::copy_n(std::ranges::begin(range), suffix_size, insert_position); - uninitialized_copy_with_allocator(std::move(result.in), end(), number_of_elements - suffix_size); - } - else - { - uninitialized_move_with_allocator(end() - number_of_elements, end(), number_of_elements); - std::ranges::move_backward(insert_position, end() - number_of_elements, end()); - std::ranges::copy_n(std::ranges::begin(range), number_of_elements, insert_position); - } - - m_size += number_of_elements; - return insert_position; - } - - auto range_begin = std::ranges::begin(range); - auto range_end = std::ranges::end(range); - - auto remainder = vector{get_allocator()}; - for (; range_begin != range_end; ++range_begin) - { - remainder.emplace_back(*static_cast>(range_begin)); - } - reserve(size() + std::max(size(), remainder.size())); - - return insert_range(begin() + prefix_size, remainder); - } - - template - constexpr auto emplace(const_iterator position, Args &&... args) -> iterator - { - auto prefix_size = std::ranges::distance(begin(), position); - auto suffix_size = std::ranges::distance(position, end()); - - if (position == end()) - { - emplace_back(std::forward(args)...); - return begin() + prefix_size; - } - - if (m_capacity == m_size) - { - auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - std::allocator_traits::construct(m_allocator, new_data + prefix_size, - std::forward(args)...); - uninitialized_move_with_allocator(begin(), new_data, prefix_size); - uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size); - destroy_n(begin(), old_size); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - else - { - auto insert_position = begin() + prefix_size; - std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); - std::ranges::move_backward(insert_position, end() - 1, end()); - *insert_position = value_type{std::forward(args)...}; - } - - ++m_size; - return begin() + prefix_size; - } - - //! Erase an element at a given position. - //! - //! @note This function will panic if position == end() - //! - //! @param position An interator pointing to the element to delete - //! @return An iterator pointing to the element after the deleted element - constexpr auto erase(const_iterator position) -> iterator - { - if (position == end()) - { - os::panic("[kstd:vector] Attempted to erase end()!"); - } - - auto prefix_size = std::ranges::distance(cbegin(), position); - - std::ranges::move(begin() + prefix_size + 1, end(), begin() + prefix_size); - std::allocator_traits::destroy(m_allocator, end() - 1); - --m_size; - - return begin() + prefix_size; - } - - //! Erase a range of elements from this vector. - //! - //! @param first The start of the range to erase. - //! @param last The end of the range to erase. - //! @return An iterator pointing to the element after the last deleted element. - constexpr auto erase(const_iterator first, const_iterator last) -> iterator - { - if (first == last) - { - return begin() + std::ranges::distance(cbegin(), first); - } - - auto prefix_size = std::ranges::distance(cbegin(), first); - auto element_count = std::ranges::distance(first, last); - - std::ranges::move(begin() + prefix_size + element_count, end(), begin() + prefix_size); - destroy_n(end() - element_count, element_count); - m_size -= element_count; - - return begin() + prefix_size; - } - - //! Append a given element to this vector via copy construction. - constexpr auto push_back(value_type const & value) -> void - { - if (m_capacity == m_size) - { - auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - std::allocator_traits::construct(m_allocator, new_data + m_size, value); - uninitialized_move_with_allocator(begin(), new_data, size()); - destroy_n(begin(), size()); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - else - { - std::allocator_traits::construct(m_allocator, data() + size(), value); - } - ++m_size; - } - - //! Append a given element to this vector via move construction. - constexpr auto push_back(value_type && value) -> void - { - if (m_capacity == m_size) - { - auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - std::allocator_traits::construct(m_allocator, new_data + m_size, std::move(value)); - uninitialized_move_with_allocator(begin(), new_data, size()); - destroy_n(begin(), size()); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - else - { - std::allocator_traits::construct(m_allocator, data() + size(), std::move(value)); - } - ++m_size; - } - - //! Append a given element to this vector via direct construction. - template - constexpr auto emplace_back(Args &&... args) -> reference - { - if (m_capacity == m_size) - { - auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - std::allocator_traits::construct(m_allocator, new_data + m_size, std::forward(args)...); - uninitialized_move_with_allocator(begin(), new_data, size()); - destroy_n(begin(), size()); - deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size; - } - else - { - std::allocator_traits::construct(m_allocator, data() + size(), std::forward(args)...); - } - ++m_size; - return this->back(); - } - - //! Append the elements of a given range to this vector. - //! - //! @param range The range of elements to be appended. - //! @tparam SourceRange A container compatible range type. - template SourceRange> - requires requires(Allocator allocator, pointer destination, SourceRange range) { - std::allocator_traits::construct(allocator, destination, *std::ranges::begin(range)); - } - // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward, misc-no-recursion) - constexpr auto append_range(SourceRange && range) -> void - { - if constexpr (std::ranges::forward_range || std::ranges::sized_range) - { - auto number_of_elements = static_cast(std::ranges::distance(range)); - - if (!capacity()) - { - reserve(number_of_elements); - } - - if (capacity() - size() >= number_of_elements) - { - uninitialized_copy_with_allocator(std::ranges::begin(range), end(), number_of_elements); - m_size += number_of_elements; - return; - } - - auto new_capacity = m_capacity + std::max(size(), number_of_elements); - auto new_data = allocate_n(new_capacity); - auto old_size = size(); - - uninitialized_move_with_allocator(begin(), new_data, size()); - uninitialized_copy_with_allocator(std::ranges::begin(range), new_data + size(), number_of_elements); - clear_and_deallocate(); - m_data = new_data; - m_capacity = new_capacity; - m_size = old_size + number_of_elements; - return; - } - - auto range_begin = std::ranges::begin(range); - auto range_end = std::ranges::end(range); - - for (auto i = capacity() - size(); i > 0; --i, ++range_begin) - { - emplace_back(*range_begin); - } - - if (range_begin == range_end) - { - return; - } - - auto remainder = vector{get_allocator()}; - for (; range_begin != range_end; ++range_begin) - { - remainder.emplace_back(*static_cast>(range_begin)); - } - reserve(size() + std::max(size(), remainder.size())); - append_range(remainder); - } - - //! Remove the last element of this vector. - //! - //! If this vector is empty, the behavior is undefined. - constexpr auto pop_back() -> void - { - --m_size; - std::allocator_traits::destroy(m_allocator, data() + size()); - } - - private: - //! Use the allocator of this vector to allocate enough space for the given number of elements. - //! - //! @param count The number of elements to allocate space for. - [[nodiscard]] constexpr auto allocate_n(std::size_t count) -> std::allocator_traits::pointer - { - if (count) - { - return std::allocator_traits::allocate(m_allocator, count); - } - return nullptr; - } - - //! Clear this vector and release it's memory. - constexpr auto clear_and_deallocate() -> void - { - clear(); - deallocate(); - } - - //! Release the memory of this vector. - constexpr auto deallocate() - { - if (m_data) - { - std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); - m_capacity = 0; - m_size = 0; - m_data = nullptr; - } - } - - //! Destroy a number of elements in this vector. - //! - //! @param first The start of the range of the elements to be destroyed. - //! @param count The number of elements to destroy. - constexpr auto destroy_n(iterator first, std::size_t count) -> void - { - std::ranges::for_each(first, first + count, [&](auto & element) { - std::allocator_traits::destroy(m_allocator, std::addressof(element)); - }); - } - - //! Panic the kernel if the given index is out of bounds. - //! - //! @param index The index to check. - constexpr auto panic_if_out_of_bounds(size_type index) const -> void - { - if (index >= m_size) - { - os::panic("[kstd:vector] Attempted to read element at invalid index"); - } - } - - //! Copy a number of elements from a source range into the uninitialized destination range inside this vector. - //! - //! @param from The start of the source range. - //! @param to The start of the target range inside this vector. - //! @param count The number of elements to copy - template - constexpr auto uninitialized_copy_with_allocator(SourceIterator from, iterator to, size_type count) - { - for (auto i = 0uz; i < count; ++i) - { - std::allocator_traits::construct(m_allocator, to++, *from++); - } - } - - //! Move a number of elements from a source range into the uninitialized destination range inside this vector. - //! - //! @param from The start of the source range. - //! @param to The start of the target range inside this vector. - //! @param count The number of elements to copy - template - constexpr auto uninitialized_move_with_allocator(SourceIterator from, iterator to, size_type count) - { - for (auto i = 0uz; i < count; ++i) - { - std::allocator_traits::construct(m_allocator, to++, std::move(*from++)); - } - } - - //! The allocator used by this vector. - [[no_unique_address]] allocator_type m_allocator{}; - - //! The number of elements in this vector. - size_type m_size{}; - - //! The number of elements this vector has room for. - size_type m_capacity{}; - - //! The pointer to the start of the memory managed by this vector. - value_type * m_data{}; - }; - - //! Check if the content of two vectors is equal. - template - constexpr auto operator==(vector const & lhs, vector const & rhs) -> bool - { - return std::ranges::equal(lhs, rhs); - } - - //! Perform a lexicographical comparison of the content of two vectors. - template - constexpr auto operator<=>(vector const & lhs, vector const & rhs) - -> decltype(std::declval() <=> std::declval()) - { - return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); - } - - //! Deduction guide for vector construction from an interator pair. - template::value_type>> - vector(ForwardIterator, ForwardIterator, Allocator = Allocator()) - -> vector::value_type, Allocator>; - - //! Deduction guide for vector construction from an interator pair. - template::value_type>> - vector(InputIterator, InputIterator, Allocator = Allocator()) - -> vector::value_type, Allocator>; - - //! Deduction guide for vector construction from a range. - template>> - vector(kstd::from_range_t, Range &&, Allocator = Allocator()) -> vector, Allocator>; - -} // namespace kstd - -#endif diff --git a/libs/kstd/kstd/allocator b/libs/kstd/kstd/allocator new file mode 100644 index 0000000..0de0e10 --- /dev/null +++ b/libs/kstd/kstd/allocator @@ -0,0 +1,64 @@ +#ifndef KSTD_ALLOCATOR_HPP +#define KSTD_ALLOCATOR_HPP + +#include +#include +#include + +#if __has_builtin(__builtin_operator_new) >= 201'802L +#define KSTD_OPERATOR_NEW __builtin_operator_new +#define KSTD_OPERATOR_DELETE __builtin_operator_delete +#else +#define KSTD_OPERATOR_NEW ::operator new +#define KSTD_OPERATOR_DELETE ::operator delete +#endif + +namespace kstd +{ + + template + struct allocator + { + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using propagate_on_container_move_assignment = std::true_type; + using is_always_equal = std::true_type; + + constexpr allocator() noexcept = default; + + template + constexpr allocator(allocator const &) noexcept + {} + + constexpr auto allocate(std::size_t n) -> T * + { + if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + { + return static_cast(KSTD_OPERATOR_NEW(n * sizeof(T), std::align_val_t{alignof(T)})); + } + return static_cast(KSTD_OPERATOR_NEW(n * sizeof(T))); + } + + constexpr void deallocate(T * p, std::size_t n) noexcept + { + if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + { + KSTD_OPERATOR_DELETE(p, n * sizeof(T), std::align_val_t{alignof(T)}); + } + KSTD_OPERATOR_DELETE(p, n * sizeof(T)); + } + }; + + template + constexpr auto operator==(allocator const &, allocator const &) noexcept -> bool + { + return true; + } + +} // namespace kstd + +#undef KSTD_OPERATOR_NEW +#undef KSTD_OPERATOR_DELETE + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/asm_ptr b/libs/kstd/kstd/asm_ptr new file mode 100644 index 0000000..c06a8b5 --- /dev/null +++ b/libs/kstd/kstd/asm_ptr @@ -0,0 +1,78 @@ +#ifndef KSTD_ASM_POINTER_HPP +#define KSTD_ASM_POINTER_HPP + +#include +#include + +namespace kstd +{ + + /** + * @brief A pointer that is defined in some assembly source file. + * + * @tparam Type The type of the pointer + */ + template + struct asm_ptr + { + using value_type = Type; + using pointer = value_type *; + using const_pointer = value_type const *; + using reference = value_type &; + using const_reference = value_type const &; + + asm_ptr() = delete; + asm_ptr(asm_ptr const &) = delete; + asm_ptr(asm_ptr &&) = delete; + ~asm_ptr() = delete; + + constexpr auto operator=(asm_ptr const &) = delete; + constexpr auto operator=(asm_ptr &&) = delete; + + auto get() const noexcept -> pointer + { + return m_ptr; + } + + constexpr auto operator+(std::ptrdiff_t offset) const noexcept -> pointer + { + return std::bit_cast(m_ptr) + offset; + } + + constexpr auto operator*() noexcept -> reference + { + return *(std::bit_cast(m_ptr)); + } + + constexpr auto operator*() const noexcept -> const_reference + { + return *(std::bit_cast(m_ptr)); + } + + constexpr auto operator[](std::ptrdiff_t offset) noexcept -> reference + { + return *(*this + offset); + } + + constexpr auto operator[](std::ptrdiff_t offset) const noexcept -> const_reference + { + return *(*this + offset); + } + + constexpr auto operator->() noexcept -> pointer + { + return m_ptr; + } + + constexpr auto operator->() const noexcept -> const_pointer + { + return m_ptr; + } + + private: + pointer m_ptr; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/concepts.hpp b/libs/kstd/kstd/bits/concepts.hpp new file mode 100644 index 0000000..74c25cb --- /dev/null +++ b/libs/kstd/kstd/bits/concepts.hpp @@ -0,0 +1,15 @@ +#ifndef KSTD_BITS_CONCEPTS_HPP +#define KSTD_BITS_CONCEPTS_HPP + +#include +#include +namespace kstd::bits +{ + + template + concept container_compatible_range = + std::ranges::input_range && std::convertible_to, ValueType>; + +} + +#endif diff --git a/libs/kstd/kstd/bits/flat_map.hpp b/libs/kstd/kstd/bits/flat_map.hpp new file mode 100644 index 0000000..fe46203 --- /dev/null +++ b/libs/kstd/kstd/bits/flat_map.hpp @@ -0,0 +1,186 @@ +#ifndef KSTD_BITS_FLAT_MAP_HPP +#define KSTD_BITS_FLAT_MAP_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace kstd::bits +{ + + template + struct flat_map_reference + { + using key_type = KeyType; + using mapped_type = MappedType; + + constexpr flat_map_reference(key_type const & key, mapped_type & mapped) + : first{key} + , second{mapped} + {} + + constexpr auto operator=(flat_map_reference const & other) const -> flat_map_reference const & + { + second = other.second; + return *this; + } + + constexpr auto operator=(flat_map_reference && other) const -> flat_map_reference const & + { + second = std::move(other.second); + return *this; + } + + template + requires(std::tuple_size_v> == 2) + constexpr auto operator=(TupleLikeType && tuple) const -> flat_map_reference const & + { + second = std::forward(tuple).second; + return *this; + } + + template + requires(Index >= 0 && Index <= 1) + [[nodiscard]] constexpr auto get() const noexcept -> decltype(auto) + { + if constexpr (Index == 0) + { + return (first); + } + else + { + return (second); + } + } + + key_type const & first; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + mapped_type & second; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + }; + + template + struct flat_map_pointer + { + Reference reference; + + [[nodiscard]] constexpr auto operator->() noexcept -> Reference * + { + return std::addressof(reference); + } + + [[nodiscard]] constexpr auto operator->() const noexcept -> Reference const * + { + return std::addressof(reference); + } + }; + + template + struct flat_map_iterator + { + using iterator_category = std::random_access_iterator_tag; + using value_type = std::pair; + using difference_type = std::ptrdiff_t; + using reference = flat_map_reference; + using pointer = flat_map_pointer; + + constexpr flat_map_iterator() = default; + + constexpr flat_map_iterator(KeyIterator key_iterator, MappedIterator mapped_iterator) + : m_key_iterator{key_iterator} + , m_mapped_iterator{mapped_iterator} + {} + + template + requires(std::convertible_to && + std::convertible_to) + constexpr flat_map_iterator( + flat_map_iterator const & other) noexcept + : m_key_iterator{other.m_key_iterator} + , m_mapped_iterator{other.m_mapped_iterator} + {} + + [[nodiscard]] auto key_iterator() const noexcept -> KeyIterator + { + return m_key_iterator; + } + + [[nodiscard]] constexpr auto operator*() const noexcept -> reference + { + return {*m_key_iterator, *m_mapped_iterator}; + } + + [[nodiscard]] constexpr auto operator->() const noexcept -> pointer + { + return { + {*m_key_iterator, *m_mapped_iterator} + }; + } + + constexpr auto operator++() noexcept -> flat_map_iterator & + { + ++m_key_iterator; + ++m_mapped_iterator; + return *this; + } + + constexpr auto operator++(int) noexcept -> flat_map_iterator + { + auto copy = *this; + ++(*this); + return copy; + } + + constexpr auto operator--() noexcept -> flat_map_iterator & + { + --m_key_iterator; + --m_mapped_iterator; + return *this; + } + + constexpr auto operator--(int) noexcept -> flat_map_iterator + { + auto copy = *this; + --(*this); + return copy; + } + + [[nodiscard]] constexpr auto operator+(difference_type offset) const noexcept -> flat_map_iterator + { + return {m_key_iterator + offset, m_mapped_iterator + offset}; + } + + [[nodiscard]] constexpr auto operator-(flat_map_iterator const & other) const noexcept -> difference_type + { + return m_key_iterator - other.m_key_iterator; + } + + [[nodiscard]] constexpr auto operator<=>(flat_map_iterator const & other) const noexcept = default; + + private: + KeyIterator m_key_iterator{}; + MappedIterator m_mapped_iterator{}; + }; + +} // namespace kstd::bits + +template +struct std::tuple_size> : std::integral_constant +{ +}; + +template +struct std::tuple_element<0, kstd::bits::flat_map_reference> +{ + using type = K const &; +}; + +template +struct std::tuple_element<1, kstd::bits::flat_map_reference> +{ + using type = M &; +}; + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/arg.hpp b/libs/kstd/kstd/bits/format/arg.hpp new file mode 100644 index 0000000..e65b26f --- /dev/null +++ b/libs/kstd/kstd/bits/format/arg.hpp @@ -0,0 +1,75 @@ +#ifndef KSTD_BITS_FORMAT_ARG_HPP +#define KSTD_BITS_FORMAT_ARG_HPP + +// IWYU pragma: private, include + +#include +#include + +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + enum struct arg_type : std::uint8_t + { + none, + boolean, + character, + integer, + unsigned_integer, + string_view, + c_string, + pointer, + user_defined, + }; + } // namespace bits::format + + struct format_arg + { + bits::format::arg_type type{}; + union + { + bool boolean; + char character; + std::int64_t integer; + std::uint64_t unsigned_integer; + std::string_view string_view; + char const * c_string; + void const * pointer; + struct + { + void const * pointer; + auto (*format)(void const * value, format_parse_context & parse_context, format_context & context) -> void; + } user_defined; + } value{}; + }; + + namespace bits::format + { + constexpr auto extrat_dynamic_width(format_arg const & arg) -> std::size_t + { + if (arg.type == arg_type::unsigned_integer) + { + return static_cast(arg.value.unsigned_integer); + } + else if (arg.type == arg_type::integer) + { + if (arg.value.integer < 0) + { + error("Dynamic width cannont be negative."); + } + return static_cast(arg.value.integer); + } + error("Dynamic width argument is not an integral value."); + return 0; + } + } // namespace bits::format + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/args.hpp b/libs/kstd/kstd/bits/format/args.hpp new file mode 100644 index 0000000..e8e3114 --- /dev/null +++ b/libs/kstd/kstd/bits/format/args.hpp @@ -0,0 +1,160 @@ +#ifndef KSTD_BITS_FORMAT_ARGS_HPP +#define KSTD_BITS_FORMAT_ARGS_HPP + +// IWYU pragma: private, include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + + template + auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context) + -> void + { + auto typed_value_pointer = static_cast(value_pointer); + auto fmt = formatter>{}; + auto const it = fmt.parse(parse_context); + parse_context.advance_to(it); + fmt.format(*typed_value_pointer, context); + } + + template + constexpr auto determine_arg_type() -> arg_type + { + using decay_type = std::remove_cvref_t; + if constexpr (std::same_as) + { + return arg_type::boolean; + } + else if constexpr (std::same_as) + { + return arg_type::character; + } + else if constexpr (std::integral && std::is_signed_v) + { + return arg_type::integer; + } + else if constexpr (std::integral && std::is_unsigned_v) + { + return arg_type::unsigned_integer; + } + else if constexpr (std::same_as) + { + return arg_type::string_view; + } + else if constexpr (std::same_as, char *> || + std::same_as, char const *>) + { + return arg_type::c_string; + } + else if constexpr (std::is_pointer_v || std::same_as) + { + if constexpr (std::same_as || std::same_as) + { + return arg_type::user_defined; + } + else + { + return arg_type::pointer; + } + } + else + { + return arg_type::user_defined; + } + } + + template + constexpr auto make_single_arg(ValueType const & value) -> format_arg + { + auto result = format_arg{}; + constexpr auto type = determine_arg_type(); + result.type = type; + + if constexpr (type == arg_type::boolean) + { + result.value.boolean = value; + } + else if constexpr (type == arg_type::character) + { + result.value.character = value; + } + else if constexpr (type == arg_type::integer) + { + result.value.integer = static_cast(value); + } + else if constexpr (type == arg_type::unsigned_integer) + { + result.value.unsigned_integer = static_cast(value); + } + else if constexpr (type == arg_type::string_view) + { + result.value.string_view = value; + } + else if constexpr (type == arg_type::c_string) + { + result.value.c_string = value; + } + else if constexpr (type == arg_type::pointer) + { + if constexpr (std::same_as, std::nullptr_t>) + { + result.value.pointer = nullptr; + } + else + { + result.value.pointer = static_cast(value); + } + } + else + { + result.value.user_defined.pointer = &value; + result.value.user_defined.format = format_trampoline; + } + return result; + } + + } // namespace bits::format + + using format_args = std::span; + + template + struct format_arg_store + { + std::array args{}; + }; + + template + [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store + { + using namespace bits::format; + + if constexpr (sizeof...(Arguments) == 0) + { + return format_arg_store<0>{}; + } + else + { + return format_arg_store{ + std::array{make_single_arg(args)...}}; + } + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/context.hpp b/libs/kstd/kstd/bits/format/context.hpp new file mode 100644 index 0000000..c166ba9 --- /dev/null +++ b/libs/kstd/kstd/bits/format/context.hpp @@ -0,0 +1,65 @@ +#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP +#define KSTD_BITS_FORMAT_CONTEXT_HPP + +// IWYU pragma: private, include + +#include +#include +#include + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + template + constexpr auto inline is_width_v = std::integral && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as; + } + + struct format_context + { + using format_args = std::span; + format_args args{}; + + format_context(bits::format::output_buffer & buffer, format_args args) + : args{args} + , m_buffer{&buffer} + {} + + [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & + { + if (id >= args.size()) + { + kstd::os::panic("[kstd:format] argument index out of range!"); + } + return args[id]; + } + + constexpr auto push(std::string_view string) -> void + { + m_buffer->push(string); + } + + constexpr auto push(char character) -> void + { + m_buffer->push(character); + } + + private: + bits::format::output_buffer * m_buffer; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/error.hpp b/libs/kstd/kstd/bits/format/error.hpp new file mode 100644 index 0000000..c0cb53d --- /dev/null +++ b/libs/kstd/kstd/bits/format/error.hpp @@ -0,0 +1,24 @@ +#ifndef KSTD_BITS_FORMAT_ERROR_HPP +#define KSTD_BITS_FORMAT_ERROR_HPP + +#include + +namespace kstd::bits::format +{ + + constexpr auto error(char const * message) -> void + { + if consteval + { + extern void compile_time_format_error_triggered(char const *); + compile_time_format_error_triggered(message); + } + else + { + kstd::os::panic("Error while formatting a string."); + } + } + +} // namespace kstd::bits::format + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter.hpp b/libs/kstd/kstd/bits/format/formatter.hpp new file mode 100644 index 0000000..eb28829 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter.hpp @@ -0,0 +1,92 @@ +#ifndef KSTD_BITS_FORMATTER_HPP +#define KSTD_BITS_FORMATTER_HPP + +// IWYU pragma: private, include + +#include +#include +#include + +#include +#include +#include + +namespace kstd +{ + + template + struct formatter + { + formatter() = delete; + formatter(formatter const &) = delete; + auto operator=(formatter const &) -> formatter & = delete; + }; + + template + struct range_formatter + { + constexpr auto set_separator(std::string_view sep) -> void + { + m_separator = sep; + } + + constexpr auto set_brackets(std::string_view opening, std::string_view closing) + { + m_prefix = opening; + m_suffix = closing; + } + + constexpr auto parse(format_parse_context & context) + { + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it == 'n') + { + set_brackets("", ""); + std::advance(it, 1); + } + + if (it != end && *it == ':') + { + std::advance(it, 1); + context.advance_to(it); + it = m_inner_formatter.parse(context); + } + + if (it != end && *it != '}') + { + bits::format::error("Invalid formate specifier for range"); + } + + return it; + } + + template + auto format(Range const & range, format_context & context) + { + context.push(m_prefix); + + auto is_first = true; + std::ranges::for_each(range, [&](auto const & element) { + if (!is_first) + { + context.push(m_separator); + } + m_inner_formatter.format(element, context); + is_first = false; + }); + + context.push(m_suffix); + } + + private: + kstd::formatter m_inner_formatter{}; + std::string_view m_separator{", "}; + std::string_view m_prefix{"["}; + std::string_view m_suffix{"]"}; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/bool.hpp b/libs/kstd/kstd/bits/format/formatter/bool.hpp new file mode 100644 index 0000000..cc8d190 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/bool.hpp @@ -0,0 +1,83 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP +#define KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP + +#include +#include +#include +#include +#include + +#include +#include + +namespace kstd +{ + + template<> + struct formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it != '}') + { + if (*it == 's') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for bool."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(bool value, format_context & context) const -> void + { + auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; + auto final_width = 0uz; + + if (specifiers.mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = bits::format::extrat_dynamic_width(arg); + } + + auto padding = bits::format::calculate_format_padding(final_width, text.size(), specifiers.align, + bits::format::alignment::left); + + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + + context.push(text); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/byte.hpp b/libs/kstd/kstd/bits/format/formatter/byte.hpp new file mode 100644 index 0000000..cc8aece --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/byte.hpp @@ -0,0 +1,23 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP +#define KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP + +#include +#include +#include + +#include + +namespace kstd +{ + + template<> + struct formatter : formatter + { + auto format(std::byte value, format_context & context) const -> void + { + formatter::format(static_cast(value), context); + } + }; + +} // namespace kstd +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/char.hpp b/libs/kstd/kstd/bits/format/formatter/char.hpp new file mode 100644 index 0000000..92489a1 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/char.hpp @@ -0,0 +1,94 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP +#define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP + +#include +#include +#include +#include +#include +#include + +#include + +namespace kstd +{ + + template<> + struct formatter : formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (specifiers.alternative_form || specifiers.zero_pad || specifiers.sign != bits::format::sign_mode::none) + { + bits::format::error("Invalid format specifiers for 'char'"); + } + + if (it != end && *it != '}') + { + if (*it == 'c' || *it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for char."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(char value, format_context & context) const -> void + { + if (specifiers.type == '\0' || specifiers.type == 'c') + { + auto final_width = 0uz; + + if (specifiers.mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = bits::format::extrat_dynamic_width(arg); + } + + auto padding = + bits::format::calculate_format_padding(final_width, 1, specifiers.align, bits::format::alignment::left); + + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + + context.push(value); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + else + { + formatter::format(static_cast(value), context); + } + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/kstd/bits/format/formatter/cstring.hpp new file mode 100644 index 0000000..553c8ca --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/cstring.hpp @@ -0,0 +1,29 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP +#define KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP + +#include +#include +#include + +#include + +namespace kstd +{ + + template<> + struct formatter : formatter + { + auto format(char const * string, format_context & context) const -> void + { + formatter::format(string ? std::string_view{string} : "(null)", context); + } + }; + + template<> + struct formatter : formatter + { + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/integral.hpp b/libs/kstd/kstd/bits/format/formatter/integral.hpp new file mode 100644 index 0000000..d17dc95 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/integral.hpp @@ -0,0 +1,204 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP +#define KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + + template + struct formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto static maximum_digits = 80; + + enum struct base + { + bin = 2, + oct = 8, + dec = 10, + hex = 16, + }; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it != '}') + { + if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for integral type."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(T value, format_context & context) const -> void + { + auto final_width = 0uz; + if (specifiers.mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = bits::format::extrat_dynamic_width(arg); + } + + using unsigned_T = std::make_unsigned_t; + auto absolute_value = static_cast(value); + auto is_negative = false; + + if constexpr (std::is_signed_v) + { + if (value < 0) + { + is_negative = true; + absolute_value = 0 - static_cast(value); + } + } + + auto const base = [type = specifiers.type] -> auto { + switch (type) + { + case 'x': + case 'X': + case 'p': + return base::hex; + case 'b': + case 'B': + return base::bin; + case 'o': + return base::oct; + default: + return base::dec; + } + }(); + + auto buffer = std::array{}; + auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; + auto current = buffer.rbegin(); + + if (absolute_value == 0) + { + *current = '0'; + std::advance(current, 1); + } + else + { + while (absolute_value != 0) + { + *current = digits[absolute_value % std::to_underlying(base)]; + std::advance(current, 1); + absolute_value /= std::to_underlying(base); + } + } + + auto content_length = static_cast(std::distance(buffer.rbegin(), current)); + auto prefix = std::array{'0', '\0'}; + auto prefix_length = 0uz; + if (specifiers.alternative_form) + { + switch (base) + { + case base::bin: + prefix[1] = specifiers.type == 'B' ? 'B' : 'b'; + prefix_length = 2; + break; + case base::oct: + prefix_length = 1; + break; + case base::hex: + prefix[1] = specifiers.type == 'X' ? 'X' : 'x'; + prefix_length = 2; + break; + default: + break; + } + } + + auto sign_character = '\0'; + if (is_negative) + { + sign_character = '-'; + } + else if (specifiers.sign == bits::format::sign_mode::plus) + { + sign_character = '+'; + } + else if (specifiers.sign == bits::format::sign_mode::space) + { + sign_character = ' '; + } + + auto const total_length = content_length + prefix_length + (sign_character != '\0'); + auto const padding = bits::format::calculate_format_padding(final_width, total_length, specifiers.align, + bits::format::alignment::right); + auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::format::alignment::none); + + if (!effective_zero_pad) + { + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + } + + if (sign_character != '\0') + { + context.push(sign_character); + } + if (prefix_length > 0) + { + context.push(std::string_view{prefix.data(), prefix_length}); + } + + if (effective_zero_pad) + { + for (auto i = 0uz; i < padding.left; ++i) + { + context.push('0'); + } + } + + context.push(std::string_view{current.base(), content_length}); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/ordering.hpp b/libs/kstd/kstd/bits/format/formatter/ordering.hpp new file mode 100644 index 0000000..7832226 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/ordering.hpp @@ -0,0 +1,111 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP +#define KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP + +#include +#include +#include +#include + +#include + +namespace kstd +{ + + template<> + struct formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + return context.begin(); + } + + auto format(std::strong_ordering value, format_context & context) const -> void + { + if (value == std::strong_ordering::equal) + { + return context.push(specifiers.alternative_form ? "==" : "equal"); + } + else if (value == std::strong_ordering::equivalent) + { + return context.push(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::strong_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::strong_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + kstd::os::panic("[kstd:format] Invalid strong ordering value!"); + } + }; + + template<> + struct formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + return context.begin(); + } + + auto format(std::weak_ordering value, format_context & context) const -> void + { + if (value == std::weak_ordering::equivalent) + { + return context.push(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::weak_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::weak_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + kstd::os::panic("[kstd:format] Invalid weak ordering value!"); + } + }; + + template<> + struct formatter + { + bits::format::specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + return context.begin(); + } + + auto format(std::partial_ordering value, format_context & context) const -> void + { + if (value == std::partial_ordering::equivalent) + { + return context.push(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::partial_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::partial_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + else if (value == std::partial_ordering::unordered) + { + return context.push(specifiers.alternative_form ? "<=>" : "unordered"); + } + kstd::os::panic("[kstd:format] Invalid partial ordering value!"); + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/pointer.hpp b/libs/kstd/kstd/bits/format/formatter/pointer.hpp new file mode 100644 index 0000000..15f9a5b --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/pointer.hpp @@ -0,0 +1,42 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP +#define KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP + +#include +#include +#include +#include +#include + +#include +#include + +namespace kstd +{ + + template + struct formatter : formatter + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto result = formatter::parse(context); + if (!this->specifiers.type) + { + this->specifiers.type = 'p'; + this->specifiers.alternative_form = true; + } + return result; + } + + auto format(T const * pointer, format_context & context) const -> void + { + formatter::format(std::bit_cast(pointer), context); + } + }; + + template + struct formatter : formatter + { + }; + +} // namespace kstd +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/range.hpp b/libs/kstd/kstd/bits/format/formatter/range.hpp new file mode 100644 index 0000000..05af06f --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/range.hpp @@ -0,0 +1,32 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP +#define KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP + +#include + +#include +#include +#include +#include + +namespace kstd +{ + namespace bits::format + { + template + concept iterable = requires(T const & t) { + t.begin(); + t.end(); + }; + + template + concept formattable_range = iterable && !std::same_as, std::string_view> && + !std::same_as, char *> && !std::same_as, char const *>; + } // namespace bits::format + + template + struct formatter : range_formatter> + { + }; +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/formatter/string_view.hpp b/libs/kstd/kstd/bits/format/formatter/string_view.hpp new file mode 100644 index 0000000..7d74579 --- /dev/null +++ b/libs/kstd/kstd/bits/format/formatter/string_view.hpp @@ -0,0 +1,41 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP +#define KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP + +#include +#include +#include +#include + +#include + +namespace kstd +{ + + template<> + struct formatter + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto it = context.begin(); + + if (it != context.end() && *it == 's') + { + ++it; + } + + if (it != context.end() && *it != '}') + { + bits::format::error("Invalid specifier for string_view."); + } + return it; + } + + auto format(std::string_view const & string, format_context & context) const -> void + { + context.push(string); + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/fwd.hpp b/libs/kstd/kstd/bits/format/fwd.hpp new file mode 100644 index 0000000..6caedae --- /dev/null +++ b/libs/kstd/kstd/bits/format/fwd.hpp @@ -0,0 +1,23 @@ +#ifndef KSTD_BITS_FORMAT_FWD_HPP +#define KSTD_BITS_FORMAT_FWD_HPP + +// IWYU pragma: private + +#include + +namespace kstd +{ + + struct format_parse_context; + struct format_context; + struct format_arg; + + template + struct formatter; + + template + struct format_arg_store; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/output_buffer.hpp b/libs/kstd/kstd/bits/format/output_buffer.hpp new file mode 100644 index 0000000..fd7a2b4 --- /dev/null +++ b/libs/kstd/kstd/bits/format/output_buffer.hpp @@ -0,0 +1,32 @@ +#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP +#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP + +// IWYU pragma: private, include + +#include + +namespace kstd::bits::format +{ + + //! An abstract interface for formatted output buffers. + //! + //! This interface is intended to be use for functions dealing with string formatting, like the print and the format + //! family. + struct output_buffer + { + virtual ~output_buffer() = default; + + //! Push a text segment into the buffer. + //! + //! @param text The text segment to push. + virtual auto push(std::string_view text) -> void = 0; + + //! Push a single character into the buffer. + //! + //! @param character The character to push into the buffer. + virtual auto push(char character) -> void = 0; + }; + +} // namespace kstd::bits::format + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/parse_context.hpp b/libs/kstd/kstd/bits/format/parse_context.hpp new file mode 100644 index 0000000..cab8d72 --- /dev/null +++ b/libs/kstd/kstd/bits/format/parse_context.hpp @@ -0,0 +1,109 @@ +#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP +#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP + +// IWYU pragma: private, include + +#include + +#include +#include + +namespace kstd +{ + + struct format_parse_context + { + using iterator = std::string_view::const_iterator; + + constexpr format_parse_context(std::string_view format, std::size_t argument_count, + bool const * is_integral = nullptr) + : m_current{format.begin()} + , m_end{format.end()} + , m_argument_count{argument_count} + , m_is_integral{is_integral} + {} + + [[nodiscard]] constexpr auto begin() const -> iterator + { + return m_current; + } + + [[nodiscard]] constexpr auto end() const -> iterator + { + return m_end; + } + + constexpr auto advance_to(iterator position) -> void + { + m_current = position; + } + + constexpr auto next_arg_id() -> std::size_t + { + if (m_mode == index_mode::manual) + { + bits::format::error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::automatic; + + if (m_next_argument_id >= m_argument_count) + { + bits::format::error("Argument index out of bounds."); + } + return m_next_argument_id++; + } + + constexpr auto check_arg_id(std::size_t index) -> void + { + if (m_mode == index_mode::automatic) + { + bits::format::error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::manual; + + if (index >= m_argument_count) + { + bits::format::error("Argument index out of bounds."); + } + } + + constexpr auto check_dynamic_width_id(std::size_t id) -> void + { + check_arg_id(id); + if (m_is_integral && !m_is_integral[id]) + { + bits::format::error("Dynamic width argument must be an integral object."); + } + } + + constexpr auto next_dynamic_width_id() -> std::size_t + { + auto const id = next_arg_id(); + if (m_is_integral && !m_is_integral[id]) + { + bits::format::error("Dynamic width argument must be an integral object."); + } + return id; + } + + private: + enum class index_mode + { + unknown, + automatic, + manual, + }; + + iterator m_current{}; + iterator m_end{}; + index_mode m_mode{}; + std::size_t m_next_argument_id{}; + std::size_t m_argument_count{}; + bool const * m_is_integral{}; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/specifiers.hpp b/libs/kstd/kstd/bits/format/specifiers.hpp new file mode 100644 index 0000000..211c95d --- /dev/null +++ b/libs/kstd/kstd/bits/format/specifiers.hpp @@ -0,0 +1,205 @@ +#ifndef KSTD_BITS_FORMAT_SPECS_HPP +#define KSTD_BITS_FORMAT_SPECS_HPP + +// IWYU pragma: private + +#include +#include + +#include +#include +#include + +namespace kstd::bits::format +{ + enum struct alignment : std::uint8_t + { + none, + left, + right, + center, + }; + + enum struct sign_mode : std::uint8_t + { + none, + plus, + minus, + space, + }; + + enum struct width_mode : std::uint8_t + { + none, + static_value, + dynamic_argument_id + }; + + struct specifiers + { + char fill{' '}; + alignment align{}; + sign_mode sign{}; + bool alternative_form{}; + bool zero_pad{}; + + width_mode mode{}; + std::size_t width_value{}; + char type{}; + }; + + struct padding + { + std::size_t left{}; + std::size_t right{}; + }; + + constexpr auto parse_format_specifiers(format_parse_context & context) -> specifiers + { + auto specs = specifiers{}; + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it == '}') + { + return specs; + } + + if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^')) + { + specs.fill = *it; + switch (*std::next(it)) + { + case '<': + specs.align = alignment::left; + break; + case '>': + specs.align = alignment::right; + break; + case '^': + default: + specs.align = alignment::center; + break; + } + std::advance(it, 2); + } + else if (*it == '<' || *it == '>' || *it == '^') + { + switch (*it) + { + case '<': + specs.align = alignment::left; + break; + case '>': + specs.align = alignment::right; + break; + case '^': + default: + specs.align = alignment::center; + break; + } + std::advance(it, 1); + } + + if (it != end && (*it == '+' || *it == '-' || *it == ' ')) + { + switch (*it) + { + case '+': + specs.sign = sign_mode::plus; + break; + case '-': + specs.sign = sign_mode::minus; + break; + case ' ': + default: + specs.sign = sign_mode::space; + break; + } + std::advance(it, 1); + } + + if (it != end && *it == '#') + { + specs.alternative_form = true; + std::advance(it, 1); + } + + if (it != end && *it == '0') + { + specs.zero_pad = true; + std::advance(it, 1); + } + + if (it != end && *it == '{') + { + specs.mode = width_mode::dynamic_argument_id; + std::advance(it, 1); + auto argument_id = 0uz; + + if (it != end && *it >= '0' && *it <= '9') + { + while (it != end && *it >= '0' && *it <= '9') + { + argument_id = argument_id * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + context.check_dynamic_width_id(argument_id); + } + else + { + argument_id = context.next_dynamic_width_id(); + } + + if (it == end || *it != '}') + { + error("Expected '}' for dynamic width."); + } + std::advance(it, 1); + specs.width_value = argument_id; + } + else if (it != end && *it >= '0' && *it <= '9') + { + specs.mode = width_mode::static_value; + while (it != end && *it >= '0' && *it <= '9') + { + specs.width_value = specs.width_value * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + } + + context.advance_to(it); + return specs; + } + + constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length, + alignment requested_alignment, alignment default_alignment) -> padding + { + if (target_width <= content_length) + { + return {}; + } + + auto total_padding = target_width - content_length; + auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment; + + switch (effective_alignment) + { + case alignment::center: + { + auto left = total_padding / 2; + auto right = total_padding - left; + return {left, right}; + } + case alignment::left: + return {0, total_padding}; + case alignment::right: + default: + return {total_padding, 0}; + break; + } + } + +} // namespace kstd::bits::format + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/string.hpp b/libs/kstd/kstd/bits/format/string.hpp new file mode 100644 index 0000000..e7e4088 --- /dev/null +++ b/libs/kstd/kstd/bits/format/string.hpp @@ -0,0 +1,148 @@ +#ifndef KSTD_BITS_FORMAT_STRING_HPP +#define KSTD_BITS_FORMAT_STRING_HPP + +// IWYU pragma: private, include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + template + constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void + { + auto found = false; + auto current_index = 0uz; + + (..., [&] { + if (current_index == target_index && !found) + { + using decay_type = std::remove_cvref_t; + static_assert(std::is_default_constructible_v>, "Missing formatter specialization."); + auto fmt = formatter{}; + + auto it = fmt.parse(context); + context.advance_to(it); + found = true; + } + ++current_index; + }()); + + if (!found) + { + error("Argument index out of bounds."); + } + } + + template + constexpr auto validate_dynamic_width(std::size_t target_index) -> void + { + auto is_valid_integer = false; + auto current_index = 0uz; + + (..., [&] { + if (current_index == target_index) + { + using decay_type = std::remove_cvref_t; + if constexpr (std::is_integral_v) + { + is_valid_integer = true; + } + } + ++current_index; + }()); + + if (!is_valid_integer) + { + error("Dynamic width argument must be an integral object."); + } + } + } // namespace bits::format + + template + struct format_string + { + template + consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT + : str_view{str} + { + using namespace bits::format; + + auto const is_width_compatible = + std::array 0 ? sizeof...(Args) : 1)>{is_width_v>...}; + auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; + auto it = context.begin(); + + while (it != context.end()) + { + if (*it == '{') + { + ++it; + if (it != context.end() && *it == '{') + { + ++it; + context.advance_to(it); + continue; + } + + context.advance_to(it); + auto argument_id = 0uz; + + if (it != context.end() && *it >= '0' && *it <= '9') + { + while (it != context.end() && *it >= '0' && *it <= '9') + { + argument_id = argument_id * 10 + static_cast(*it - '0'); + ++it; + } + context.check_arg_id(argument_id); + context.advance_to(it); + } + else + { + argument_id = context.next_arg_id(); + } + + if (it != context.end() && *it == ':') + { + ++it; + context.advance_to(it); + } + + validate_argument(argument_id, context); + + it = context.begin(); + if (it == context.end() || *it != '}') + { + bits::format::error("Missing closing '}' in format string."); + } + } + else if (*it == '}') + { + ++it; + if (it != context.end() && *it == '}') + { + bits::format::error("Unescaped '}' in format string."); + } + } + ++it; + context.advance_to(it); + } + } + + std::string_view str_view; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/format/vformat.hpp b/libs/kstd/kstd/bits/format/vformat.hpp new file mode 100644 index 0000000..994fae5 --- /dev/null +++ b/libs/kstd/kstd/bits/format/vformat.hpp @@ -0,0 +1,99 @@ +#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP +#define KSTD_BITS_FORMAT_VFORMAT_HPP + +// IWYU pragma: private, include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + //! Format a string with the given arguments into the given buffer. + //! + //! External implementations of `vprint` may call this function. + //! + //! @param buffer The buffer to format into. + //! @param format The format string to use. + //! @param args The arguments for the format string. + auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void; + + struct string_writer : output_buffer + { + auto push(std::string_view text) -> void override; + auto push(char character) -> void override; + + auto release() -> string &&; + + private: + string m_result{}; + }; + + template Output> + struct iterator_writer : output_buffer + { + explicit iterator_writer(Output iterator) + : m_output{iterator} + {} + + [[nodiscard]] auto iterator() const -> Output + { + return m_output; + } + + auto push(std::string_view text) -> void override + { + m_output = std::ranges::copy(text, m_output).out; + } + + auto push(char character) -> void override + { + *m_output++ = character; + } + + private: + Output m_output{}; + }; + + } // namespace bits::format + + //! Format a given string with the provided arguments. + //! + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return A new string containing the result of the format operation. + template + auto format(format_string...> format, ArgumentTypes &&... args) -> string + { + auto buffer = bits::format::string_writer{}; + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); + return buffer.release(); + } + + //! Format a given string with the provided arguments. + //! + //! @param iterator The iterator to write to. + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return An iterator past the last element written. + template Output, typename... ArgumentTypes> + auto format_to(Output iterator, format_string...> format, + ArgumentTypes &&... args) -> Output + { + auto buffer = bits::format::iterator_writer{iterator}; + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); + return buffer.iterator(); + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/observer_ptr.hpp b/libs/kstd/kstd/bits/observer_ptr.hpp new file mode 100644 index 0000000..2593d7a --- /dev/null +++ b/libs/kstd/kstd/bits/observer_ptr.hpp @@ -0,0 +1,163 @@ +#ifndef KSTD_OBSERVER_PTR_HPP +#define KSTD_OBSERVER_PTR_HPP + +// IWYU pragma: private, include + +#include + +#include +#include +#include +#include +#include + +namespace kstd +{ + + template + struct observer_ptr + { + //! The type of the element being pointed to. + using element_type = ElementType; + + //! Construct an empty observer pointer. + constexpr observer_ptr() noexcept = default; + + //! Construct an empty observer pointer from a null pointer. + constexpr observer_ptr(std::nullptr_t) noexcept {} + + //! Construct an observer pointer from a raw pointer. + constexpr explicit observer_ptr(element_type * pointer) + : m_ptr{pointer} + {} + + //! Construct an observer pointer from another observer pointer. + template + requires std::convertible_to + constexpr observer_ptr(observer_ptr other) noexcept + : m_ptr{other.get()} + {} + + //! Copy construct an observer pointer. + constexpr observer_ptr(observer_ptr const & other) noexcept = default; + + //! Move construct an observer pointer. + constexpr observer_ptr(observer_ptr && other) noexcept = default; + + //! Copy assign an observer pointer. + constexpr auto operator=(observer_ptr const & other) noexcept -> observer_ptr & = default; + + //! Move assign an observer pointer. + constexpr auto operator=(observer_ptr && other) noexcept -> observer_ptr & = default; + + //! Stop watching the the watched object. + //! + //! @return The currently watched object, or nullptr if no object is being watched. + [[nodiscard]] constexpr auto release() noexcept -> element_type * + { + return std::exchange(m_ptr, nullptr); + } + + //! Reset the observer pointer. + //! + //! @param pointer The new object to watch. + constexpr auto reset(element_type * pointer = nullptr) noexcept -> void + { + m_ptr = pointer; + } + + //! Swap the observer pointer with another observer pointer. + //! + //! @param other The other observer pointer to swap with. + constexpr auto swap(observer_ptr & other) noexcept -> void + { + std::swap(m_ptr, other.m_ptr); + } + + //! Get the currently watched object. + //! + //! @return The currently watched object, or nullptr if no object is being watched. + [[nodiscard]] constexpr auto get() const noexcept -> element_type * + { + return m_ptr; + } + + //! Check if the observer pointer is watching an object. + //! + //! @return True if the observer pointer is watching an object, false otherwise. + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return m_ptr != nullptr; + } + + //! Get the currently watched object. + //! + //! @return A reference to the currently watched object. + [[nodiscard]] constexpr auto operator*() const -> std::add_lvalue_reference_t + { + throw_on_null(); + return *m_ptr; + } + + //! Get the currently watched object. + //! + //! @return A pointer to the currently watched object. + [[nodiscard]] constexpr auto operator->() const -> element_type * + { + throw_on_null(); + return m_ptr; + } + + //! Convert the observer pointer to a raw pointer. + //! + //! @return A pointer to the currently watched object. + constexpr explicit operator element_type *() const noexcept + { + return m_ptr; + } + + //! Compare the observer pointer with another observer pointer. + //!> + //! @param other The other observer pointer to compare with. + //! @return The result of the comparison. + constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default; + + private: + //! Throw an exception if the observer pointer is null. + //! + //! @throws std::runtime_error if the observer pointer is null. + constexpr auto throw_on_null() const -> void + { + if (m_ptr == nullptr) + { + os::panic("[kstd:observer_ptr] Dereferencing a null observer pointer"); + } + } + + //! The raw pointer to the watched object. + ElementType * m_ptr{}; + }; + + //! Swap two observer pointers. + //! + //! @param lhs The first observer pointer to swap. + //! @param rhs The second observer pointer to swap. + template + constexpr auto swap(observer_ptr & lhs, observer_ptr & rhs) noexcept -> void + { + lhs.swap(rhs); + } + + //! Create an observer pointer from a raw pointer. + //! + //! @param pointer The raw pointer to create an observer pointer from. + //! @return An observer pointer to the given raw pointer. + template + constexpr auto make_observer(ElementType * pointer) noexcept -> observer_ptr + { + return observer_ptr{pointer}; + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/observer_ptr.test.cpp b/libs/kstd/kstd/bits/observer_ptr.test.cpp new file mode 100644 index 0000000..1ba9c63 --- /dev/null +++ b/libs/kstd/kstd/bits/observer_ptr.test.cpp @@ -0,0 +1,360 @@ +#include +#include + +#include + +#include +#include +#include +#include + +namespace +{ + struct Base + { + }; + + struct Derived : Base + { + }; + + struct Element + { + int value{}; + + constexpr auto operator<=>(Element const &) const noexcept = default; + }; +} // namespace + +SCENARIO("Observer Pointer initialization and construction", "[observer_ptr]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto ptr = kstd::observer_ptr{}; + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + } + + WHEN("constructing from a nullptr") + { + auto ptr = kstd::observer_ptr{nullptr}; + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + } + + WHEN("constructing from a raw pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + + THEN("the observer pointer is not null") + { + REQUIRE(ptr); + } + + THEN("the observer pointer points to the correct object") + { + REQUIRE(&*ptr == &value); + } + } + + WHEN("copy constructing from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = ptr; + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("copy constructing from an existing observer pointer with a compatible type") + { + auto value = Derived{}; + auto ptr = kstd::observer_ptr(&value); + kstd::observer_ptr copy = ptr; + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("copy assigning from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = ptr; + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("move constructing from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = std::move(ptr); + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("move assigning from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = std::move(ptr); + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("constructing an observer pointer using make_observer") + { + auto value = 1; + auto ptr = kstd::make_observer(&value); + + THEN("the observer pointer points to the correct object") + { + REQUIRE(&*ptr == &value); + } + + THEN("the observe pointer has the correct element type") + { + STATIC_REQUIRE(std::is_same_v>); + } + } + } +} + +SCENARIO("Observer pointer modifiers", "[observer_ptr]") +{ + GIVEN("A non-null observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + + WHEN("releasing the observer pointer") + { + auto raw_ptr = ptr.release(); + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + + THEN("the returned pointer points to the correct object") + { + REQUIRE(raw_ptr == &value); + } + } + + WHEN("resetting the observer pointer to nullptr") + { + ptr.reset(); + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + } + + WHEN("resetting the observer pointer to a new object") + { + auto other_value = 2; + ptr.reset(&other_value); + + THEN("the observer pointer points to the new object") + { + REQUIRE(&*ptr == &other_value); + } + } + + WHEN("swapping it with another observer pointer") + { + auto other_value = 2; + auto other_ptr = kstd::observer_ptr{&other_value}; + ptr.swap(other_ptr); + + THEN("the observer pointer points to the other object") + { + REQUIRE(&*ptr == &other_value); + } + + THEN("the other observer pointer points to the original object") + { + REQUIRE(&*other_ptr == &value); + } + } + + WHEN("using namespace-level swap to swap it with another observer pointer") + { + using std::swap; + auto other_value = 2; + auto other_ptr = kstd::observer_ptr{&other_value}; + swap(ptr, other_ptr); + + THEN("the observer pointer points to the other object") + { + REQUIRE(&*ptr == &other_value); + } + + THEN("the other observer pointer points to the original object") + { + REQUIRE(&*other_ptr == &value); + } + } + } +} + +SCENARIO("Observer pointer observers", "[observer_ptr]") +{ + GIVEN("A non-null observer pointer") + { + auto value = Element{1}; + auto ptr = kstd::observer_ptr{&value}; + + WHEN("getting the raw pointer") + { + auto raw_ptr = ptr.get(); + + THEN("the raw pointer points to the correct object") + { + REQUIRE(raw_ptr == &value); + } + } + + WHEN("dereferencing the observer pointer") + { + auto dereferenced = *ptr; + + THEN("the dereferenced value is the correct value") + { + REQUIRE(dereferenced == value); + } + } + + WHEN("writing through the observer pointer with the arrow operator") + { + ptr->value = 2; + + THEN("the value is updated") + { + REQUIRE(value.value == 2); + } + } + + WHEN("converting the observer pointer to a raw pointer") + { + auto raw_ptr = static_cast(ptr); + + THEN("the raw pointer points to the correct object") + { + REQUIRE(raw_ptr == &value); + } + } + + WHEN("checking the observer pointer as a boolean") + { + THEN("it returns true") + { + REQUIRE(static_cast(ptr)); + } + } + } + + GIVEN("A null observer pointer") + { + auto ptr = kstd::observer_ptr{}; + + WHEN("checking the observer pointer as a boolean") + { + THEN("it returns false") + { + REQUIRE_FALSE(static_cast(ptr)); + } + } + + WHEN("dereferencing the observer pointer") + { + THEN("the observer pointer panics") + { + REQUIRE_THROWS_AS(*ptr, kstd::tests::os_panic); + } + } + + WHEN("writing through the observer pointer with the arrow operator") + { + THEN("the observer pointer panics") + { + REQUIRE_THROWS_AS(ptr->value = 2, kstd::tests::os_panic); + } + } + } +} + +SCENARIO("Observer pointer comparisons", "[observer_ptr]") +{ + GIVEN("Observer pointers to elements of an array") + { + auto arr = std::array{1, 2}; + auto ptr1 = kstd::observer_ptr{&arr[0]}; + auto ptr2 = kstd::observer_ptr{&arr[1]}; + + WHEN("comparing the same observer pointer") + { + THEN("they are equal") + { + REQUIRE(ptr1 == ptr1); + REQUIRE((ptr1 <=> ptr1) == std::strong_ordering::equal); + } + } + + WHEN("comparing different observer pointers") + { + THEN("they are ordered correctly") + { + REQUIRE(ptr1 != ptr2); + REQUIRE(ptr1 < ptr2); + REQUIRE(ptr1 <= ptr2); + REQUIRE(ptr2 > ptr1); + REQUIRE(ptr2 >= ptr1); + REQUIRE((ptr1 <=> ptr2) == std::strong_ordering::less); + REQUIRE((ptr2 <=> ptr1) == std::strong_ordering::greater); + } + } + } + + GIVEN("A null observer pointer") + { + auto ptr = kstd::observer_ptr{}; + + WHEN("comparing with another null observer pointer") + { + auto other_ptr = kstd::observer_ptr{}; + + THEN("they are equal") + { + REQUIRE(ptr == other_ptr); + REQUIRE((ptr <=> other_ptr) == std::strong_ordering::equal); + } + } + } +} \ No newline at end of file diff --git a/libs/kstd/kstd/bits/print_sink.hpp b/libs/kstd/kstd/bits/print_sink.hpp new file mode 100644 index 0000000..af765e0 --- /dev/null +++ b/libs/kstd/kstd/bits/print_sink.hpp @@ -0,0 +1,17 @@ +#ifndef KSTD_BITS_PRINT_SINK_HPP +#define KSTD_BITS_PRINT_SINK_HPP + +// IWYU pragma: private, include + +namespace kstd +{ + + enum struct print_sink + { + stdout, + stderr, + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/shared_ptr.hpp b/libs/kstd/kstd/bits/shared_ptr.hpp new file mode 100644 index 0000000..8930095 --- /dev/null +++ b/libs/kstd/kstd/bits/shared_ptr.hpp @@ -0,0 +1,648 @@ +#ifndef KSTD_BITS_SHARED_PTR_HPP +#define KSTD_BITS_SHARED_PTR_HPP + +#include +#include +#include +#include + +// IWYU pragma: private, include + +namespace kstd +{ + /** + * @brief Control block for shared_ptr and weak_ptr. This control block contains the reference counts for shared + * ownership and weak ownership. The shared_count tracks the number of shared_ptr instances that own the managed + * object, while the weak_count tracks the number of weak_ptr instances that reference the managed object. + * The weak_count is needed to determine when it is safe to delete the shared_control_block itself + */ + struct shared_control_block + { + std::atomic shared_count; + std::atomic weak_count; + + explicit shared_control_block(std::size_t shared = 1, std::size_t weak = 0) + : shared_count(shared) + , weak_count(weak) + {} + }; + + template + struct shared_ptr; + + /** + * @brief weak_ptr is a smart pointer that holds a non-owning weak reference to an object that is managed by + * shared_ptr. It must be converted to shared_ptr in to be able to access the referenced object. A weak_ptr is created + * as a copy of a shared_ptr, or as a copy of another weak_ptr. A weak_ptr is typically used to break circular + * references between shared_ptr to avoid memory leaks. A weak_ptr does not contribute to the reference count of the + * object, and it does not manage the lifetime of the object. If the object managed by shared_ptr is destroyed, the + * weak_ptr becomes expired and cannot be used to access the object anymore. + */ + template + struct weak_ptr + { + template + friend struct shared_ptr; + + /** + * @brief Constructs a null weak_ptr. + */ + weak_ptr() noexcept + : pointer(nullptr) + , control(nullptr) + {} + + template + requires(std::is_convertible_v) + weak_ptr(shared_ptr const & other) + : pointer(other.pointer) + , control(other.control) + { + if (control != nullptr) + { + ++(control->weak_count); + } + } + + /** + * @brief Copy constructor. Constructs a weak_ptr which shares ownership of the object managed by other. + */ + weak_ptr(weak_ptr const & other) + : pointer(other.pointer) + , control(other.control) + { + if (control != nullptr) + { + ++(control->weak_count); + } + } + + /** + * @brief Move constructor. Constructs a weak_ptr which takes ownership of the object managed by other. + */ + weak_ptr(weak_ptr && other) noexcept + : pointer(other.pointer) + , control(other.control) + { + other.pointer = nullptr; + other.control = nullptr; + } + + /** + * @brief Assignment operator. Assigns the weak_ptr to another weak_ptr. + */ + auto operator=(weak_ptr const & other) -> weak_ptr & + { + if (this != &other) + { + cleanup(); + pointer = other.pointer; + control = other.control; + if (control != nullptr) + { + ++(control->weak_count); + } + } + + return *this; + } + + /** + * @brief Move assignment operator. Move-assigns a weak_ptr from other. + */ + auto operator=(weak_ptr && other) noexcept -> weak_ptr & + { + if (this != &other) + { + cleanup(); + pointer = other.pointer; + control = other.control; + other.pointer = nullptr; + other.control = nullptr; + } + + return *this; + } + + /** + * @brief Destructor. Cleans up resources if necessary. + */ + ~weak_ptr() + { + cleanup(); + } + + /** + * @brief Returns a shared_ptr that shares ownership of the managed object if the managed object still exists, or an + * empty shared_ptr otherwise. + */ + [[nodiscard]] auto lock() const -> shared_ptr + { + return shared_ptr(*this); + } + + private: + auto cleanup() -> void + { + if (control != nullptr) + { + if (--(control->weak_count) == 0 && control->shared_count == 0) + { + delete control; + } + } + } + + T * pointer; + shared_control_block * control; + }; + + /** + * @brief enable_shared_from_this is a base class that allows an object that is currently managed by a shared_ptr to + * create additional shared_ptr instances that share ownership of the same object. This is usefl when you want to + * create shared_ptr instances in a member function of the object. + * + * @tparam T The type of the managed object. + */ + template + struct enable_shared_from_this + { + template + friend struct shared_ptr; + + friend T; + + public: + /** + * @brief Returns a shared_ptr that shares ownership of *this. + */ + auto shared_from_this() -> shared_ptr + { + return shared_ptr(weak_this); + } + + /** + * @brief Returns a shared_ptr that shares ownership of *this. + */ + auto shared_from_this() const -> shared_ptr + { + return shared_ptr(weak_this); + } + + private: + enable_shared_from_this() = default; + enable_shared_from_this(enable_shared_from_this const &) = default; + auto operator=(enable_shared_from_this const &) -> enable_shared_from_this & = default; + ~enable_shared_from_this() = default; + + void internal_assign_ptr(shared_ptr const & ptr) const + { + weak_this = ptr; + } + + mutable weak_ptr weak_this{}; ///< Weak pointer to the object, used for shared_from_this functionality. + }; + + /** + * @brief Shared_pointer is a smart pointer that retains shared ownership of an object through a pointer. Several + * shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of + * the following happens: the last remaining shared_ptr owning the object is destroyed; the last remaining + * shared_ptr owning the object is assigned another pointer via operator= or reset(). A + * shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used + * to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(), + * the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count + * reaches zero. + * + * @tparam T The type of the managed object. + */ + template + struct shared_ptr + { + template + friend struct shared_ptr; + + template + friend struct weak_ptr; + + /** + * @brief Construct an empty shared_ptr. + */ + shared_ptr() noexcept + : pointer(nullptr) + , control(nullptr) + {} + + /** + * @brief Construct an empty shared_ptr from nullptr. + */ + shared_ptr(std::nullptr_t) noexcept + : pointer(nullptr) + , control(nullptr) + {} + + /** + * @brief Constructor. + * + * @param pointer A pointer to an object to manage (default is nullptr). + */ + template + requires(std::is_convertible_v) + explicit shared_ptr(U * pointer = nullptr) + : pointer(pointer) + , control(pointer != nullptr ? new shared_control_block() : nullptr) + { + assign_enable_shared_from_this(pointer); + } + + /** + * @brief Constructor a shared_ptr from a weak_ptr. If other is not expired, constructs a shared_ptr which shares + * ownership of the object managed by other. Otherwise, constructs an empty shared_ptr. + * + * @param other The weak_ptr to construct from. + */ + template + requires(std::is_convertible_v) + explicit shared_ptr(weak_ptr const & other) + : pointer(nullptr) + , control(nullptr) + { + if (other.control != nullptr && other.control->shared_count != 0) + { + pointer = other.pointer; + control = other.control; + ++(control->shared_count); + } + } + + /** + * @brief Copy constructor. + * + * @param other The shared_ptr to copy from. + */ + shared_ptr(shared_ptr const & other) + : pointer(other.pointer) + , control(other.control) + { + if (control != nullptr) + { + ++(control->shared_count); + } + } + + /** + * @brief Converting copy constructor for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other The shared_ptr to copy from. + */ + template + requires(std::is_convertible_v) + shared_ptr(shared_ptr const & other) + : pointer(other.pointer) + , control(other.control) + { + if (control != nullptr) + { + ++(control->shared_count); + } + } + + /** + * @brief Move constructor. + * + * @param other The shared_ptr to move from. + */ + shared_ptr(shared_ptr && other) noexcept + : pointer(other.pointer) + , control(other.control) + { + other.pointer = nullptr; + other.control = nullptr; + } + + /** + * @brief Converting move constructor for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other The shared_ptr to move from. + */ + template + requires(std::is_convertible_v) + shared_ptr(shared_ptr && other) noexcept + : pointer(other.pointer) + , control(other.control) + { + other.pointer = nullptr; + other.control = nullptr; + } + + /** + * @brief Copy assignment operator. Replaces the managed object with the one managed by r. Shares ownership of the + * object managed by r. If r manages no object, *this manages no object too. Equivalent to + * shared_ptr(r).swap(*this). + * + * @param other Another smart pointer to share the ownership with. + * @return Reference to this shared pointer. + */ + auto operator=(shared_ptr const & other) -> shared_ptr & + { + if (this != &other) + { + cleanup(); + pointer = other.pointer; + control = other.control; + + if (control != nullptr) + { + ++(control->shared_count); + } + } + + return *this; + } + + /** + * @brief Converting copy assignment for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other Another smart pointer to share ownership with. + * @return Reference to this shared pointer. + */ + template + requires(std::is_convertible_v) + auto operator=(shared_ptr const & other) -> shared_ptr & + { + cleanup(); + pointer = other.pointer; + control = other.control; + + if (control != nullptr) + { + ++(control->shared_count); + } + + return *this; + } + + /** + * @brief Move assignment operator. Move-assigns a shared_ptr from r. After the assignment, *this contains a copy of + * the previous state of r, and r is empty. Equivalent to shared_ptr(std::move(r)).swap(*this). + * + * @param other Another smart pointer to acquire the ownership from. + * @return Reference to this shared pointer. + */ + auto operator=(shared_ptr && other) noexcept -> shared_ptr & + { + if (this != &other) + { + cleanup(); + pointer = other.pointer; + control = other.control; + other.pointer = nullptr; + other.control = nullptr; + } + + return *this; + } + + /** + * @brief Converting move assignment for compatible shared_ptr types. + * + * @tparam U Source pointer element type. + * @param other Another smart pointer to acquire ownership from. + * @return Reference to this shared pointer. + */ + template + requires(std::is_convertible_v) + auto operator=(shared_ptr && other) noexcept -> shared_ptr & + { + cleanup(); + pointer = other.pointer; + control = other.control; + other.pointer = nullptr; + other.control = nullptr; + + return *this; + } + + /** + * @brief Reset this shared_ptr to empty via nullptr assignment. + */ + auto operator=(std::nullptr_t) noexcept -> shared_ptr & + { + cleanup(); + pointer = nullptr; + control = nullptr; + return *this; + } + + /** + * @brief Destructor. Cleans up resources if necessary. + */ + ~shared_ptr() + { + cleanup(); + } + + /** + * @brief Replaces the managed object. + * + * @param ptr Pointer to a new object to manage (default = nullptr). + */ + void reset(T * ptr = nullptr) + { + cleanup(); + pointer = ptr; + control = ptr != nullptr ? new shared_control_block() : nullptr; + assign_enable_shared_from_this(ptr); + } + + /** + * @brief Exchanges the stored pointer values and the ownerships of *this and r. Reference counts, if any, are not + * adjusted. + * + * @param other The shared_ptr to swap with. + */ + void swap(shared_ptr & other) + { + std::swap(pointer, other.pointer); + std::swap(control, other.control); + } + + /** + * @brief Dereference operator. If get() is a null pointer, the behavior is undefined. + * + * @return Returns the object owned by *this, equivalent to *get(). + */ + [[nodiscard]] auto operator*() const -> T & + { + return *pointer; + } + + /** + * @brief Member access operator. + * + * @return Returns a pointer to the object owned by *this, i.e. get(). + */ + [[nodiscard]] auto operator->() const -> T * + { + return pointer; + } + + /** + * @brief Returns a pointer to the managed object or nullptr if no object is owned. + * + * @return Pointer to the managed object or nullptr if no object is owned. + */ + [[nodiscard]] auto get() const -> T * + { + return pointer; + } + + /** + * @brief Returns the number of different shared_ptr instances (*this included) managing the current object. If + * there is no managed object, ​0​ is returned. + * + * @note Common use cases include comparison with ​0​. If use_count returns zero, the shared pointer is empty + * and manages no objects (whether or not its stored pointer is nullptr). Comparison with 1. If use_count returns 1, + * there are no other owners. + * + * @return The number of Shared_pointer instances managing the current object or ​0​ if there is no managed + * object. + */ + [[nodiscard]] auto use_count() const -> std::size_t + { + if (control != nullptr) + { + return control->shared_count; + } + + return 0; + } + + /** + * @brief Checks whether *this owns an object, i.e. whether get() != nullptr. + * + * @return true if *this owns an object, false otherwise. + */ + [[nodiscard]] explicit operator bool() const + { + return pointer != nullptr; + } + + /** + * @brief Compare shared_ptr with nullptr. + */ + [[nodiscard]] auto operator==(std::nullptr_t) const -> bool + { + return pointer == nullptr; + } + + /** + * @brief Compare nullptr with shared_ptr. + */ + [[nodiscard]] friend auto operator==(std::nullptr_t, shared_ptr const & ptr) -> bool + { + return ptr.pointer == nullptr; + } + + private: + /** + * @brief If the candidate type inherits from enable_shared_from_this, assigns the internal weak pointer to this + * shared_ptr. This weak_ptr is used to implement shared_from_this functionality for the candidate type. If the + * candidate type does not inherit from enable_shared_from_this, this function does nothing. + * + * @tparam U The candidate type to check for enable_shared_from_this inheritance. + * @param candidate The candidate object to assign the internal weak pointer for. + */ + template + auto assign_enable_shared_from_this(U * candidate) -> void + { + if constexpr (requires(U * p, shared_ptr const & sp) { p->internal_assign_ptr(sp); }) + { + if (candidate != nullptr) + { + candidate->internal_assign_ptr(*this); + } + } + } + + /** + * @brief Releases ownership and deletes the object if this was the last reference to the owned managed object. + */ + auto cleanup() -> void + { + if (control != nullptr) + { + if (--(control->shared_count) == 0) + { + delete pointer; + pointer = nullptr; + + if (control->weak_count == 0) + { + delete control; + } + } + } + } + + T * pointer; ///< The managed object. + shared_control_block * control; ///< Shared control block. + }; + + /** + * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls + * lhs.swap(rhs). + * + * @tparam T Type of the managed object. + * @param lhs, rhs Smart pointers whose contents to swap. + */ + template + auto swap(shared_ptr & lhs, shared_ptr & rhs) -> void + { + lhs.swap(rhs); + } + + /** + * @brief Constructs an object of type T and wraps it in a shared_ptr. Constructs a non-array type T. The + * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is + * not an array type. The function is equivalent to: shared_ptr(new T(std::forward(args)...)). + * + * @tparam T Type of the managed object. + * @tparam Args Argument types for T's constructor. + * @param args List of arguments with which an instance of T will be constructed. + * @returns Shared_pointer of an instance of type T. + */ + template + auto make_shared(Args &&... args) -> shared_ptr + { + return shared_ptr(new T(std::forward(args)...)); + } + + /** + * @brief Equality operator for shared_ptr. Two shared_ptr instances are equal if they point to the same object + * @tparam T, U Types of the managed objects of the shared_ptr instances being compared. + * @param lhs, rhs The shared_ptr instances to compare. + * @return true if lhs and rhs point to the same object, false otherwise. + */ + template + [[nodiscard]] auto inline operator==(shared_ptr const & lhs, shared_ptr const & rhs) -> bool + { + return lhs.get() == rhs.get(); + } + + /** + * @brief Three-way comparison operator for shared_ptr. Compares the stored pointers of lhs and rhs using operator<=>. + * @tparam T, U Types of the managed objects of the shared_ptr instances being compared. + * @param lhs, rhs The shared_ptr instances to compare. + * @return The result of comparing the stored pointers of lhs and rhs using operator<=> + */ + template + [[nodiscard]] auto inline operator<=>(shared_ptr const & lhs, shared_ptr const & rhs) + { + return lhs.get() <=> rhs.get(); + } +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/bits/unique_ptr.hpp b/libs/kstd/kstd/bits/unique_ptr.hpp new file mode 100644 index 0000000..3d803b4 --- /dev/null +++ b/libs/kstd/kstd/bits/unique_ptr.hpp @@ -0,0 +1,199 @@ +#ifndef KSTD_BITS_UNIQUE_POINTER_HPP +#define KSTD_BITS_UNIQUE_POINTER_HPP + +#include + +// IWYU pragma: private, include + +namespace kstd +{ + /** + * @brief Unique_pointer is a smart pointer that owns (is responsible for) and manages another object via a pointer + * and subsequently disposes of that object when the unique_ptr goes out of scope. + * + * @tparam T Type of the managed object. + */ + template + struct unique_ptr + { + template + friend struct unique_ptr; + + /** + * @brief Constructor. + * + * @param ptr A pointer to an object to manage (default is nullptr). + */ + explicit unique_ptr(T * ptr = nullptr) + : pointer(ptr) + { + // Nothing to do. + } + + /** + * @brief Destructor that deletes the managed object. + */ + ~unique_ptr() + { + delete pointer; + } + + /** + * @brief Deleted copy constructor to enforce unique ownership. + */ + unique_ptr(unique_ptr const &) = delete; + + template + requires(std::is_convertible_v) + unique_ptr(unique_ptr && other) noexcept + : pointer{std::exchange(other.pointer, nullptr)} + {} + + /** + * @brief Deleted copy assignment operator to enforce unique ownership. + */ + auto operator=(unique_ptr const &) -> unique_ptr & = delete; + + /** + * @brief Move constructor. + * + * @param other Unique pointer to move from. + */ + unique_ptr(unique_ptr && other) noexcept + : pointer{std::exchange(other.pointer, nullptr)} + {} + + /** + * @brief Move assignment operator. Transfers ownership from other to *this as if by calling reset(r.release()). + * + * @param other Smart pointer from which ownership will be transferred. + * @return Reference to this unique pointer. + */ + auto operator=(unique_ptr && other) noexcept -> unique_ptr & + { + if (this != &other) + { + delete pointer; + pointer = std::exchange(other.pointer, nullptr); + } + return *this; + } + + /** + * @brief Dereference operator. If get() is a null pointer, the behavior is undefined. + * + * @return Returns the object owned by *this, equivalent to *get(). + */ + auto operator*() const -> T & + { + return *pointer; + } + + /** + * @brief Member access operator. + * + * @return Returns a pointer to the object owned by *this, i.e. get(). + */ + auto operator->() const -> T * + { + return pointer; + } + + /** + * @brief Returns a pointer to the managed object or nullptr if no object is owned. + * + * @return Pointer to the managed object or nullptr if no object is owned. + */ + [[nodiscard]] auto get() const -> T * + { + return pointer; + } + + /** + * @brief Checks whether *this owns an object, i.e. whether get() != nullptr. + * + * @return true if *this owns an object, false otherwise. + */ + explicit operator bool() const noexcept + { + return pointer != nullptr; + } + + /** + * @brief Releases the ownership of the managed object, if any. + * get() returns nullptr after the call. + * The caller is responsible for cleaning up the object (e.g. by use of get_deleter()). + * + * @return Pointer to the managed object or nullptr if there was no managed object, i.e. the value which would be + * returned by get() before the call. + */ + auto release() -> T * + { + return std::exchange(pointer, nullptr); + } + + /** + * @brief Replaces the managed object. + * + * @note A test for self-reset, i.e. whether ptr points to an object already managed by *this, is not performed, + * except where provided as a compiler extension or as a debugging assert. Note that code such as + * p.reset(p.release()) does not involve self-reset, only code like p.reset(p.get()) does. + * + * @param ptr Pointer to a new object to manage (default = nullptr). + */ + auto reset(T * ptr = nullptr) -> void + { + delete std::exchange(pointer, ptr); + } + + /** + * @brief Swaps the managed objects and associated deleters of *this and another unique_ptr object other. + * + * @param other Another unique_ptr object to swap the managed object and the deleter with. + */ + auto swap(unique_ptr & other) -> void + { + using std::swap; + swap(pointer, other.pointer); + } + + /** + * @brief Defaulted three-way comparator operator. + */ + auto operator<=>(unique_ptr const & other) const = default; + + private: + T * pointer; ///< The managed pointer. + }; + + /** + * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls + * lhs.swap(rhs). + * + * @tparam T Type of the managed object. + * @param lhs, rhs Smart pointers whose contents to swap. + */ + template + auto swap(unique_ptr & lhs, unique_ptr & rhs) -> void + { + lhs.swap(rhs); + } + + /** + * @brief Constructs an object of type T and wraps it in a unique_ptr. Constructs a non-array type T. The + * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is + * not an array type. The function is equivalent to: unique_ptr(new T(std::forward(args)...)). + * + * @tparam T Type of the managed object. + * @tparam Args Argument types for T's constructor. + * @param args List of arguments with which an instance of T will be constructed. + * @returns Unique_pointer of an instance of type T. + */ + template + auto make_unique(Args &&... args) -> unique_ptr + { + return unique_ptr(new T(std::forward(args)...)); + } +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/cstring b/libs/kstd/kstd/cstring new file mode 100644 index 0000000..bd8b28d --- /dev/null +++ b/libs/kstd/kstd/cstring @@ -0,0 +1,21 @@ +#ifndef KSTD_CSTRING +#define KSTD_CSTRING + +#include + +namespace kstd::libc +{ + + extern "C" + { + auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void *; + auto memset(void * dest, int value, std::size_t size) noexcept -> void *; + auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void *; + auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int; + + auto strlen(char const * string) noexcept -> std::size_t; + } + +} // namespace kstd::libc + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/ext/bitfield_enum b/libs/kstd/kstd/ext/bitfield_enum new file mode 100644 index 0000000..80fe9d2 --- /dev/null +++ b/libs/kstd/kstd/ext/bitfield_enum @@ -0,0 +1,65 @@ +#ifndef KSTD_EXT_BITFIELD_ENUM_HPP +#define KSTD_EXT_BITFIELD_ENUM_HPP + +#include +#include +#include + +namespace kstd::ext +{ + + template + requires std::is_enum_v + struct is_bitfield_enum : std::false_type + { + }; + + //! @concept Specifies that an enum is to be used to define bits in a bitfield. + template + concept bitfield_enum = is_bitfield_enum::value; + +}; // namespace kstd::ext + +template +constexpr auto operator|(EnumType lhs, EnumType rhs) -> EnumType +{ + return std::bit_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); +} + +template +constexpr auto operator|=(EnumType & lhs, EnumType rhs) -> EnumType & +{ + return lhs = lhs | rhs; +} + +template +constexpr auto operator&(EnumType lhs, EnumType rhs) -> EnumType +{ + return std::bit_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); +} + +template +constexpr auto operator&=(EnumType & lhs, EnumType rhs) -> EnumType & +{ + return lhs = lhs & rhs; +} + +template +constexpr auto operator^(EnumType lhs, EnumType rhs) -> EnumType +{ + return std::bit_cast(std::to_underlying(lhs) ^ std::to_underlying(rhs)); +} + +template +constexpr auto operator^=(EnumType & lhs, EnumType rhs) -> EnumType & +{ + return lhs = lhs ^ rhs; +} + +template +constexpr auto operator~(EnumType lhs) -> EnumType +{ + return std::bit_cast(~std::to_underlying(lhs)); +} + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/flat_map b/libs/kstd/kstd/flat_map new file mode 100644 index 0000000..f12b1b5 --- /dev/null +++ b/libs/kstd/kstd/flat_map @@ -0,0 +1,365 @@ +#ifndef KSTD_FLAT_MAP_HPP +#define KSTD_FLAT_MAP_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + + template, + typename KeyContainerType = kstd::vector, typename MappedContainerType = kstd::vector> + struct flat_map + { + using key_container_type = KeyContainerType; + using mapped_container_type = MappedContainerType; + using key_type = KeyType; + using mapped_type = MappedType; + using value_type = std::pair; + using key_compare = KeyCompare; + using reference = std::pair; + using const_reference = std::pair; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using iterator = bits::flat_map_iterator; + using const_iterator = + bits::flat_map_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using containers = struct + { + key_container_type keys; + mapped_container_type values; + }; + + //! Compare two object of type value_type. + struct value_compare + { + constexpr auto operator()(const_reference lhs, const_reference rhs) const -> bool + { + return lhs.first < rhs.first; + } + }; + + //! Construct an empty flat map. + constexpr flat_map() + : flat_map{key_compare{}} + {} + + //! Construct an empty flat map using the given custom comparator. + //! + //! @param comparator The comparator to use for comparing keys. + constexpr explicit flat_map(key_compare const & comparator) + : m_containers{} + , m_comparator{comparator} + {} + + //! Get a reference to the mapped value associated with the given key. + //! + //! @warning This function will panic if the key is not found. + //! @param key The key to look up. + //! @return A reference to the mapped value. + [[nodiscard]] constexpr auto at(key_type const & key) -> mapped_type & + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) + { + auto offset = std::distance(m_containers.keys.begin(), found); + return *(m_containers.values.begin() + offset); + } + os::panic("[kstd::flat_map] Key not found"); + } + + //! Get a reference to the mapped value associated with the given key. + //! + //! @warning This function will panic if the key is not found. + //! @param key The key to look up. + //! @return A const reference to the mapped value. + [[nodiscard]] constexpr auto at(key_type const & key) const -> mapped_type const & + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) + { + auto offset = std::distance(m_containers.keys.cbegin(), found); + return *(m_containers.values.cbegin() + offset); + } + os::panic("[kstd::flat_map] Key not found"); + } + + //! Get a reference to the mapped value associated with the given key. + //! + //! @note This overload only participates in overload resolution if the key compare type is transparent. + //! @warning This function will panic if the key is not found. + //! @param x The key to look up. + //! @return A reference to the mapped value. + template + requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } + [[nodiscard]] constexpr auto at(K const & x) -> mapped_type & + { + auto found = find(x); + if (found != end()) + { + auto offset = std::distance(m_containers.keys.begin(), found.key_iterator()); + return *(m_containers.values.begin() + offset); + } + os::panic("[kstd::flat_map] Key not found"); + } + + //! Get a reference to the mapped value associated with the given key. + //! @note This overload only participates in overload resolution if the key compare type is transparent. + //! @warning This function will panic if the key is not found. + //! @param x The key to look up. + //! @return A const reference to the mapped value. + template + requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } + [[nodiscard]] auto at(K const & x) const -> mapped_type const & + { + auto found = find(x); + if (found != end()) + { + auto offset = std::distance(m_containers.keys.cbegin(), found.key_iterator()); + return *(m_containers.values.cbegin() + offset); + } + os::panic("[kstd::flat_map] Key not found"); + } + + //! Get an iterator to the first element. + [[nodiscard]] auto begin() noexcept -> iterator + { + return iterator{m_containers.keys.begin(), m_containers.values.begin()}; + } + + //! Get an iterator to the first element. + [[nodiscard]] auto begin() const noexcept -> const_iterator + { + return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()}; + } + + //! Get an iterator to the first element. + [[nodiscard]] auto cbegin() const noexcept -> const_iterator + { + return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto end() noexcept -> iterator + { + return iterator{m_containers.keys.end(), m_containers.values.end()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto end() const noexcept -> const_iterator + { + return const_iterator{m_containers.keys.cend(), m_containers.values.cend()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto cend() const noexcept -> const_iterator + { + return const_iterator{m_containers.keys.cend(), m_containers.values.cend()}; + } + + //! Get an iterator to the first element. + [[nodiscard]] auto rbegin() noexcept -> reverse_iterator + { + return reverse_iterator{end()}; + } + + //! Get an iterator to the first element. + [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator{cend()}; + } + + //! Get an iterator to the first element. + [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator{cend()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto rend() noexcept -> reverse_iterator + { + return reverse_iterator{begin()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator{cbegin()}; + } + + //! Get an iterator to the element past the last element. + [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator{cbegin()}; + } + + //! Check whether this flat map is empty or not + [[nodiscard]] auto empty() const noexcept -> bool + { + return m_containers.keys.empty(); + } + + //! Get the number of elements in this flat map. + [[nodiscard]] auto size() const noexcept -> size_type + { + return m_containers.keys.size(); + } + + //! Get the maximum number of elements possible in this flat map + [[nodiscard]] auto max_size() const noexcept -> size_type + { + return std::min(m_containers.keys.max_size(), m_containers.values.max_size()); + } + + //! Try to insert a new key-value pair into the map. + //! + //! @param args Arguments to use for constructing the key-value pair. + //! @return A pair of an iterator to the inserted element and a boolean indicating whether the insertion took place. + template + auto emplace(Args &&... args) -> std::pair + requires std::constructible_from + { + auto value = value_type{std::forward(args)...}; + auto found = std::ranges::lower_bound(m_containers.keys, value.first, m_comparator); + + if (found != m_containers.keys.cend() && !m_comparator(value.first, *found) && !m_comparator(*found, value.first)) + { + auto offset = std::distance(m_containers.keys.begin(), found); + return { + iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}, + false + }; + } + + auto offset = std::distance(m_containers.keys.begin(), found); + auto key_iterator = m_containers.keys.begin() + offset; + auto mapped_iterator = m_containers.values.begin() + offset; + + auto inserted_key = m_containers.keys.insert(key_iterator, std::move(value.first)); + auto inserted_mapped = m_containers.values.insert(mapped_iterator, std::move(value.second)); + + return { + iterator{inserted_key, inserted_mapped}, + true + }; + } + + //! Try to insert a element for the given key into this map. + //! + //! This function does nothing if the key is already present. + //! + //! @param key The key to insert a value for. + //! @param args The arguments to use to construct the mapped value. + template + auto try_emplace(key_type const & key, Args &&... args) -> std::pair + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(*found, key) && !m_comparator(key, *found)) + { + return {found, false}; + } + + auto offset = std::distance(m_containers.keys.cbegin(), found); + auto intersertion_point = m_containers.value.begin() + offset; + + auto inserted_key = m_containers.keys.emplace(key); + auto inserted_mapped = m_containers.values.emplace(std::forward(args)...); + + return { + iterator{inserted_key, inserted_mapped}, + true + }; + } + + //! Find an element with an equivalent key. + //! + //! @param key The key to look up. + //! @return An iterator to the element with the equivalent key, or end() if no such element is found. + [[nodiscard]] auto find(key_type const & key) noexcept -> iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) + { + auto offset = std::distance(m_containers.keys.begin(), found); + return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}; + } + return end(); + } + + //! Find an element with an equivalent key. + //! + //! @param key The key to look up. + //! @return An iterator to the element with the equivalent key, or end() if no such element is found. + [[nodiscard]] auto find(key_type const & key) const noexcept -> const_iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key)) + { + auto offset = std::distance(m_containers.keys.cbegin(), found); + return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset}; + } + return cend(); + } + + //! Find an element with an equivalent key. + //! @note This overload only participates in overload resolution if the key compare type is transparent. + //! @param x The key to look up. + //! @return An iterator to the element with the equivalent key, or end() if no such element is found. + template + requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } + [[nodiscard]] auto find(K const & x) noexcept -> iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x)) + { + auto offset = std::distance(m_containers.keys.begin(), found); + return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset}; + } + return end(); + } + + //! Find an element with an equivalent key. + //! @note This overload only participates in overload resolution if the key compare type is transparent. + //! @param x The key to look up. + //! @return An iterator to the element with the equivalent key, or end() if no such element is found. + template + requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; } + [[nodiscard]] auto find(K const & x) const noexcept -> const_iterator + { + auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator); + if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x)) + { + auto offset = std::distance(m_containers.keys.cbegin(), found); + return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset}; + } + return cend(); + } + + //! Check if the map contains the given key. + //! + //! @param key The key to check. + //! @return true iff. the key is found, false otherwise. + [[nodiscard]] constexpr auto contains(key_type const & key) const noexcept -> bool + { + return find(key) != cend(); + } + + private: + containers m_containers; + key_compare m_comparator; + }; +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/flat_map.test.cpp b/libs/kstd/kstd/flat_map.test.cpp new file mode 100644 index 0000000..2e5a47c --- /dev/null +++ b/libs/kstd/kstd/flat_map.test.cpp @@ -0,0 +1,314 @@ +#include + +#include + +#include + +#include +#include +#include + +SCENARIO("Flat Map initialization and construction", "[flat_map]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto map = kstd::flat_map{}; + + THEN("the Flat Map does not contain elements") + { + REQUIRE_FALSE(map.contains(1)); + } + } + } +} + +SCENARIO("Flat Map modifiers", "[flat_map]") +{ + GIVEN("An empty Flat Map") + { + auto map = kstd::flat_map{}; + + WHEN("emplacing a new element") + { + auto [it, inserted] = map.emplace(1, 100); + + THEN("the map contains the new element") + { + REQUIRE(inserted); + REQUIRE(map.contains(1)); + } + } + + WHEN("emplacing an existing element") + { + map.emplace(1, 100); + auto [it, inserted] = map.emplace(1, 200); + + THEN("the map does not insert the duplicate") + { + REQUIRE_FALSE(inserted); + REQUIRE(map.contains(1)); + } + } + } +} + +SCENARIO("Flat Map element access", "[flat_map]") +{ + GIVEN("A populated Flat Map") + { + auto map = kstd::flat_map{}; + map.emplace(1, 10); + map.emplace(2, 20); + map.emplace(3, 30); + + WHEN("accessing an existing element with at()") + { + auto & val = map.at(2); + + THEN("it returns a reference to the mapped value") + { + REQUIRE(val == 20); + } + + THEN("the mapped value can be modified") + { + val = 200; + REQUIRE(map.at(2) == 200); + } + } + + WHEN("accessing a non-existent element with at()") + { + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); + } + } + } + + GIVEN("A const populated Flat Map") + { + auto map_builder = kstd::flat_map{}; + map_builder.emplace(1, 10); + map_builder.emplace(2, 20); + auto const map = map_builder; + + WHEN("accessing an existing element with const at()") + { + auto const & val = map.at(2); + + THEN("it returns a const reference to the mapped value") + { + REQUIRE(val == 20); + } + } + + WHEN("accessing a non-existent element with const at()") + { + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); + } + } + } +} + +SCENARIO("Flat Map iterators", "[flat_map]") +{ + GIVEN("A populated Flat Map") + { + auto map = kstd::flat_map{}; + map.emplace(1, 10); + map.emplace(2, 20); + map.emplace(3, 30); + + WHEN("using forward iterators") + { + THEN("they navigate the elements in the correct forward order") + { + auto it = map.begin(); + REQUIRE(it != map.end()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it != map.end()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.end()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it == map.end()); + } + + THEN("const forward iterators provide correct access") + { + auto it = map.cbegin(); + REQUIRE(it != map.cend()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it != map.cend()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.cend()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it == map.cend()); + } + + THEN("assignment through the proxy modifies the mapped value") + { + auto it = map.begin(); + + *it = std::pair{1, 100}; + + REQUIRE(it->second == 100); + REQUIRE(map.at(1) == 100); + } + + THEN("structured bindings evaluate correctly") + { + auto it = map.cbegin(); + + auto [key, value] = *it; + + REQUIRE(key == 1); + REQUIRE(value == 10); + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + } + } + + WHEN("using reverse iterators") + { + THEN("they navigate the elements in the correct reverse order") + { + auto it = map.rbegin(); + REQUIRE(it != map.rend()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it != map.rend()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.rend()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it == map.rend()); + } + + THEN("const reverse iterators provide correct access") + { + auto it = map.crbegin(); + REQUIRE(it != map.crend()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it != map.crend()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.crend()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it == map.crend()); + } + } + } + + GIVEN("an empty Flat Map") + { + auto map = kstd::flat_map{}; + + WHEN("getting iterators") + { + THEN("begin() equals end() and cbegin() equals cend()") + { + REQUIRE(map.begin() == map.end()); + REQUIRE(map.cbegin() == map.cend()); + } + + THEN("rbegin() equals rend() and crbegin() equals crend()") + { + REQUIRE(map.rbegin() == map.rend()); + REQUIRE(map.crbegin() == map.crend()); + } + } + } +} + +SCENARIO("Flat Map heterogeneous element access", "[flat_map]") +{ + GIVEN("A populated Flat Map with a transparent comparator") + { + auto map = kstd::flat_map>{}; + map.emplace(1, 10); + map.emplace(2, 20); + map.emplace(3, 30); + + WHEN("accessing an existing element with a different key type via at()") + { + long const key = 2L; + auto & val = map.at(key); + + THEN("it returns a reference to the mapped value") + { + REQUIRE(val == 20); + } + + THEN("the mapped value can be modified") + { + val = 200; + REQUIRE(map.at(2L) == 200); + } + } + + WHEN("accessing a non-existent element with a different key type via at()") + { + long const key = 4L; + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); + } + } + } + + GIVEN("A const populated Flat Map with a transparent comparator") + { + auto map_builder = kstd::flat_map>{}; + map_builder.emplace(1, 10); + map_builder.emplace(2, 20); + auto const map = map_builder; + + WHEN("accessing an existing element with a different key type via const at()") + { + long const key = 2L; + auto const & val = map.at(key); + + THEN("it returns a const reference to the mapped value") + { + REQUIRE(val == 20); + } + } + + WHEN("accessing a non-existent element with a different key type via const at()") + { + long const key = 4L; + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); + } + } + } +} diff --git a/libs/kstd/kstd/format b/libs/kstd/kstd/format new file mode 100644 index 0000000..e04b79a --- /dev/null +++ b/libs/kstd/kstd/format @@ -0,0 +1,22 @@ +#ifndef KSTD_FORMAT_HPP +#define KSTD_FORMAT_HPP + +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/format.test.cpp b/libs/kstd/kstd/format.test.cpp new file mode 100644 index 0000000..624779a --- /dev/null +++ b/libs/kstd/kstd/format.test.cpp @@ -0,0 +1,114 @@ +#include + +#include + +#include +#include + +SCENARIO("Formatting to a new string", "[format]") +{ + GIVEN("a format string without any placeholders") + { + auto const & fmt = "This is a test"; + + WHEN("calling format with without any arguments.") + { + auto result = kstd::format(fmt); + + THEN("the result is the unmodified string") + { + REQUIRE(result == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + auto result = kstd::format(fmt, 1, 2, 3); + + THEN("the result is the unmodified string") + { + REQUIRE(result == "This is a test"); + } + } + } + + GIVEN("a format string with placeholders") + { + auto const & fmt = "Here are some placeholders: {} {} {}"; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + auto result = kstd::format(fmt, 1, true, 'a'); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 1 true a"); + } + } + + WHEN("calling format with too many arguments") + { + auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 2 false b"); + } + } + } +} + +SCENARIO("Formatting to an output iterator", "[format]") +{ + auto buffer = std::ostringstream{}; + + GIVEN("a format string without any placeholders") + { + auto const & fmt = "This is a test"; + + WHEN("calling format with without any arguments.") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt); + + THEN("the unmodified string is written to the iterator") + { + REQUIRE(buffer.str() == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, 2, 3); + + THEN("the unmodified string is written to the iterator") + { + REQUIRE(buffer.str() == "This is a test"); + } + } + } + + GIVEN("a format string with placeholders") + { + auto const & fmt = "Here are some placeholders: {} {} {}"; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, true, -100); + + THEN("the formatted string is written to the iterator") + { + REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100"); + } + } + + WHEN("calling format with too many arguments") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 2, false, -200, 4, 5, 6); + + THEN("the formatted string is written to the iterator") + { + REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200"); + } + } + } +} \ No newline at end of file diff --git a/libs/kstd/kstd/libc/stdlib.cpp b/libs/kstd/kstd/libc/stdlib.cpp new file mode 100644 index 0000000..7ed051f --- /dev/null +++ b/libs/kstd/kstd/libc/stdlib.cpp @@ -0,0 +1,19 @@ +#include + +namespace kstd::libc +{ + + extern "C" + { + [[noreturn]] auto abort() -> void + { + kstd::os::abort(); + } + + [[noreturn, gnu::weak]] auto free(void *) -> void + { + kstd::os::panic("Tried to call free."); + } + } + +} // namespace kstd::libc \ No newline at end of file diff --git a/libs/kstd/kstd/libc/string.cpp b/libs/kstd/kstd/libc/string.cpp new file mode 100644 index 0000000..b7cdb82 --- /dev/null +++ b/libs/kstd/kstd/libc/string.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include +#include +#include +#include + +namespace kstd::libc +{ + + auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void * + { + auto dest_span = std::span{static_cast(dest), size}; + auto src_span = std::span{static_cast(src), size}; + + for (std::size_t i = 0; i < size; ++i) + { + dest_span[i] = src_span[i]; + } + + return dest; + } + + auto memset(void * dest, int value, std::size_t size) noexcept -> void * + { + auto const byte_value = static_cast(static_cast(value)); + auto dest_span = std::span{static_cast(dest), size}; + + for (std::size_t i = 0; i < size; ++i) + { + dest_span[i] = byte_value; + } + + return dest; + } + + auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int + { + auto left_span = std::span{static_cast(lhs), size}; + auto right_span = std::span{static_cast(rhs), size}; + auto mismatched = std::ranges::mismatch(left_span, right_span); + + if (mismatched.in1 == left_span.end()) + { + return 0; + } + + return std::bit_cast(*mismatched.in1) - std::bit_cast(*mismatched.in2); + } + + auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void * + { + auto dest_span = std::span{static_cast(dest), size}; + auto src_span = std::span{static_cast(src), size}; + if (dest < src) + { + for (std::size_t i = 0; i < size; ++i) + { + dest_span[i] = src_span[i]; + } + } + else + { + for (std::size_t i = size; i > 0; --i) + { + dest_span[i - 1] = src_span[i - 1]; + } + } + return dest; + } + + auto strlen(char const * string) noexcept -> std::size_t + { + return std::distance(string, std::ranges::find(string, nullptr, '\0')); + } + +} // namespace kstd::libc \ No newline at end of file diff --git a/libs/kstd/kstd/memory b/libs/kstd/kstd/memory new file mode 100644 index 0000000..f108c6d --- /dev/null +++ b/libs/kstd/kstd/memory @@ -0,0 +1,8 @@ +#ifndef KSTD_MEMORY_HPP +#define KSTD_MEMORY_HPP + +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/mutex b/libs/kstd/kstd/mutex new file mode 100644 index 0000000..b2a31aa --- /dev/null +++ b/libs/kstd/kstd/mutex @@ -0,0 +1,101 @@ +#ifndef KSTD_MUTEX_HPP +#define KSTD_MUTEX_HPP + +#include + +namespace kstd +{ + + //! A non-recursive mutex. + struct mutex + { + mutex(); + ~mutex(); + + mutex(mutex const &) = delete; + mutex(mutex &&) = delete; + + auto operator=(mutex const &) -> mutex & = delete; + auto operator=(mutex &&) -> mutex & = delete; + + //! Lock the mutex. + //! + //! @note This function blocks for as long as the mutex is not available. + auto lock() -> void; + + //! Try to lock the mutex. + //! + //! @note This function never blocks. + //! @return @p true iff. the mutex was successfully locked, @p false otherwise. + auto try_lock() -> bool; + + //! Unlock the mutex. + //! + //! @note The behavior is undefined if the mutex is not currently held by the thread unlocking it. + auto unlock() -> void; + + private: + std::atomic_flag m_locked{}; + }; + + //! A tag type to specify that a given @p lockable wrapper should adopt ownership of the @p lockable. + struct adopt_lock_t + { + explicit adopt_lock_t() = default; + } constexpr inline adopt_lock{}; + + //! A tag type to specify that a given @p lockable wrapper should defer locking the @p lockable. + struct defer_lock_t + { + explicit defer_lock_t() = default; + } constexpr inline defer_lock{}; + + //! A tag type to specify that a given @p lockable wrapper should attempt to lock the @p lockable. + struct try_to_lock_t + { + explicit try_to_lock_t() = default; + } constexpr inline try_to_lock{}; + + //! An RAII wrapper for a single @p lockable like a kstd::mutex. + template + struct lock_guard + { + using mutex_type = MutexType; + + //! Construct a new lock_guard and immediately lock the given mutex. + //! + //! @note This function will block until the mutex was successfully locked. + //! @param mutex The mutex to lock. + lock_guard(MutexType & mutex) noexcept + : m_mutex(mutex) + { + m_mutex.lock(); + } + + //! Construct a new lock_guard and take ownership of the given mutex. + //! + //! @note The behavior is undefined if the mutex is not owned by the current thread. + //! @param mutex The mutex to take ownership of. + lock_guard(MutexType & mutex, adopt_lock_t) noexcept + : m_mutex(mutex) + {} + + //! Destroy this lock_guard and release the owned mutex. + ~lock_guard() + { + m_mutex.unlock(); + } + + lock_guard(lock_guard const &) noexcept = delete; + lock_guard(lock_guard &&) noexcept = delete; + + auto operator=(lock_guard const &) noexcept -> lock_guard & = delete; + auto operator=(lock_guard &&) noexcept -> lock_guard & = delete; + + private: + mutex_type & m_mutex; + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/mutex.cpp b/libs/kstd/kstd/mutex.cpp new file mode 100644 index 0000000..7387657 --- /dev/null +++ b/libs/kstd/kstd/mutex.cpp @@ -0,0 +1,38 @@ +#include + +#include + +#include + +namespace kstd +{ + + mutex::mutex() = default; + + mutex::~mutex() + { + if (m_locked.test(std::memory_order_relaxed)) + { + os::panic("[KSTD] Tried to destroy a locked mutex."); + } + } + + auto mutex::lock() -> void + { + while (!try_lock()) + { + asm volatile("nop"); + } + } + + auto mutex::try_lock() -> bool + { + return !m_locked.test_and_set(std::memory_order_acquire); + } + + auto mutex::unlock() -> void + { + m_locked.clear(std::memory_order_release); + } + +} // namespace kstd diff --git a/libs/kstd/kstd/os/error.cpp b/libs/kstd/kstd/os/error.cpp new file mode 100644 index 0000000..f969cb5 --- /dev/null +++ b/libs/kstd/kstd/os/error.cpp @@ -0,0 +1,12 @@ +#include + +namespace kstd::os +{ + + [[gnu::weak, noreturn]] + auto abort() -> void + { + os::panic("Abort called."); + } + +} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/kstd/os/error.hpp b/libs/kstd/kstd/os/error.hpp new file mode 100644 index 0000000..9d43fb1 --- /dev/null +++ b/libs/kstd/kstd/os/error.hpp @@ -0,0 +1,34 @@ +#ifndef KSTD_OS_ERROR_HPP +#define KSTD_OS_ERROR_HPP + +#include +#include + +namespace kstd::os +{ + /** + * @brief Handle an unrecoverable library error. + * + * The operating system kernel may choose to implement this function in order to try to perform additional cleanup + * before terminating execution. If the kernel does not implement this function, the default implementation will be + * chosen. This default implementation doest nothing. + */ + [[noreturn]] + auto abort() -> void; + + /** + * @brief Terminate execution of the operating system. + * + * The operating system must implement this function. This function must terminate the execution of the operating + * system kernel as is. It may choose to restart the kernel or to halt execution entirely. The implementation must + * guarantee that execution never return from this function. + * + * @param message A message describing the reason for termination of execution. + * @param where The source code location at which the panic was triggered. In general, no argument shall be provided + * for this parameter, thus implicitly capturing the location at which the call originates. + */ + [[noreturn]] + auto panic(std::string_view message, std::source_location where = std::source_location::current()) -> void; +} // namespace kstd::os + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/os/print.hpp b/libs/kstd/kstd/os/print.hpp new file mode 100644 index 0000000..36cb43d --- /dev/null +++ b/libs/kstd/kstd/os/print.hpp @@ -0,0 +1,14 @@ +#ifndef KSTD_OS_PRINT_HPP +#define KSTD_OS_PRINT_HPP + +#include +#include + +#include + +namespace kstd::os +{ + auto vprint(print_sink sink, std::string_view format, kstd::format_args args) -> void; +} // namespace kstd::os + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/print b/libs/kstd/kstd/print new file mode 100644 index 0000000..1033f72 --- /dev/null +++ b/libs/kstd/kstd/print @@ -0,0 +1,69 @@ +#ifndef KSTD_PRINT +#define KSTD_PRINT + +#include // IWYU pragma: export +#include +#include + +#include + +namespace kstd +{ + + //! @qualifier kernel-defined + //! Format the given string using the given arguments and print it to the currently active output device. + //! + //! @param format The format string + //! @param args The arguments to use to place in the format string's placeholders. + template + // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) + auto print(kstd::format_string...> format, Args const &... args) -> void + { + auto const arg_store = kstd::make_format_args(args...); + os::vprint(print_sink::stdout, format.str_view, arg_store.args); + } + + //! @qualifier kernel-defined + //! Format the given error string using the given arguments and print it to the currently active output device. + //! + //! @param format The format string + //! @param args The arguments to use to place in the format string's placeholders. + template + // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) + auto print(print_sink sink, kstd::format_string...> format, Args &&... args) -> void + { + auto const arg_store = kstd::make_format_args(args...); + os::vprint(sink, format.str_view, arg_store.args); + } + + //! @qualifier kernel-defined + //! Format the given string using the given arguments and print it, including a newline, to the currently active + //! output device. + //! + //! @param format The format string + //! @param args The arguments to use to place in the format string's placeholders. + template + // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) + auto println(kstd::format_string...> format, Args &&... args) -> void + { + print(print_sink::stdout, format, std::forward(args)...); + print(print_sink::stdout, "\n"); + } + + //! @qualifier kernel-defined + //! Format the given error string using the given arguments and print it, including a newline, to the currently active + //! output device. + //! + //! @param format The format string + //! @param args The arguments + template + // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) + auto println(print_sink sink, kstd::format_string...> format, Args &&... args) -> void + { + print(sink, format, std::forward(args)...); + print(sink, "\n"); + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/ranges b/libs/kstd/kstd/ranges new file mode 100644 index 0000000..78c3adb --- /dev/null +++ b/libs/kstd/kstd/ranges @@ -0,0 +1,21 @@ +#ifndef KSTD_RANGES +#define KSTD_RANGES + +#include // 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/kstd/stack b/libs/kstd/kstd/stack new file mode 100644 index 0000000..02e44ea --- /dev/null +++ b/libs/kstd/kstd/stack @@ -0,0 +1,192 @@ +#ifndef KSTD_STACK_HPP +#define KSTD_STACK_HPP + +#include + +#include +#include + +namespace kstd +{ + /** + * @brief Custom stack implementation mirroring the std::stack to allow for the usage of STL functionality with our + * custom memory management. + * + * @tparam T Element the stack instance should contain. + * @tparam Container Actual underlying container that should be wrapped to provide stack functionality. Requires + * access to pop_back(), push_back(), back(), size(), empty() and emplace_back() + */ + template> + struct stack + { + using container_type = Container; ///< Type of the underlying container used to implement stack-like interface. + using value_type = Container::value_type; ///< Type of the elements contained in the underlying container. + using size_type = Container::size_type; ///< Type of the size in the underlying container. + using reference = Container::reference; ///< Type of reference to the elements. + using const_reference = Container::const_reference; ///< Type of constant reference to the elements. + + /** + * @brief Default Constructor. + */ + stack() = default; + + stack(stack const &) = delete; + stack(stack &&) = delete; + auto operator=(stack const &) -> stack & = delete; + auto operator=(stack &&) -> stack & = delete; + /** + * @brief Constructs data with the given amount of elements containing the given value or alternatively the default + * constructed value. + * + * @param n Amount of elements we want to create and set the given value for. + * @param initial Inital value of all elements in the underlying data array. + */ + explicit stack(size_type n, value_type initial = value_type{}) + : _container(n, initial) + { + // Nothing to do. + } + + /** + * @brief Constructs data by copying all element from the given exclusive range. + * + * @tparam InputIterator Template that should have atleast input iterator characteristics. + * @param first Input iterator to the first element in the range we want to copy from. + * @param last Input iterator to one past the last element in the range we want to copy from. + */ + template + explicit stack(InputIterator first, InputIterator last) + : _container(first, last) + { + // Nothing to do. + } + + /** + * @brief Construct data by copying all elements from the initializer list. + * + * @param elements List we want to copy all elements from. + */ + explicit stack(std::initializer_list elements) + : _container(elements) + { + // Nothing to do. + } + + /** + * @brief Copy constructor. + * + * @note Allocates underlying data container with the same capacity as stack we are copying from and copies all + * elements from it. + * + * @param other Other instance of stack we want to copy the data from. + */ + stack(stack const & other) + : _container(other) + { + // Nothing to do. + } + + /** + * @brief Destructor. + */ + ~stack() = default; + + /** + * @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. + */ + auto size() const -> size_type + { + return _container.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 top() -> reference + { + return _container.back(); + } + + /** + * @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 top() const -> const_reference + { + return _container.back(); + } + + /** + * @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(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 + auto push(U && value) -> void + { + _container.push_back(std::forward(value)); + } + + /** + * @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).... + * + * 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 + auto emplace(Args &&... args) -> reference + { + _container.emplace_back(std::forward(args)...); + } + + /** + * @brief Removes the last element of the container. + * + * @note 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() -> void + { + _container.pop_back(); + } + + /** + * @brief Whether there are currently any items this container or not. + * + * @return True if there are no elements, false if there are. + */ + auto empty() const -> bool + { + return _container.empty(); + } + + private: + container_type _container = {}; ///< Underlying container used by the stack to actually save the data. + }; + +} // namespace kstd + +#endif diff --git a/libs/kstd/kstd/string b/libs/kstd/kstd/string new file mode 100644 index 0000000..e228a04 --- /dev/null +++ b/libs/kstd/kstd/string @@ -0,0 +1,380 @@ +#ifndef KSTD_STRING_HPP +#define KSTD_STRING_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace kstd +{ + /** + * @brief A simple string implementation that owns its data and provides basic operations. + */ + struct string + { + //! The type of the characters contained in this string. + using value_type = char; + //! The type of the underlying storage used by this string. + using storage_type = kstd::vector; + //! The type of all sizes used in and with this string. + using size_type = std::size_t; + //! The type of the difference between two iterators. + using difference_type = std::ptrdiff_t; + //! The type of references to single values in this string. + using reference = value_type &; + //! The type of references to constant single values in this string. + using const_reference = value_type const &; + //! The type of pointers to single values in this string. + using pointer = value_type *; + //! The type of pointers to constant single values in this string. + using const_pointer = value_type const *; + //! The type of iterators into this string. + using iterator = pointer; + //! The type of constant iterators into this string. + using const_iterator = const_pointer; + + /** + * @brief Constructs an empty null-terminated string. + */ + string() + : m_storage{value_type{'\0'}} + {} + + /** + * @brief Constructs a string from a string view by copying the characters into owned storage. + * @param view The string view to copy the characters from. + */ + string(std::string_view view) + : string() + { + append(view); + } + + /** + * @brief Constructs a string from a null-terminated C-style string by copying the characters into owned storage. + * @param c_str The null-terminated C-style string to copy. + */ + string(char const * c_str) + : string() + { + if (c_str != nullptr) + { + append(std::string_view{c_str}); + } + } + + /** + * @brief Constructs a string containing a single character. + * @param c The character to copy. + */ + string(value_type c) + : string() + { + push_back(c); + } + + /** + * @brief Constructs a string by copying another string. + * @param other The string to copy. + */ + constexpr string(string const & other) + : m_storage{other.m_storage} + {} + + /** + * @brief Destructs the string. + */ + constexpr ~string() = default; + + /** + * @brief Assigns the value of another string to this string. + * @param other The string to assign from. + * @return A reference to this string. + */ + constexpr auto operator=(string const & other) -> string & = default; + + /** + * @brief Returns the number of characters in this string, not including the null terminator. + */ + [[nodiscard]] constexpr auto size() const noexcept -> size_type + { + return m_storage.empty() ? 0 : m_storage.size() - 1; + } + + /** + * @brief Checks if this string is empty, not including the null terminator. + */ + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return size() == 0; + } + + /** + * @brief Clears the content of the string, resulting in an empty string. + * The string remains null-terminated after this operation. + */ + constexpr auto clear() -> void + { + m_storage.clear(); + m_storage.push_back(value_type{'\0'}); + } + + //! Get a pointer to the underlying storage of the string + [[nodiscard]] constexpr auto data() noexcept -> pointer + { + return m_storage.data(); + } + + //! Get a const pointer to the underlying storage of the string + [[nodiscard]] constexpr auto data() const noexcept -> const_pointer + { + return m_storage.data(); + } + + //! Get a const pointer to the underlying storage of the string + [[nodiscard]] constexpr auto c_str() const noexcept -> const_pointer + { + return data(); + } + + //! Get an iterator to the beginning of the string + [[nodiscard]] constexpr auto begin() noexcept -> iterator + { + return data(); + } + + //! Get an const iterator to the beginning of the string + [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator + { + return data(); + } + + //! Get an const iterator to the beginning of the string + [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator + { + return begin(); + } + + //! Get an iterator to the end of the string + [[nodiscard]] constexpr auto end() noexcept -> iterator + { + return data() + size(); + } + + //! Get an const iterator to the end of the string + [[nodiscard]] constexpr auto end() const noexcept -> const_iterator + { + return data() + size(); + } + + //! Get an const iterator to the end of the string + [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator + { + return end(); + } + + //! Get a reference to the first character of the string + [[nodiscard]] constexpr auto front() -> reference + { + return m_storage.front(); + } + + //! Get a const reference to the first character of the string + [[nodiscard]] constexpr auto front() const -> const_reference + { + return m_storage.front(); + } + + //! Get a reference to the last character of the string + [[nodiscard]] constexpr auto back() -> reference + { + return m_storage[size() - 1]; + } + + //! Get a const reference to the last character of the string + [[nodiscard]] constexpr auto back() const -> const_reference + { + return m_storage[size() - 1]; + } + + /** + * @brief Appends a character to the end of the string. + * @param ch The character to append. + */ + constexpr auto push_back(value_type ch) -> void + { + m_storage.back() = ch; + m_storage.push_back(value_type{'\0'}); + } + + /** + * @brief Appends a string view to the end of the string by copying the characters into owned storage. + * @param view The string view to append. + * @return A reference to this string. + */ + constexpr auto append(std::string_view view) -> string & + { + if (!view.empty()) + { + std::ranges::for_each(view, [this](auto const ch) { push_back(ch); }); + } + + return *this; + } + + /** + * @brief Appends another string to the end of this string by copying the characters into owned storage. + * @param other The string to append. + * @return A reference to this string. + */ + constexpr auto append(string const & other) -> string & + { + return append(other.view()); + } + + /** + * @brief Appends another string to the end of this string by copying the characters into owned storage. + * @param other The string to append. + * @return A reference to this string. + */ + constexpr auto operator+=(string const & other) -> string & + { + return append(other); + } + + /** + * @brief Appends a character to the end of the string. + * @param ch The character to append. + * @return A reference to this string. + */ + constexpr auto operator+=(value_type ch) -> string & + { + push_back(ch); + return *this; + } + + /** + * @brief Returns a string view of this string, which is a non-owning view into the characters of this string. + */ + [[nodiscard]] constexpr auto view() const noexcept -> std::string_view + { + return std::string_view{data(), size()}; + } + + private: + //! The underlying storage of the string, which owns the characters and ensures null-termination. + storage_type m_storage{}; + }; + + /** + * @brief Concatenates a strings and a character and returns the result as a new string. + * @param lhs The string to concatenate. + * @param rhs The string to concatenate. + * @return A new string that is the result of concatenating @p lhs and @p rhs. + */ + [[nodiscard]] constexpr auto inline operator+(string const & lhs, string const & rhs) -> string + { + string result{lhs}; + result += rhs; + return result; + } + + /** + * @brief Converts an unsigned integer to a string by converting each digit to the corresponding character and + * concatenating them. + * @tparam N The type of the unsigned integer to convert. + * @param value The unsigned integer to convert. + * @return A string representation of the given unsigned integer. + */ + template + requires std::unsigned_integral + [[nodiscard]] constexpr auto inline to_string(N value) -> string + { + if (value == 0) + { + return "0"; + } + + string result; + + while (value > 0) + { + char const digit = '0' + (value % 10); + result.push_back(digit); + value /= 10; + } + + std::reverse(result.begin(), result.end()); + return result; + } + + [[nodiscard]] constexpr auto inline operator==(string const & lhs, string const & rhs) -> bool + { + return lhs.view() == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(string const & lhs, std::string_view rhs) -> bool + { + return lhs.view() == rhs; + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, std::string_view rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(std::string_view lhs, string const & rhs) -> bool + { + return lhs == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(std::string_view lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(string const & lhs, char const * rhs) -> bool + { + return lhs.view() == std::string_view{rhs}; + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, char const * rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(char const * lhs, string const & rhs) -> bool + { + return std::string_view{lhs} == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(char const * lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + + template<> + struct formatter : formatter + { + auto format(string const & str, format_context & context) const -> void + { + formatter::format(str.view(), context); + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp new file mode 100644 index 0000000..53d7c9a --- /dev/null +++ b/libs/kstd/kstd/string.test.cpp @@ -0,0 +1,445 @@ +#include + +#include + +#include +#include +#include + +SCENARIO("String initialization and construction", "[string]") +{ + GIVEN("Nothing") + { + WHEN("constructing an empty string") + { + auto str = kstd::string{}; + + THEN("the size is zero and therefore the string is empty") + { + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + + THEN("the string is empty") + { + REQUIRE(str.view() == std::string_view{}); + } + } + } + + GIVEN("A string view") + { + auto view = std::string_view{"Blub Blub"}; + + WHEN("constructing a string from string_view") + { + auto str = kstd::string{view}; + + THEN("the string is not empty and has the same size as the view") + { + REQUIRE(!str.empty()); + REQUIRE(str.size() == view.size()); + } + + THEN("the string contains the same characters as the view") + { + REQUIRE(str.view() == view); + } + } + } + + GIVEN("A C-style string") + { + auto c_str = "Blub Blub"; + + WHEN("constructing a string from the C-style string") + { + auto str = kstd::string{c_str}; + + THEN("the string is not empty and has the same size as the C-style string") + { + REQUIRE(!str.empty()); + REQUIRE(str.size() == std::strlen(c_str)); + } + + THEN("the string contains the same characters as the C-style string") + { + REQUIRE(str.view() == c_str); + } + } + } + + GIVEN("A character") + { + auto ch = 'x'; + + WHEN("constructing a string from the character") + { + auto str = kstd::string{ch}; + + THEN("the string is not empty and has size 1") + { + REQUIRE(!str.empty()); + REQUIRE(str.size() == 1); + } + + THEN("the string contains the same character as the given character") + { + REQUIRE(str.view() == std::string_view{&ch, 1}); + } + } + } + + GIVEN("Another string") + { + auto other = kstd::string{"Blub Blub"}; + + WHEN("copy constructing a new string") + { + auto str = kstd::string{other}; + + THEN("the new string contains the same characters as the original") + { + REQUIRE(str.view() == other.view()); + } + } + + auto str = kstd::string{"Blub"}; + + WHEN("copy assigning another string") + { + auto other = kstd::string{"Blub Blub"}; + str = other; + + THEN("the string contains the same characters as the assigned string") + { + REQUIRE(str.view() == other.view()); + } + } + } + + GIVEN("A string") + { + auto str = kstd::string{"Hello"}; + + WHEN("copy assigning a string view") + { + auto view = std::string_view{"Hello, world!"}; + str = view; + + THEN("the string contains the same characters as the assigned view") + { + REQUIRE(str.view() == view); + } + } + } + + GIVEN("A string") + { + auto str = kstd::string{"Hello"}; + + WHEN("copy assigning a C-style string") + { + auto c_str = "Hello, world!"; + str = c_str; + + THEN("the string contains the same characters as the assigned C-style string") + { + REQUIRE(str.view() == c_str); + } + } + } +} + +SCENARIO("String concatenation", "[string]") +{ + GIVEN("Two strings") + { + auto str1 = kstd::string{"Blub"}; + auto str2 = kstd::string{" Blub"}; + + WHEN("appending the second string to the first string") + { + str1.append(str2); + + THEN("the first string contains the characters of both strings concatenated") + { + REQUIRE(str1.view() == "Blub Blub"); + } + + THEN("the size of the first string is the sum of the sizes of both strings") + { + REQUIRE(str1.size() == str2.size() + 4); + } + } + + WHEN("using operator+= to append the second string to the first string") + { + str1 += str2; + + THEN("the first string contains the characters of both strings concatenated") + { + REQUIRE(str1.view() == "Blub Blub"); + } + + THEN("the size of the first string is the sum of the sizes of both strings") + { + REQUIRE(str1.size() == str2.size() + 4); + } + } + + WHEN("using operator+ to concatenate the two strings into a new string") + { + auto str3 = str1 + str2; + + THEN("the new string contains the characters of both strings concatenated") + { + REQUIRE(str3.view() == "Blub Blub"); + } + + THEN("the size of the new string is the sum of the sizes of both strings") + { + REQUIRE(str3.size() == str1.size() + str2.size()); + } + } + } + + GIVEN("A string and a string view") + { + auto str = kstd::string{"Blub"}; + auto view = std::string_view{" Blub"}; + + WHEN("appending the string view to the string") + { + str.append(view); + + THEN("the string contains the characters of both the original string and the appended view concatenated") + { + REQUIRE(str.view() == "Blub Blub"); + } + + THEN("the size of the string is the sum of the sizes of the original string and the appended view") + { + REQUIRE(str.size() == view.size() + 4); + } + } + } + + GIVEN("A string and a character") + { + auto str = kstd::string{"Blub"}; + auto ch = '!'; + + WHEN("appending the character to the string") + { + str.push_back(ch); + + THEN("the string contains the original characters followed by the appended character") + { + REQUIRE(str.view() == "Blub!"); + } + + THEN("the size of the string is one more than the original size") + { + REQUIRE(str.size() == 5); + } + } + + WHEN("using operator+= to append the character to the string") + { + str += ch; + + THEN("the string contains the original characters followed by the appended character") + { + REQUIRE(str.view() == "Blub!"); + } + + THEN("the size of the string is one more than the original size") + { + REQUIRE(str.size() == 5); + } + } + } +} + +SCENARIO("String conversion and comparison", "[string]") +{ + GIVEN("An unsigned integer") + { + constexpr auto value1 = 12345u; + constexpr auto value2 = 0u; + + WHEN("converting the unsigned integer to a string") + { + auto str1 = kstd::to_string(value1); + auto str2 = kstd::to_string(value2); + + THEN("the string contains the decimal representation of the unsigned integer") + { + REQUIRE(str1.view() == "12345"); + REQUIRE(str2.view() == "0"); + } + } + } + + GIVEN("Two strings with the same characters") + { + auto str1 = kstd::string{"Blub Blub"}; + auto str2 = kstd::string{"Blub Blub"}; + + THEN("the strings are equal") + { + REQUIRE(str1 == str2); + } + + THEN("the strings are not unequal") + { + REQUIRE(!(str1 != str2)); + } + } + + GIVEN("A string and a string view with the same characters") + { + auto str = kstd::string{"Blub Blub"}; + auto view = std::string_view{"Blub Blub"}; + + THEN("the string and the string view are equal") + { + REQUIRE(str == view); + REQUIRE(view == str); + } + + THEN("the string and the string view are not unequal") + { + REQUIRE(!(str != view)); + REQUIRE(!(view != str)); + } + } +} + +SCENARIO("String clearing", "[string]") +{ + GIVEN("A non-empty string") + { + auto str = kstd::string{"Blub Blub"}; + + WHEN("clearing the string") + { + str.clear(); + + THEN("the string is empty and has size zero") + { + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + + THEN("the string contains no characters") + { + REQUIRE(str.view() == std::string_view{}); + } + } + } +} + +SCENARIO("String iteration", "[string]") +{ + GIVEN("A string") + { + auto str = kstd::string{"Blub"}; + + WHEN("iterating over the characters of the string as string_view using a range-based for loop") + { + kstd::string result; + + for (auto ch : str.view()) + { + result.push_back(ch); + } + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == str); + } + } + + WHEN("using std::ranges::for_each to iterate over the characters of the string") + { + kstd::string result; + + std::ranges::for_each(str, [&result](auto ch) { result.push_back(ch); }); + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == str); + } + } + + WHEN("using front and back to access the first and last characters of the string") + { + THEN("front returns the first character of the string") + { + REQUIRE(str.front() == 'B'); + } + + THEN("back returns the last character of the string") + { + REQUIRE(str.back() == 'b'); + } + } + } + + GIVEN("A const string") + { + auto const str = kstd::string{"Blub"}; + + WHEN("iterating over the characters of the string as string_view using a range-based for loop") + { + kstd::string result; + + for (auto ch : str.view()) + { + result.push_back(ch); + } + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == str.view()); + } + } + + WHEN("using front and back to access the first and last characters of the string") + { + THEN("front returns the first character of the string") + { + REQUIRE(str.front() == 'B'); + } + + THEN("back returns the last character of the string") + { + REQUIRE(str.back() == 'b'); + } + } + } + + GIVEN("An empty string") + { + auto str = kstd::string{}; + + WHEN("iterating over the characters of an empty string") + { + kstd::string result; + + for (auto ch : str.view()) + { + result.push_back(ch); + } + + THEN("no characters are iterated and the result is an empty string") + { + REQUIRE(result.empty()); + REQUIRE(result.size() == 0); + REQUIRE(result.view() == std::string_view{}); + } + } + } +} diff --git a/libs/kstd/kstd/test_support/os_panic.hpp b/libs/kstd/kstd/test_support/os_panic.hpp new file mode 100644 index 0000000..4396a9f --- /dev/null +++ b/libs/kstd/kstd/test_support/os_panic.hpp @@ -0,0 +1,23 @@ +#ifndef KSTD_TESTS_OS_PANIC_HPP +#define KSTD_TESTS_OS_PANIC_HPP + +#include +#include +#include + +namespace kstd::tests +{ + + struct os_panic : std::runtime_error + { + os_panic(std::string message, std::source_location location) + : std::runtime_error{message} + , location(location) + {} + + std::source_location location; + }; + +} // namespace kstd::tests + +#endif \ No newline at end of file diff --git a/libs/kstd/kstd/test_support/os_panic.test.cpp b/libs/kstd/kstd/test_support/os_panic.test.cpp new file mode 100644 index 0000000..c30411a --- /dev/null +++ b/libs/kstd/kstd/test_support/os_panic.test.cpp @@ -0,0 +1,15 @@ +#include + +#include +#include +#include + +namespace kstd::os +{ + + auto panic(std::string_view message, std::source_location location) + { + throw kstd::tests::os_panic{std::string{message}, location}; + } + +} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/kstd/test_support/test_types.hpp b/libs/kstd/kstd/test_support/test_types.hpp new file mode 100644 index 0000000..8231fd3 --- /dev/null +++ b/libs/kstd/kstd/test_support/test_types.hpp @@ -0,0 +1,313 @@ +#ifndef KSTD_TESTS_TEST_TYPES_HPP +#define KSTD_TESTS_TEST_TYPES_HPP + +#include +#include +#include + +namespace kstd::tests +{ + + //! A type tracking copy and move operations + //! + //! This type is designed to test move and copy semantics of standard library containers implemented in kstd. + struct special_member_tracker + { + //! A value indicating that the object was moved from. + constexpr auto static moved_from_v = -1; + + constexpr special_member_tracker() + : default_constructed_count{1} + {} + + //! Construct a new move tracker with the given value, if any. + constexpr special_member_tracker(int v = 0) + : value{v} + , value_constructed_count{1} + {} + + //! Construct a new move tracker by copying an existing one. + constexpr special_member_tracker(special_member_tracker const & other) + : value{other.value} + , copy_constructed_count{1} + {} + + //! Construct a new move tracker by moving from an existing one. + constexpr special_member_tracker(special_member_tracker && other) noexcept + : value{other.value} + , move_constructed_count{1} + { + other.value = moved_from_v; + } + + //! Copy assign a new move tracker from an existing one. + constexpr auto operator=(special_member_tracker const & other) -> special_member_tracker & + { + if (this != &other) + { + value = other.value; + ++copy_assigned_count; + ++other.copied_from_count; + } + return *this; + } + + //! Move assign a new move tracker from an existing one. + //! + //! This function ensures that the moved-from state is marked. + constexpr auto operator=(special_member_tracker && other) noexcept -> special_member_tracker & + { + if (this != &other) + { + value = other.value; + ++move_assigned_count; + other.value = moved_from_v; + ++other.moved_from_count; + } + return *this; + } + + ~special_member_tracker() + { + ++destroyed_count; + } + + auto reset_counts() -> void + { + default_constructed_count = 0; + copy_constructed_count = 0; + move_constructed_count = 0; + value_constructed_count = 0; + copy_assigned_count = 0; + move_assigned_count = 0; + destroyed_count = 0; + copied_from_count = 0; + moved_from_count = 0; + } + + //! A simple value to be able to track the move-from state. + int value{}; + //! A counter to track how many times an instance of this type was default constructed. + std::size_t default_constructed_count{0}; + //! A counter to track how many times an instance of this type was copy constructed. + std::size_t copy_constructed_count{0}; + //! A counter to track how many times an instance of this type was move constructed. + std::size_t move_constructed_count{0}; + //! A counter to track how many times an instance of this type was value constructed. + std::size_t value_constructed_count{0}; + //! A counter to track how many times an instance of this type was copy assigned. + std::size_t copy_assigned_count{0}; + //! A counter to track how many times an instance of this type was move assigned. + std::size_t move_assigned_count{0}; + //! A counter to track how many times an instance of this type was destroyed. + std::size_t destroyed_count{0}; + //! A counter to track how many times an instance of this type was copied from another instance. + mutable std::size_t copied_from_count{0}; + //! A counter to track how many times an instance of this type was moved from another instance. + std::size_t moved_from_count{0}; + }; + + //! A type that is not default constructible. + //! + //! This type is designed to test default construction semantics of standard library containers implemented in kstd. + struct non_default_constructible + { + //! A simple placeholder value. + int value{}; + + //! Construct a new non-default-constructible object with the given value. + constexpr explicit non_default_constructible(int v) + : value{v} + {} + + //! Compare two non-default-constructible objects for equality. + [[nodiscard]] constexpr auto operator==(non_default_constructible const & other) const -> bool + { + return value == other.value; + } + }; + + //! An allocator that tracks the number of allocations. + //! + //! This allocator is designed to test allocation semantics of standard library containers implemented in kstd. + //! + //! @tparam T The type of the elements to be allocated. + template + struct tracking_allocator + { + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + //! A pointer to a counter that is incremented on allocation. + //! + //! A pointer is used so that multiple allocators can share the same counter. + int * allocation_count{}; + + //! Construct a new tracking allocator referencing the given counter. + tracking_allocator(int * counter) + : allocation_count{counter} + {} + + //! Construct a new tracking allocator by copying from another tracking allocator. + //! + //! This constructor is templated to allow for conversion from allocators of different types. + //! + //! @tparam U The type of the elements to be allocated by the other allocator. + template + tracking_allocator(tracking_allocator const & other) noexcept + : allocation_count{other.allocation_count} + {} + + //! Allocate memory for n elements of type T. + //! + //! @param n The number of elements to allocate. + //! @return A pointer to the allocated memory. + [[nodiscard]] auto allocate(std::size_t n) -> T * + { + if (allocation_count != nullptr) + { + ++(*allocation_count); + } + return static_cast(::operator new(n * sizeof(T))); + } + + //! Deallocate memory for n elements of type T. + //! + //! @param p A pointer to the memory to deallocate. + //! @param n The number of elements to deallocate. + auto deallocate(T * p, std::size_t n) noexcept -> void + { + ::operator delete(p, n * sizeof(T)); + } + + //! Compare two tracking allocators for equality. + //! + //! Two allocators are considered equal if they reference the same allocation counter. + //! + //! @param other The other tracking allocator to compare to. + //! @return True if the two tracking allocators are equal, false otherwise. + [[nodiscard]] auto operator==(tracking_allocator const & other) const -> bool + { + return allocation_count == other.allocation_count; + } + + //! Compare two tracking_allocators for inequality. + //! + //! @param other The other tracking_allocator to compare to. + //! @return True if the two tracking_allocators are not equal, false otherwise. + [[nodiscard]] auto operator!=(tracking_allocator const & other) const -> bool + { + return allocation_count != other.allocation_count; + } + }; + + //! An allocator that propagates copy assignment. + //! + //! This allocator is designed to test copy assignment semantics of standard library containers implemented in kstd. + //! + //! @tparam T The type of the elements to be allocated. + template + struct propagating_allocator : tracking_allocator + { + //! A flag to indicate that the allocator propagates copy assignment. + using propagate_on_container_copy_assignment = std::true_type; + + //! Construct a new propagating allocator referencing the given counter. + //! + //! @param counter A pointer to a counter that is incremented on allocation. + //! @see tracking_allocator::tracking_allocator(int*) + propagating_allocator(int * counter) + : tracking_allocator{counter} + {} + + //! Construct a new propagating allocator by copying from another propagating allocator. + //! + //! This constructor is templated to allow for conversion from allocators of different types. + //! + //! @tparam U The type of the elements to be allocated by the other allocator. + //! @see tracking_allocator::tracking_allocator(tracking_allocator const&) + template + propagating_allocator(propagating_allocator const & other) noexcept + : tracking_allocator{other.allocation_count} + {} + }; + + //! A test input iterator. + //! + //! This iterator is designed to test input iterator semantics of standard library containers implemented in kstd. + struct test_input_iterator + { + using iterator_concept = std::input_iterator_tag; + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = int; + using reference = int const &; + using pointer = int const *; + + //! The current element pointed to by the iterator. + int const * current; + //! The number of elements in the range. + std::size_t count; + + explicit test_input_iterator() + : current{nullptr} + , count{0} + {} + + //! Construct a new test input iterator. + //! + //! @param current The current element pointed to by the iterator. + //! @param count The number of elements in the range. + explicit test_input_iterator(int const * current, std::size_t count) + : current{current} + , count{count} + {} + + //! Dereference the iterator to get the current element. + //! + //! @return The current element pointed to by the iterator. + [[nodiscard]] auto operator*() const -> int + { + return *current; + } + + //! Increment the iterator to point to the next element. + //! + //! @return A reference to the incremented iterator. + auto operator++() -> test_input_iterator & + { + ++current; + --count; + return *this; + } + + //! Increment the iterator to point to the next element. + auto operator++(int) -> void + { + ++*this; + } + + //! Compare two test input iterators for equality. + //! + //! @param other The other test input iterator to compare to. + //! @return True if the two test input iterators are equal, false otherwise. + [[nodiscard]] auto operator==(test_input_iterator const & other) const -> bool + { + if (current == nullptr && other.current == nullptr) + { + return true; + } + + if (current == nullptr || other.current == nullptr) + { + return count == other.count; + } + + return current == other.current && count == other.count; + } + }; + +} // namespace kstd::tests + +#endif diff --git a/libs/kstd/kstd/units b/libs/kstd/kstd/units new file mode 100644 index 0000000..df5eb37 --- /dev/null +++ b/libs/kstd/kstd/units @@ -0,0 +1,149 @@ +#ifndef KSTD_UNITS_HPP +#define KSTD_UNITS_HPP + +#include +#include +#include + +namespace kstd +{ + + //! A basic template for strongly typed units. + template + struct basic_unit + { + using value_type = ValueType; + + constexpr basic_unit() noexcept + : value{} + {} + + explicit constexpr basic_unit(value_type value) noexcept + : value{value} + {} + + explicit constexpr operator value_type() const noexcept + { + return value; + } + + constexpr auto operator+(basic_unit const & other) const noexcept -> basic_unit + { + return basic_unit{value + other.value}; + } + + constexpr auto operator+=(basic_unit const & other) noexcept -> basic_unit & + { + return *this = *this + other; + } + + constexpr auto operator-(basic_unit const & other) const noexcept -> basic_unit + { + return basic_unit{value - other.value}; + } + + constexpr auto operator-=(basic_unit const & other) noexcept -> basic_unit & + { + return *this = *this - other; + } + + constexpr auto operator*(std::integral auto factor) noexcept -> basic_unit + { + return basic_unit{value * factor}; + } + + constexpr auto operator*=(std::integral auto factor) noexcept -> basic_unit + { + return *this = *this * factor; + } + + constexpr auto operator/(std::integral auto divisor) noexcept -> basic_unit + { + return basic_unit{value / divisor}; + } + + constexpr auto operator/=(std::integral auto divisor) noexcept -> basic_unit + { + return *this = *this / divisor; + } + + constexpr auto operator/(basic_unit const & other) const noexcept + { + return value / other.value; + } + + constexpr auto operator<=>(basic_unit const & other) const noexcept -> std::strong_ordering = default; + + value_type value; + }; + + template + constexpr auto operator*(Factor factor, basic_unit const & unit) noexcept + -> basic_unit + { + return basic_unit{unit.value * factor}; + } + + namespace units + { + using bytes = basic_unit; + + constexpr auto KiB(std::size_t value) noexcept -> bytes + { + return bytes{value * 1024}; + } + + constexpr auto MiB(std::size_t value) noexcept -> bytes + { + return bytes{value * 1024 * 1024}; + } + + constexpr auto GiB(std::size_t value) noexcept -> bytes + { + return bytes{value * 1024 * 1024 * 1024}; + } + + template + constexpr auto operator+(ValueType * pointer, bytes offset) -> ValueType * + { + return pointer + offset.value; + } + + } // namespace units + + namespace units_literals + { + constexpr auto operator""_B(unsigned long long value) noexcept -> units::bytes + { + return units::bytes{value}; + } + + constexpr auto operator""_KiB(unsigned long long value) noexcept -> units::bytes + { + return units::KiB(value); + } + + constexpr auto operator""_MiB(unsigned long long value) noexcept -> units::bytes + { + return units::MiB(value); + } + + constexpr auto operator""_GiB(unsigned long long value) noexcept -> units::bytes + { + return units::GiB(value); + } + + } // namespace units_literals + + template + constexpr auto object_size(ValueType const &) -> units::bytes + { + return units::bytes{sizeof(ValueType)}; + } + + template + constexpr auto type_size = units::bytes{sizeof(T)}; + +} // namespace kstd + +#endif diff --git a/libs/kstd/kstd/vector b/libs/kstd/kstd/vector new file mode 100644 index 0000000..c714957 --- /dev/null +++ b/libs/kstd/kstd/vector @@ -0,0 +1,1154 @@ +#ifndef KSTD_VECTOR_HPP +#define KSTD_VECTOR_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + //! A resizable, contiguous container. + //! + //! @tparam ValueType The type of values contained in this vector. + //! @tparam Allocator The type of allocator used for memory management by this container. + template> + struct vector + { + //! The type of the elements contained in this vector. + using value_type = ValueType; + //! The allocator used by this vector for memory management. + using allocator_type = Allocator; + //! The type of all sizes used in and with this vector. + using size_type = std::size_t; + //! The type of the difference between two iterators. + using difference_type = std::ptrdiff_t; + //! The type of references to elements in this vector. + using reference = value_type &; + //! The type of references to constant elements in this vector. + using const_reference = value_type const &; + //! The type of pointers to elements in this vector. + using pointer = std::allocator_traits::pointer; + //! The type of pointers to constant elements in this vector. + using const_pointer = std::allocator_traits::const_pointer; + //! The type of iterators into this container. + using iterator = pointer; + //! The type of constant iterators into this container. + using const_iterator = const_pointer; + //! The type of reverse iterators into this container. + using reverse_iterator = std::reverse_iterator; + //! The type of constant reverse iterators into this container. + using const_reverse_iterator = std::reverse_iterator; + + //! Construct a new, empty vector. + constexpr vector() noexcept(std::is_nothrow_default_constructible_v) + : 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) + : m_allocator{allocator} + , m_size{0} + , m_capacity{0} + , m_data{allocate_n(m_capacity)} + {} + + //! 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) + : 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::construct(m_allocator, m_data + i); + } + } + + //! Construct a new vector and fill it with the given number of copy constructed elements. + //! + //! @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) + : m_allocator{allocator} + , m_size{count} + , m_capacity{m_size} + , m_data{allocate_n(m_capacity)} + { + for (auto i = 0uz; i < count; ++i) + { + std::allocator_traits::construct(m_allocator, m_data + i, value); + } + } + + //! Construct a new vector and initialize it's content by copying all elements in the given range. + //! + //! @tparam ForwardIterator 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 + constexpr vector(ForwardIterator first, ForwardIterator last, + allocator_type const & allocator = + allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) + : m_allocator{allocator} + , m_size{static_cast(std::ranges::distance(first, last))} + , m_capacity{m_size} + , m_data{allocate_n(m_capacity)} + { + for (auto destination = m_data; first != last; ++first, ++destination) + { + std::allocator_traits::construct(m_allocator, destination, *first); + } + } + + //! Construct a new vector and initialize it's content by copying all elements in the given range. + //! + //! @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 + constexpr vector(InputIterator first, InputIterator last, + allocator_type const & allocator = + allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) + : m_allocator{allocator} + , m_size{0} + , m_capacity{0} + , m_data{} + { + while (first != last) + { + emplace_back(*first); + ++first; + } + } + + //! Construct a new vector and initialize it's content by copying all elements in the given range. + //! + //! + template + requires((std::ranges::forward_range || std::ranges::sized_range) && + kstd::bits::container_compatible_range) + 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)) + { + std::allocator_traits::construct(m_allocator, destination++, + std::forward>(element)); + } + } + + //! Construct a new vector and initialize it's content by copying all elements from a given vector. + //! + //! @param other The source vector. + 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)) + { + 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. + constexpr vector(vector && other) noexcept + : 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)) + {} + + //! 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 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 const & allocator) + : m_allocator{allocator} + , m_size{} + , m_capacity{} + , m_data{} + { + if constexpr (!std::allocator_traits::is_always_equal::value) + { + if (m_allocator != other.m_allocator) + { + m_capacity = other.size(); + m_data = allocate_n(capacity()); + m_size = other.size(); + uninitialized_move_with_allocator(other.begin(), begin(), other.size()); + other.clear(); + return; + } + } + 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. + vector(std::initializer_list list, allocator_type const & allocator = allocator_type{}) + : vector{std::ranges::begin(list), std::ranges::end(list), allocator} + {} + + //! Destroy this vector. + constexpr ~vector() + { + clear_and_deallocate(); + } + + //! Replace the contents of this vector by the copying from the given source vector. + //! + //! @param other The source vector. + constexpr auto operator=(vector const & other) -> vector & + { + if (this == std::addressof(other)) + { + return *this; + } + + if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value) + { + if (get_allocator() != other.get_allocator()) + { + clear_and_deallocate(); + } + m_allocator = other.get_allocator(); + } + + if (capacity() >= other.size()) + { + auto const overlap = std::min(m_size, other.m_size); + std::ranges::copy(other.begin(), other.begin() + overlap, begin()); + + if (m_size < other.m_size) + { + uninitialized_copy_with_allocator(other.begin() + size(), begin() + size(), other.m_size - size()); + } + else if (m_size > other.m_size) + { + destroy_n(begin() + other.size(), size() - other.size()); + } + } + else + { + auto new_data = allocate_n(other.size()); + uninitialized_copy_with_allocator(other.begin(), new_data, other.size()); + clear_and_deallocate(); + m_data = new_data; + m_capacity = other.size(); + } + + m_size = other.size(); + return *this; + } + + //! Replace the contents fo this vector by moving from the given source. + //! + //! @param other The source vector. + constexpr auto operator=(vector && other) noexcept( + std::allocator_traits::propagate_on_container_move_assignment::value || + std::allocator_traits::is_always_equal::value) -> vector & + { + using std::swap; + + if (this == std::addressof(other)) + { + return *this; + } + + if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) + { + clear_and_deallocate(); + 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) + { + clear_and_deallocate(); + swap(m_size, other.m_size); + swap(m_capacity, other.m_capacity); + swap(m_data, other.m_data); + } + else + { + if (capacity() >= other.size()) + { + auto const overlap = std::min(size(), other.size()); + std::ranges::move(other.begin(), other.begin() + overlap, begin()); + + if (size() < other.size()) + { + uninitialized_move_with_allocator(other.begin() + size(), begin() + size(), other.size() - size()); + } + else if (size() > other.size()) + { + destroy_n(begin() + other.size(), size() - other.size()); + } + } + else + { + auto new_data = allocate_n(other.size()); + uninitialized_move_with_allocator(other.begin(), new_data, other.size()); + clear_and_deallocate(); + m_data = new_data; + m_capacity = other.m_size; + } + + other.destroy_n(other.begin(), other.size()); + m_size = std::exchange(other.m_size, size_type{}); + } + + return *this; + } + + //! 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 + { + return m_allocator; + } + + //! 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 + { + panic_if_out_of_bounds(index); + return (*this)[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]; + } + + //! 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 + { + return data()[index]; + } + + //! 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 + { + return data()[index]; + } + + //! Get a reference to the first element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] constexpr auto front() -> reference + { + return *begin(); + } + + //! Get a reference to the first element of this vector. + //! + //! The behavior is undefined if this vector is empty. + [[nodiscard]] constexpr auto front() const -> const_reference + { + return *begin(); + } + + //! 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 *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(); + } + + //! Get a pointer to the beginning of the underlying contiguous storage. + [[nodiscard]] constexpr auto data() noexcept -> pointer + { + return m_data; + } + + //! Get a pointer to the beginning of the underlying contiguous storage. + [[nodiscard]] constexpr auto data() const noexcept -> const_pointer + { + return m_data; + } + + //! 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 empty() ? end() : data(); + } + + //! 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 empty() ? end() : data(); + } + + //! 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 begin(); + } + + //! Get an iterator past the last element of this vector. + [[nodiscard]] constexpr auto end() noexcept -> pointer + { + return capacity() ? data() + size() : nullptr; + } + + //! Get an iterator past the last element of this vector. + [[nodiscard]] constexpr auto end() const noexcept -> const_pointer + { + return capacity() ? data() + size() : nullptr; + } + + //! Get an iterator past the last element of this vector. + [[nodiscard]] constexpr auto cend() const noexcept -> const_pointer + { + return end(); + } + + //! Get a reverse iterator to the reverse beginning. + [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator + { + return empty() ? rend() : reverse_iterator{end()}; + } + + //! Get a reverse iterator to the reverse beginning. + [[nodiscard]] constexpr auto rbegin() const noexcept -> const_reverse_iterator + { + return empty() ? rend() : const_reverse_iterator{end()}; + } + + //! Get a reverse iterator to the reverse beginning. + [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator + { + return rbegin(); + } + + //! Get a reverse iterator to the reverse end. + [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator + { + return reverse_iterator{begin()}; + } + + //! Get a reverse iterator to the reverse end. + [[nodiscard]] constexpr auto rend() const noexcept -> const_reverse_iterator + { + return const_reverse_iterator{begin()}; + } + + //! Get a reverse iterator to the reverse end. + [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator + { + return rend(); + } + + //! Check whether this vector is empty. + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return !size(); + } + + //! Get the number of elements present in this vector. + [[nodiscard]] constexpr auto size() const noexcept -> size_type + { + return m_size; + } + + //! Get the maximum possible number of element for this vector. + [[nodiscard]] constexpr auto max_size() const noexcept -> size_type + { + return std::allocator_traits::max_size(m_allocator); + } + + //! 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."); + } + + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + uninitialized_move_with_allocator(begin(), new_data, size()); + clear_and_deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + + //! Resize this vector to contain @p new_size elements. + constexpr auto resize(size_type new_size) -> void + { + resize(new_size, value_type{}); + } + + //! Resize this vector to contain @p new_size elements, filling new elements with @p value. + constexpr auto resize(size_type new_size, const_reference value) -> void + { + if (new_size < size()) + { + destroy_n(begin() + new_size, size() - new_size); + m_size = new_size; + return; + } + + if (new_size == size()) + { + return; + } + + if (new_size > max_size()) + { + kstd::os::panic("[kstd:vector] Tried to resize more space than theoretically possible."); + } + + if (new_size > capacity()) + { + reserve(new_size); + } + + for (auto i = size(); i < new_size; ++i) + { + std::allocator_traits::construct(m_allocator, m_data + i, value); + } + + m_size = new_size; + } + + //! Get the number of element this vector has currently space for, including elements currently in this vector. + [[nodiscard]] constexpr auto capacity() const noexcept -> size_type + { + return m_capacity; + } + + //! Try to release unused storage space. + constexpr auto shrink_to_fit() -> void + { + if (m_size == m_capacity) + { + return; + } + + auto new_data = allocate_n(m_size); + auto old_size = size(); + uninitialized_move_with_allocator(begin(), new_data, old_size); + clear_and_deallocate(); + std::exchange(m_data, new_data); + m_capacity = old_size; + m_size = old_size; + } + + //! Clear the contents of this vector. + constexpr auto clear() noexcept -> void + { + destroy_n(begin(), size()); + m_size = 0; + } + + //! Insert an element at a given position. + //! + //! @param position The position to insert the element at. + //! @param value The value to insert. + //! @return An iterator to the inserted element. + constexpr auto insert(const_iterator position, value_type const & value) -> iterator + { + auto prefix_size = std::ranges::distance(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + + if (position == end()) + { + push_back(value); + return begin() + prefix_size; + } + + if (m_capacity == m_size) + { + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + std::allocator_traits::construct(m_allocator, new_data + prefix_size, value); + uninitialized_move_with_allocator(begin(), new_data, prefix_size); + uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size); + destroy_n(begin(), old_size); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + else if (&value >= begin() && &value < end()) + { + auto value_copy = value; + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = std::move(value_copy); + } + else + { + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = value; + } + + ++m_size; + return begin() + prefix_size; + } + + //! Insert an element at a given position. + //! + //! @param position The position to insert the element at. + //! @param value The value to insert. + //! @return An iterator to the inserted element. + constexpr auto insert(const_iterator position, value_type && value) -> iterator + { + auto prefix_size = std::ranges::distance(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + + if (position == end()) + { + push_back(std::move(value)); + return begin() + prefix_size; + } + + if (m_capacity == m_size) + { + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + std::allocator_traits::construct(m_allocator, new_data + prefix_size, std::move(value)); + uninitialized_move_with_allocator(begin(), new_data, prefix_size); + uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size); + destroy_n(begin(), old_size); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + else if (&value >= begin() && &value < end()) + { + auto value_copy = std::move(value); + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = std::move(value_copy); + } + else + { + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = std::move(value); + } + + ++m_size; + return begin() + prefix_size; + } + + //! Insert the element of a given range into the vector at a given position. + //! + //! @param range The source range to insert elements from. + //! @tparam SourceRange A container compatible range type. + template + requires requires(allocator_type allocator, pointer destination, SourceRange range) { + requires kstd::bits::container_compatible_range; + requires std::move_constructible; + requires std::is_move_assignable_v; + requires std::swappable; + std::allocator_traits::construct(allocator, destination, *std::ranges::begin(range)); + } + // NOLINTNEXTLINE(misc-no-recursion) + constexpr auto insert_range(const_iterator position, SourceRange && range) -> iterator + { + auto prefix_size = std::ranges::distance(begin(), position); + + if (position == end()) + { + append_range(std::forward(range)); + return begin() + prefix_size; + } + + if constexpr (std::ranges::forward_range || std::ranges::sized_range) + { + auto number_of_elements = static_cast(std::ranges::distance(range)); + if (!number_of_elements) + { + return begin() + prefix_size; + } + + if (capacity() - size() < number_of_elements) + { + reserve(size() + std::max(size(), number_of_elements)); + } + + auto insert_position = begin() + prefix_size; + auto suffix_size = static_cast(std::ranges::distance(insert_position, end())); + + if (number_of_elements >= suffix_size) + { + uninitialized_move_with_allocator(insert_position, insert_position + number_of_elements, suffix_size); + auto result = std::ranges::copy_n(std::ranges::begin(range), suffix_size, insert_position); + uninitialized_copy_with_allocator(std::move(result.in), end(), number_of_elements - suffix_size); + } + else + { + uninitialized_move_with_allocator(end() - number_of_elements, end(), number_of_elements); + std::ranges::move_backward(insert_position, end() - number_of_elements, end()); + std::ranges::copy_n(std::ranges::begin(range), number_of_elements, insert_position); + } + + m_size += number_of_elements; + return insert_position; + } + + auto range_begin = std::ranges::begin(range); + auto range_end = std::ranges::end(range); + + auto remainder = vector{get_allocator()}; + for (; range_begin != range_end; ++range_begin) + { + remainder.emplace_back(*static_cast>(range_begin)); + } + reserve(size() + std::max(size(), remainder.size())); + + return insert_range(begin() + prefix_size, remainder); + } + + template + constexpr auto emplace(const_iterator position, Args &&... args) -> iterator + { + auto prefix_size = std::ranges::distance(begin(), position); + auto suffix_size = std::ranges::distance(position, end()); + + if (position == end()) + { + emplace_back(std::forward(args)...); + return begin() + prefix_size; + } + + if (m_capacity == m_size) + { + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + std::allocator_traits::construct(m_allocator, new_data + prefix_size, + std::forward(args)...); + uninitialized_move_with_allocator(begin(), new_data, prefix_size); + uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size); + destroy_n(begin(), old_size); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + else + { + auto insert_position = begin() + prefix_size; + std::allocator_traits::construct(m_allocator, end(), std::move(*(end() - 1))); + std::ranges::move_backward(insert_position, end() - 1, end()); + *insert_position = value_type{std::forward(args)...}; + } + + ++m_size; + return begin() + prefix_size; + } + + //! Erase an element at a given position. + //! + //! @note This function will panic if position == end() + //! + //! @param position An interator pointing to the element to delete + //! @return An iterator pointing to the element after the deleted element + constexpr auto erase(const_iterator position) -> iterator + { + if (position == end()) + { + os::panic("[kstd:vector] Attempted to erase end()!"); + } + + auto prefix_size = std::ranges::distance(cbegin(), position); + + std::ranges::move(begin() + prefix_size + 1, end(), begin() + prefix_size); + std::allocator_traits::destroy(m_allocator, end() - 1); + --m_size; + + return begin() + prefix_size; + } + + //! Erase a range of elements from this vector. + //! + //! @param first The start of the range to erase. + //! @param last The end of the range to erase. + //! @return An iterator pointing to the element after the last deleted element. + constexpr auto erase(const_iterator first, const_iterator last) -> iterator + { + if (first == last) + { + return begin() + std::ranges::distance(cbegin(), first); + } + + auto prefix_size = std::ranges::distance(cbegin(), first); + auto element_count = std::ranges::distance(first, last); + + std::ranges::move(begin() + prefix_size + element_count, end(), begin() + prefix_size); + destroy_n(end() - element_count, element_count); + m_size -= element_count; + + return begin() + prefix_size; + } + + //! Append a given element to this vector via copy construction. + constexpr auto push_back(value_type const & value) -> void + { + if (m_capacity == m_size) + { + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + std::allocator_traits::construct(m_allocator, new_data + m_size, value); + uninitialized_move_with_allocator(begin(), new_data, size()); + destroy_n(begin(), size()); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + else + { + std::allocator_traits::construct(m_allocator, data() + size(), value); + } + ++m_size; + } + + //! Append a given element to this vector via move construction. + constexpr auto push_back(value_type && value) -> void + { + if (m_capacity == m_size) + { + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + std::allocator_traits::construct(m_allocator, new_data + m_size, std::move(value)); + uninitialized_move_with_allocator(begin(), new_data, size()); + destroy_n(begin(), size()); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + else + { + std::allocator_traits::construct(m_allocator, data() + size(), std::move(value)); + } + ++m_size; + } + + //! Append a given element to this vector via direct construction. + template + constexpr auto emplace_back(Args &&... args) -> reference + { + if (m_capacity == m_size) + { + auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2; + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + std::allocator_traits::construct(m_allocator, new_data + m_size, std::forward(args)...); + uninitialized_move_with_allocator(begin(), new_data, size()); + destroy_n(begin(), size()); + deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size; + } + else + { + std::allocator_traits::construct(m_allocator, data() + size(), std::forward(args)...); + } + ++m_size; + return this->back(); + } + + //! Append the elements of a given range to this vector. + //! + //! @param range The range of elements to be appended. + //! @tparam SourceRange A container compatible range type. + template SourceRange> + requires requires(Allocator allocator, pointer destination, SourceRange range) { + std::allocator_traits::construct(allocator, destination, *std::ranges::begin(range)); + } + // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward, misc-no-recursion) + constexpr auto append_range(SourceRange && range) -> void + { + if constexpr (std::ranges::forward_range || std::ranges::sized_range) + { + auto number_of_elements = static_cast(std::ranges::distance(range)); + + if (!capacity()) + { + reserve(number_of_elements); + } + + if (capacity() - size() >= number_of_elements) + { + uninitialized_copy_with_allocator(std::ranges::begin(range), end(), number_of_elements); + m_size += number_of_elements; + return; + } + + auto new_capacity = m_capacity + std::max(size(), number_of_elements); + auto new_data = allocate_n(new_capacity); + auto old_size = size(); + + uninitialized_move_with_allocator(begin(), new_data, size()); + uninitialized_copy_with_allocator(std::ranges::begin(range), new_data + size(), number_of_elements); + clear_and_deallocate(); + m_data = new_data; + m_capacity = new_capacity; + m_size = old_size + number_of_elements; + return; + } + + auto range_begin = std::ranges::begin(range); + auto range_end = std::ranges::end(range); + + for (auto i = capacity() - size(); i > 0; --i, ++range_begin) + { + emplace_back(*range_begin); + } + + if (range_begin == range_end) + { + return; + } + + auto remainder = vector{get_allocator()}; + for (; range_begin != range_end; ++range_begin) + { + remainder.emplace_back(*static_cast>(range_begin)); + } + reserve(size() + std::max(size(), remainder.size())); + append_range(remainder); + } + + //! Remove the last element of this vector. + //! + //! If this vector is empty, the behavior is undefined. + constexpr auto pop_back() -> void + { + --m_size; + std::allocator_traits::destroy(m_allocator, data() + size()); + } + + private: + //! Use the allocator of this vector to allocate enough space for the given number of elements. + //! + //! @param count The number of elements to allocate space for. + [[nodiscard]] constexpr auto allocate_n(std::size_t count) -> std::allocator_traits::pointer + { + if (count) + { + return std::allocator_traits::allocate(m_allocator, count); + } + return nullptr; + } + + //! Clear this vector and release it's memory. + constexpr auto clear_and_deallocate() -> void + { + clear(); + deallocate(); + } + + //! Release the memory of this vector. + constexpr auto deallocate() + { + if (m_data) + { + std::allocator_traits::deallocate(m_allocator, m_data, m_capacity); + m_capacity = 0; + m_size = 0; + m_data = nullptr; + } + } + + //! Destroy a number of elements in this vector. + //! + //! @param first The start of the range of the elements to be destroyed. + //! @param count The number of elements to destroy. + constexpr auto destroy_n(iterator first, std::size_t count) -> void + { + std::ranges::for_each(first, first + count, [&](auto & element) { + std::allocator_traits::destroy(m_allocator, std::addressof(element)); + }); + } + + //! Panic the kernel if the given index is out of bounds. + //! + //! @param index The index to check. + constexpr auto panic_if_out_of_bounds(size_type index) const -> void + { + if (index >= m_size) + { + os::panic("[kstd:vector] Attempted to read element at invalid index"); + } + } + + //! Copy a number of elements from a source range into the uninitialized destination range inside this vector. + //! + //! @param from The start of the source range. + //! @param to The start of the target range inside this vector. + //! @param count The number of elements to copy + template + constexpr auto uninitialized_copy_with_allocator(SourceIterator from, iterator to, size_type count) + { + for (auto i = 0uz; i < count; ++i) + { + std::allocator_traits::construct(m_allocator, to++, *from++); + } + } + + //! Move a number of elements from a source range into the uninitialized destination range inside this vector. + //! + //! @param from The start of the source range. + //! @param to The start of the target range inside this vector. + //! @param count The number of elements to copy + template + constexpr auto uninitialized_move_with_allocator(SourceIterator from, iterator to, size_type count) + { + for (auto i = 0uz; i < count; ++i) + { + std::allocator_traits::construct(m_allocator, to++, std::move(*from++)); + } + } + + //! The allocator used by this vector. + [[no_unique_address]] allocator_type m_allocator{}; + + //! The number of elements in this vector. + size_type m_size{}; + + //! The number of elements this vector has room for. + size_type m_capacity{}; + + //! The pointer to the start of the memory managed by this vector. + value_type * m_data{}; + }; + + //! Check if the content of two vectors is equal. + template + constexpr auto operator==(vector const & lhs, vector const & rhs) -> bool + { + return std::ranges::equal(lhs, rhs); + } + + //! Perform a lexicographical comparison of the content of two vectors. + template + constexpr auto operator<=>(vector const & lhs, vector const & rhs) + -> decltype(std::declval() <=> std::declval()) + { + return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); + } + + //! Deduction guide for vector construction from an interator pair. + template::value_type>> + vector(ForwardIterator, ForwardIterator, Allocator = Allocator()) + -> vector::value_type, Allocator>; + + //! Deduction guide for vector construction from an interator pair. + template::value_type>> + vector(InputIterator, InputIterator, Allocator = Allocator()) + -> vector::value_type, Allocator>; + + //! Deduction guide for vector construction from a range. + template>> + vector(kstd::from_range_t, Range &&, Allocator = Allocator()) -> vector, Allocator>; + +} // namespace kstd + +#endif diff --git a/libs/kstd/kstd/vector.test.cpp b/libs/kstd/kstd/vector.test.cpp new file mode 100644 index 0000000..8bf8f79 --- /dev/null +++ b/libs/kstd/kstd/vector.test.cpp @@ -0,0 +1,2003 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +SCENARIO("Vector initialization and construction", "[vector]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto v = kstd::vector{}; + + THEN("the vector is empty") + { + REQUIRE(v.empty()); + } + + THEN("the size and capacity are zero") + { + REQUIRE(v.size() == 0); + REQUIRE(v.capacity() == 0); + } + } + + WHEN("constructing with a specific size") + { + auto v = kstd::vector(10); + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size is and capacity match the specified value") + { + REQUIRE(v.size() == 10); + REQUIRE(v.capacity() == 10); + } + } + + WHEN("constructing from an initializer list") + { + auto v = kstd::vector{1, 2, 3, 4, 5}; + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size is and capacity match the specified value") + { + REQUIRE(v.size() == 5); + REQUIRE(v.capacity() == 5); + } + + THEN("the elements are correctly initialized") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + REQUIRE(v[3] == 4); + REQUIRE(v[4] == 5); + } + } + } + + GIVEN("A non-empty range") + { + auto range = std::array{1, 2, 3}; + + WHEN("constructing from a random-access iterator range") + { + auto v = kstd::vector{std::begin(range), std::end(range)}; + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size and capacity match the range size") + { + REQUIRE(v.size() == std::size(range)); + REQUIRE(v.capacity() == std::size(range)); + } + + THEN("the elements are correctly initialized") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + + WHEN("constructing from a range") + { + auto v = kstd::vector{kstd::from_range, range}; + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size and capacity match the range size") + { + REQUIRE(v.size() == std::ranges::size(range)); + REQUIRE(v.capacity() == std::ranges::size(range)); + } + + THEN("the elements are correctly initialized") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + } + + GIVEN("A populated vector") + { + auto source = kstd::vector{1, 2, 3, 4, 5}; + + WHEN("copy constructing a new vector") + { + auto copy = kstd::vector(source); + + THEN("the copy matches the original") + { + REQUIRE(copy.size() == source.size()); + REQUIRE(copy.capacity() == source.capacity()); + REQUIRE(copy[0] == 1); + REQUIRE(copy[1] == 2); + REQUIRE(copy[2] == 3); + REQUIRE(copy[3] == 4); + REQUIRE(copy[4] == 5); + } + + THEN("the original is left unchanged") + { + REQUIRE(source.size() == 5); + REQUIRE(source.capacity() == 5); + REQUIRE(source[0] == 1); + REQUIRE(source[1] == 2); + REQUIRE(source[2] == 3); + REQUIRE(source[3] == 4); + REQUIRE(source[4] == 5); + } + } + + WHEN("move constructing a new vector") + { + auto moved = kstd::vector(std::move(source)); + + THEN("The new vector has the original elements") + { + REQUIRE(moved.size() == 5); + REQUIRE(moved.capacity() == 5); + REQUIRE(moved[0] == 1); + REQUIRE(moved[1] == 2); + REQUIRE(moved[2] == 3); + REQUIRE(moved[3] == 4); + REQUIRE(moved[4] == 5); + } + + THEN("The original vector is left in a valid but unspecified state") + { + REQUIRE(source.empty()); + REQUIRE(source.size() == 0); + REQUIRE(source.capacity() == 0); + } + } + } +} + +SCENARIO("Vector element access", "[vector]") +{ + GIVEN("A populated vector") + { + auto v = kstd::vector{10, 20, 30}; + + WHEN("accessing elements for reading") + { + THEN("operator[] and at() return the correct elements") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + + REQUIRE(v.at(0) == 10); + REQUIRE(v.at(1) == 20); + REQUIRE(v.at(2) == 30); + } + + THEN("front() and back() return the first and last elements") + { + REQUIRE(v.front() == 10); + REQUIRE(v.back() == 30); + } + + THEN("data() return a pointer to the contiguous storage") + { + auto ptr = v.data(); + REQUIRE(ptr); + REQUIRE(ptr[0] == 10); + REQUIRE(ptr[1] == 20); + REQUIRE(ptr[2] == 30); + } + + THEN("accessing out of bounds elements panics") + { + REQUIRE_THROWS_AS(v.at(3), kstd::tests::os_panic); + } + } + + WHEN("accessing elements for writing") + { + v[0] = 100; + v.at(1) = 200; + v.back() = 300; + + THEN("the elements are correctly modified") + { + REQUIRE(v[0] == 100); + REQUIRE(v[1] == 200); + REQUIRE(v[2] == 300); + } + } + } +} + +SCENARIO("Vector iterators", "[vector]") +{ + GIVEN("A populated vector") + { + auto v = kstd::vector{1, 2, 3}; + + WHEN("using forward iterators") + { + THEN("they navigate the elements in the correct forward order") + { + auto it = v.begin(); + REQUIRE(it != v.end()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it != v.end()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.end()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it == v.end()); + } + + THEN("const forward iterators provide correct access") + { + auto it = v.cbegin(); + REQUIRE(it != v.cend()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it != v.cend()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.cend()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it == v.cend()); + } + } + + WHEN("using reverse iterators") + { + THEN("they navigate the elements in the correct reverse order") + { + auto it = v.rbegin(); + REQUIRE(it != v.rend()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it != v.rend()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.rend()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it == v.rend()); + } + + THEN("const reverse iterators provide correct access") + { + auto it = v.crbegin(); + REQUIRE(it != v.crend()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it != v.crend()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.crend()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it == v.crend()); + } + } + } + + GIVEN("an empty vector") + { + auto v = kstd::vector{}; + + WHEN("getting iterators") + { + THEN("begin() equals end() and cbegin() equals cend()") + { + REQUIRE(v.begin() == v.end()); + REQUIRE(v.cbegin() == v.cend()); + } + + THEN("rbegin() equals rend() and crbegin() equals crend()") + { + REQUIRE(v.rbegin() == v.rend()); + REQUIRE(v.crbegin() == v.crend()); + } + } + } +} + +SCENARIO("Vector capacity management", "[vector]") +{ + GIVEN("An empty vector") + { + auto v = kstd::vector{}; + + WHEN("reserving space") + { + v.reserve(10); + + THEN("the capacity is at least the reserved amount") + { + REQUIRE(v.capacity() >= 10); + } + + THEN("the size is still zero") + { + REQUIRE(v.size() == 0); + } + + THEN("the vector is still empty") + { + REQUIRE(v.empty()); + } + } + + WHEN("reserving space less than or equal to current capacity") + { + v.reserve(10); + auto const current_capacity = v.capacity(); + v.reserve(5); + + THEN("the capacity remains unchanged") + { + REQUIRE(v.capacity() == current_capacity); + } + } + + WHEN("reserving space greater than max_size") + { + THEN("a panic is triggered") + { + REQUIRE_THROWS_AS(v.reserve(v.max_size() + 1), kstd::tests::os_panic); + } + } + } + + GIVEN("A populated vector with excess capacity") + { + auto v = kstd::vector{1, 2, 3}; + v.reserve(10); + + REQUIRE(v.capacity() == 10); + + WHEN("calling shrink_to_fit") + { + v.shrink_to_fit(); + + THEN("the capacity is reduced to match the size") + { + REQUIRE(v.capacity() == 3); + REQUIRE(v.size() == 3); + } + + THEN("the elements remain unchanged") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + } +} + +SCENARIO("Vector modifiers", "[vector]") +{ + GIVEN("An empty vector") + { + auto v = kstd::vector{}; + + WHEN("push_back is called with a value") + { + v.push_back(10); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v.back() == 10); + } + } + + WHEN("emplace_back is called with constructor arguments") + { + v.emplace_back(20); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v.back() == 20); + } + } + + WHEN("elements are added while capacity is sufficient") + { + v.reserve(10); + auto const capacity = v.capacity(); + + v.push_back(10); + v.emplace_back(20); + + THEN("the elements are added without reallocation") + { + REQUIRE(v.size() == 2); + REQUIRE(v.capacity() == capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + } + } + + WHEN("emplace is called with the end iterator and constructor arguments") + { + v.emplace(v.end(), 20); + + THEN("the element is appended and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v.back() == 20); + } + } + + WHEN("emplace is called while capacity is sufficient") + { + v.reserve(10); + auto const capacity = v.capacity(); + + v.emplace(v.end(), 20); + + THEN("the element is appended without reallocation") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() == capacity); + REQUIRE(v.back() == 20); + } + } + + WHEN("inserting an element") + { + auto it = v.insert(v.cbegin(), 40); + + THEN("the size and capacity increase and the element is inserted") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v[0] == 40); + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting an lvalue element") + { + auto const value = 40; + auto it = v.insert(v.cbegin(), value); + + THEN("the size and capacity increase and the element is inserted") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v[0] == 40); + REQUIRE(it == v.begin()); + } + } + + WHEN("appending a range") + { + auto const range = std::views::iota(0, 3); + v.append_range(range); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 2); + } + } + + WHEN("appending from an input range") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + + v.append_range(std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + + WHEN("appending from an input range with sufficient capacity") + { + v.reserve(3); + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + + v.append_range(std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity stays the same") + { + REQUIRE(v.capacity() == 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + + WHEN("resizing the vector to a greater size") + { + v.resize(3); + + THEN("the size and capacity increase and the elements are value initialized") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() >= 3); + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 0); + REQUIRE(v[2] == 0); + } + } + + WHEN("resizing the vector to a greater size with initial value") + { + v.resize(3, 2); + + THEN("the size and capacity increase and the elements are initialized to the given value") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() >= 3); + REQUIRE(v[0] == 2); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 2); + } + } + + WHEN("inserting a range at the beginning") + { + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.begin(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting a range at the end") + { + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.end(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting from an input range without sufficient capacity") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + } + + GIVEN("A populated vector") + { + auto v = kstd::vector{10, 20, 30}; + auto initial_capacity = v.capacity(); + + WHEN("push_back is called") + { + v.push_back(40); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + } + } + + WHEN("emplace_back is called with constructor arguments") + { + v.emplace_back(40); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + } + } + + WHEN("emplace is called with an iterator and constructor arguments") + { + auto it = v.emplace(v.begin() + 2, 25); + + THEN("the element is inserted and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v.at(2) == 25); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.cbegin() + 2); + REQUIRE(*it == 25); + } + } + + WHEN("emplace is called with an iterator and sufficient capacity") + { + v.reserve(v.size() + 1); + + auto it = v.emplace(v.begin() + 2, 25); + + THEN("the element is inserted and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v.at(2) == 25); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.cbegin() + 2); + REQUIRE(*it == 25); + } + } + + WHEN("push_back is called with a reference to an internal element") + { + v.shrink_to_fit(); + auto const original_value = v[0]; + + v.push_back(v[0]); + + THEN("reallocation handles the internal reference safely without dangling") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == original_value); + REQUIRE(v.back() == original_value); + } + } + + WHEN("pop_back is called") + { + v.pop_back(); + + THEN("the last element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v.capacity() == initial_capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + } + } + + WHEN("clear is called") + { + v.clear(); + + THEN("the vector is empty") + { + REQUIRE(v.empty()); + REQUIRE(v.size() == 0); + REQUIRE(v.capacity() == initial_capacity); + } + } + + WHEN("inserting at the beginning") + { + auto it = v.insert(v.cbegin(), 5); + + THEN("the element is inserted at the front") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 5); + REQUIRE(v[1] == 10); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting in the middle") + { + auto it = v.insert(v.cbegin() + 1, 15); + + THEN("the element is inserted in the middle") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 15); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting at the end") + { + auto it = v.insert(v.cend(), 40); + + THEN("the element is inserted at the back") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + REQUIRE(it == v.begin() + 3); + } + } + + WHEN("inserting an lvalue at the end") + { + auto const value = 40; + auto it = v.insert(v.cend(), value); + + THEN("the element is inserted at the back") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + REQUIRE(it == v.begin() + 3); + } + } + + WHEN("inserting when capacity is sufficient") + { + v.reserve(10); + auto const capacity = v.capacity(); + + auto it = v.insert(v.cbegin() + 1, 15); + + THEN("the element is added without reallocation") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() == capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 15); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting a reference to an existing element with reallocation") + { + v.shrink_to_fit(); + REQUIRE(v.capacity() == v.size()); + auto it = v.insert(v.cbegin() + 1, v[2]); + + THEN("the element is correctly copied and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting a reference to an existing element without reallocation") + { + v.reserve(10); + REQUIRE(v.capacity() > v.size()); + auto it = v.insert(v.cbegin() + 1, v[2]); + + THEN("the element is correctly copied and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting an rvalue reference to an existing element with reallocation") + { + v.shrink_to_fit(); + REQUIRE(v.capacity() == v.size()); + auto it = v.insert(v.cbegin() + 1, std::move(v[2])); + + THEN("the element is correctly moved and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting an rvalue reference to an existing element without reallocation") + { + v.reserve(10); + REQUIRE(v.capacity() > v.size()); + auto it = v.insert(v.cbegin() + 1, std::move(v[2])); + + THEN("the element is correctly moved and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing the first element") + { + auto it = v.erase(v.cbegin()); + + THEN("the first element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 20); + REQUIRE(v[1] == 30); + REQUIRE(it == v.begin()); + } + } + + WHEN("erasing a middle element") + { + auto it = v.erase(v.cbegin() + 1); + + THEN("the middle element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing the last element") + { + auto it = v.erase(v.cend() - 1); + + THEN("the last element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(it == v.end()); + } + } + + WHEN("erasing the end() iterator") + { + THEN("a panic is triggered") + { + REQUIRE_THROWS_AS(v.erase(v.end()), kstd::tests::os_panic); + } + } + + WHEN("erasing a range of elements") + { + auto it = v.erase(v.cbegin() + 1, v.cend() - 1); + + THEN("the specified range is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing an empty range") + { + auto it = v.erase(v.cbegin() + 1, v.cbegin() + 1); + + THEN("the vector is unchanged") + { + REQUIRE(v.size() == 3); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("appending a range") + { + auto initial_size = v.size(); + v.append_range(std::views::iota(0, 3)); + + THEN("capacity is increased") + { + REQUIRE(v.capacity() >= initial_capacity); + } + + THEN("size is increased") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[initial_size + 0] == 0); + REQUIRE(v[initial_size + 1] == 1); + REQUIRE(v[initial_size + 2] == 2); + } + } + + WHEN("resizing the vector to a greater size") + { + auto initial_size = v.size(); + v.resize(initial_size + 3); + + THEN("the size and capacity increase and the elements are value initialized") + { + REQUIRE(v.size() == initial_size + 3); + REQUIRE(v.capacity() >= initial_size + 3); + REQUIRE(v[initial_size + 0] == 0); + REQUIRE(v[initial_size + 1] == 0); + REQUIRE(v[initial_size + 2] == 0); + } + } + + WHEN("resizing the vector to a greater size with initial value") + { + auto initial_size = v.size(); + v.resize(initial_size + 3, 2); + + THEN("the size and capacity increase and the elements are initialized to the given value") + { + REQUIRE(v.size() == initial_size + 3); + REQUIRE(v.capacity() >= initial_size + 3); + REQUIRE(v[initial_size + 0] == 2); + REQUIRE(v[initial_size + 1] == 2); + REQUIRE(v[initial_size + 2] == 2); + } + } + + WHEN("resizing the vector to a smaller size") + { + v.resize(1); + + THEN("the size decreases and the elements are destroyed") + { + REQUIRE(v.size() == 1); + REQUIRE(v[0] == 10); + } + } + + WHEN("inserting an empty range") + { + auto initial_size = v.size(); + auto it = v.insert_range(v.begin(), std::views::empty); + + THEN("the size does not change") + { + REQUIRE(v.size() == initial_size); + } + + THEN("the capacity does not change") + { + REQUIRE(v.capacity() == initial_capacity); + } + + THEN("the content is unchanged") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + } + + THEN("the returned iterator points to the position of insertion") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting a range at the beginning") + { + auto initial_size = v.size(); + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.begin(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= initial_size + 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + REQUIRE(v[3] == 10); + REQUIRE(v[4] == 20); + REQUIRE(v[5] == 30); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting a range at the end") + { + auto initial_size = v.size(); + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.end(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= initial_size + 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 1); + REQUIRE(v[4] == 2); + REQUIRE(v[5] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin() + initial_size); + } + } + + WHEN("inserting a range that causes reallocation") + { + auto initial_size = v.size(); + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.begin() + 1, arr); + + THEN("the size increases") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= initial_size + 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 2); + REQUIRE(v[3] == 3); + REQUIRE(v[4] == 20); + REQUIRE(v[5] == 30); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting fewer elements than the suffix without reallocation") + { + v.reserve(10); + auto const capacity = v.capacity(); + auto const arr = std::array{1}; + auto it = v.insert_range(v.begin() + 1, arr); + + THEN("the elements are correctly placed") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + } + + THEN("no reallocation occurs") + { + REQUIRE(v.capacity() == capacity); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting fewer elements than the suffix without sufficient capacity") + { + v.shrink_to_fit(); + auto const arr = std::array{1}; + auto it = v.insert_range(v.begin() + 1, arr); + + THEN("the elements are correctly placed") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting from an input range without sufficient capacity") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 6); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 6); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + REQUIRE(v[3] == 10); + REQUIRE(v[4] == 20); + REQUIRE(v[5] == 30); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + } +} + +SCENARIO("Vector comparison", "[vector]") +{ + GIVEN("Two identical vectors") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 3}; + + WHEN("comparing for equality") + { + THEN("the vectors are equal") + { + REQUIRE(v1 == v2); + } + + THEN("the vectors and not not-equal") + { + REQUIRE_FALSE(v1 != v2); + } + } + + WHEN("comparing using the spaceship operator") + { + THEN("the vectors are equivalent") + { + REQUIRE((v1 <=> v2) == 0); + REQUIRE(v1 <= v2); + REQUIRE(v1 >= v2); + } + } + } + + GIVEN("Two vectors of different sizes") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 3, 4}; + + WHEN("comparing for equality") + { + THEN("the vectors are not equal") + { + REQUIRE_FALSE(v1 == v2); + } + + THEN("the vectors are not-equal") + { + REQUIRE(v1 != v2); + } + } + + WHEN("comparing for ordering") + { + THEN("the shorter vector evaluates as less than the longer vector") + { + REQUIRE(v1 < v2); + REQUIRE(v2 > v1); + } + } + } + + GIVEN("Two vectors of the same size but different elements") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 4}; + + WHEN("comparing for ordering") + { + THEN("they are ordered lexicographically") + { + REQUIRE(v1 < v2); + REQUIRE(v2 > v1); + } + } + } +} + +SCENARIO("Vector with non-default-constructible types", "[vector]") +{ + GIVEN("A type without a default constructor") + { + WHEN("constructing an empty vector") + { + auto v = kstd::vector{}; + + THEN("the vector is empty") + { + REQUIRE(v.empty()); + } + } + + WHEN("using emplace_back") + { + auto v = kstd::vector{}; + v.emplace_back(40); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back() == kstd::tests::non_default_constructible{40}); + } + } + } +} + +SCENARIO("Vector with custom allocator", "[vector]") +{ + GIVEN("a tracking allocator acting as the vector's memory manager") + { + auto allocations = 0; + auto allocator = kstd::tests::tracking_allocator{&allocations}; + + WHEN("a vector uses this allocator to allocate memory") + { + auto v = kstd::vector>(allocator); + REQUIRE(allocations == 0); + + v.reserve(10); + + THEN("the allocator was used to allocate memory") + { + REQUIRE(allocations > 0); + } + } + } +} + +SCENARIO("Vector modifier move semantics", "[vector]") +{ + GIVEN("An empty vector and a move tracker element") + { + auto v = kstd::vector{}; + auto tracker = kstd::tests::special_member_tracker{40}; + + WHEN("push_back is called with the move tracker") + { + v.push_back(std::move(tracker)); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back().move_constructed_count == 1); + REQUIRE(v.back().copy_constructed_count == 0); + REQUIRE(v.back().value == 40); + } + + THEN("the original tracker is left in a valid but unspecified state") + { + REQUIRE(tracker.value == -1); + } + } + } + + GIVEN("An empty vector") + { + auto v = kstd::vector{}; + + WHEN("emplace_back is called with constructor arguments") + { + v.emplace_back(40); + + THEN("the element is constructed directly, without moves or copies") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back().move_constructed_count == 0); + REQUIRE(v.back().copy_constructed_count == 0); + REQUIRE(v.back().value == 40); + } + } + } + + GIVEN("A populated vector of move trackers") + { + auto v = kstd::vector{}; + v.reserve(10); + v.emplace_back(10); + v.emplace_back(20); + v.emplace_back(30); + + WHEN("inserting an element in the middle with sufficient capacity") + { + auto const tracker = kstd::tests::special_member_tracker{15}; + v.insert(v.cbegin() + 1, tracker); + + THEN("the shifted elements are move-assigned and the new element is copy-assigned") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + REQUIRE(v[1].value == 15); + REQUIRE(v[1].move_assigned_count == 0); + REQUIRE(v[1].copy_assigned_count == 1); + REQUIRE(tracker.copied_from_count == 1); + REQUIRE(v[2].value == 20); + REQUIRE(v[2].move_assigned_count == 1); + REQUIRE(v[2].copy_assigned_count == 0); + REQUIRE(v[3].value == 30); + REQUIRE(v[3].move_constructed_count == 1); + } + } + + WHEN("inserting an rvalue element in the middle with sufficient capacity") + { + auto tracker = kstd::tests::special_member_tracker{15}; + v.insert(v.cbegin() + 1, std::move(tracker)); + + THEN("the shifted elements are move-assigned and the new element is move-assigned") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + REQUIRE(v[1].value == 15); + REQUIRE(v[1].move_assigned_count == 1); + REQUIRE(v[1].copy_assigned_count == 0); + REQUIRE(tracker.moved_from_count == 1); + REQUIRE(v[2].value == 20); + REQUIRE(v[2].move_assigned_count == 1); + REQUIRE(v[3].value == 30); + REQUIRE(v[3].move_constructed_count == 1); + } + } + + WHEN("erasing an element in the middle") + { + for (auto & elem : v) + { + elem.reset_counts(); + } + + auto it = v.erase(v.cbegin() + 1); + + THEN("the subsequent elements are move-assigned leftwards") + { + REQUIRE(v.size() == 2); + + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + + REQUIRE(v[1].value == 30); + REQUIRE(v[1].move_assigned_count == 1); + REQUIRE(v[1].copy_assigned_count == 0); + + REQUIRE(v.data()[2].destroyed_count == 1); + REQUIRE(v.data()[2].moved_from_count == 1); + + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing the last element") + { + for (auto & elem : v) + { + elem.reset_counts(); + } + + auto it = v.erase(v.cend() - 1); + + THEN("no elements are moved, just the last element destroyed") + { + REQUIRE(v.size() == 2); + + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_constructed_count == 0); + REQUIRE(v[0].copy_constructed_count == 0); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + + REQUIRE(v[1].value == 20); + REQUIRE(v[1].move_constructed_count == 0); + REQUIRE(v[1].copy_constructed_count == 0); + REQUIRE(v[1].move_assigned_count == 0); + REQUIRE(v[1].copy_assigned_count == 0); + + REQUIRE(v.data()[2].destroyed_count == 1); + REQUIRE(v.data()[2].moved_from_count == 0); + REQUIRE(v.data()[2].copied_from_count == 0); + + REQUIRE(it == v.end()); + } + } + + WHEN("erasing a range of elements in the middle") + { + v.emplace_back(40); + v.emplace_back(50); + + for (auto & elem : v) + { + elem.reset_counts(); + } + + auto it = v.erase(v.cbegin() + 1, v.cbegin() + 3); + + THEN("the specified elements are destroyed and subsequent elements are move-assigned leftwards") + { + REQUIRE(v.size() == 3); + + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_constructed_count == 0); + REQUIRE(v[0].copy_constructed_count == 0); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + + REQUIRE(v[1].value == 40); + REQUIRE(v[1].move_constructed_count == 0); + REQUIRE(v[1].copy_constructed_count == 0); + REQUIRE(v[1].move_assigned_count == 1); + REQUIRE(v[1].copy_assigned_count == 0); + + REQUIRE(v[2].value == 50); + REQUIRE(v[2].move_constructed_count == 0); + REQUIRE(v[2].copy_constructed_count == 0); + REQUIRE(v[2].move_assigned_count == 1); + REQUIRE(v[2].copy_assigned_count == 0); + + REQUIRE(v.data()[3].destroyed_count == 1); + REQUIRE(v.data()[3].moved_from_count == 1); + REQUIRE(v.data()[3].copied_from_count == 0); + + REQUIRE(v.data()[4].destroyed_count == 1); + REQUIRE(v.data()[4].moved_from_count == 1); + REQUIRE(v.data()[4].copied_from_count == 0); + + REQUIRE(it == v.begin() + 1); + } + } + } +} + +SCENARIO("Vector advanced construction", "[vector]") +{ + GIVEN("A count and a default value") + { + WHEN("constructing with count and default argument") + { + auto const count = 5uz; + auto const value = 42; + auto v = kstd::vector(count, value); + + THEN("the vector is initialized with count copies of value") + { + REQUIRE(v.size() == 5); + REQUIRE(v.capacity() == 5); + REQUIRE(v.front() == 42); + REQUIRE(v.back() == 42); + } + } + } + + GIVEN("A pure input iterator range") + { + WHEN("constructing from input iterators") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + + auto v = kstd::vector(first, last); + + THEN("the vector is generated dynamically and initialized correctly") + { + REQUIRE(v.size() == 3); + REQUIRE(v[0] == 1); + REQUIRE(v[2] == 3); + } + } + } + + GIVEN("A tracking allocator and a source vector") + { + auto allocs = 0; + auto allocator = kstd::tests::tracking_allocator{&allocs}; + auto source = kstd::vector>{allocator}; + source.push_back(1); + source.push_back(2); + source.push_back(3); + + allocs = 0; + + WHEN("copy constructing with an allocator") + { + auto copy = kstd::vector>(source, allocator); + + THEN("the copy succeeds and the allocator is used") + { + REQUIRE(copy.size() == 3); + REQUIRE(allocs > 0); + REQUIRE(copy[0] == 1); + REQUIRE(copy[2] == 3); + } + } + + WHEN("move constructing with an identically comparing allocator") + { + auto moved = kstd::vector>(std::move(source), allocator); + + THEN("the move succeeds and no new allocations are made (memory is stolen)") + { + REQUIRE(moved.size() == 3); + REQUIRE(allocs == 0); + REQUIRE(moved[0] == 1); + REQUIRE(moved[2] == 3); + } + } + + WHEN("move constructing with a non-equal allocator") + { + auto allocs2 = 0; + auto allocator2 = kstd::tests::tracking_allocator{&allocs2}; + + auto moved = kstd::vector>(std::move(source), allocator2); + + THEN("the move allocates new memory and moves elements") + { + REQUIRE(allocs2 > 0); + REQUIRE(moved.size() == 3); + REQUIRE(source.empty()); + } + } + } +} + +SCENARIO("Vector assignment operators", "[vector]") +{ + GIVEN("A source vector and an empty target vector") + { + auto source = kstd::vector{1, 2, 3}; + auto target = kstd::vector{}; + + WHEN("copy assigning") + { + target = source; + + THEN("the target matches the source") + { + REQUIRE(target.size() == 3); + REQUIRE(target[0] == 1); + REQUIRE(target[2] == 3); + REQUIRE(source.size() == 3); + } + } + + WHEN("move assigning") + { + target = std::move(source); + + THEN("the target assumes the source's data") + { + REQUIRE(target.size() == 3); + REQUIRE(target[0] == 1); + REQUIRE(target[2] == 3); + REQUIRE(source.empty()); + } + } + } + + GIVEN("Vectors with propagating copy allocator") + { + auto allocs1 = 0; + auto allocs2 = 0; + auto alloc1 = kstd::tests::propagating_allocator{&allocs1}; + auto alloc2 = kstd::tests::propagating_allocator{&allocs2}; + + auto v1 = kstd::vector>{alloc1}; + v1.push_back(1); + v1.push_back(2); + auto v2 = kstd::vector>{alloc2}; + + WHEN("copy assigning") + { + v2 = v1; + THEN("the allocator propagates") + { + REQUIRE(v2.get_allocator() == v1.get_allocator()); + } + } + } + + GIVEN("Vectors for copy assignment overlap") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{4, 5}; + v2.reserve(10); + + WHEN("copy assigning a larger vector to a smaller one with enough capacity") + { + v2 = v1; + THEN("elements are copied and size is updated") + { + REQUIRE(v2.size() == 3); + REQUIRE(v2.capacity() >= 10); + REQUIRE(v2[2] == 3); + } + } + + auto v3 = kstd::vector{1, 2, 3, 4}; + v3.reserve(10); + WHEN("copy assigning a smaller vector to a larger one") + { + v3 = v1; + THEN("excess elements are destroyed") + { + REQUIRE(v3.size() == 3); + REQUIRE(v3[0] == 1); + } + } + } + + GIVEN("Vectors with the same tracking allocator") + { + auto allocs = 0; + auto alloc = kstd::tests::tracking_allocator{&allocs}; + auto v1 = kstd::vector>{alloc}; + v1.push_back(1); + auto v2 = kstd::vector>{alloc}; + + WHEN("move assigning") + { + v2 = std::move(v1); + THEN("memory is stolen without allocation") + { + REQUIRE(v2.size() == 1); + REQUIRE(allocs == 1); + } + } + } + + GIVEN("Vectors with different non-propagating tracking allocators") + { + auto allocs1 = 0; + auto allocs2 = 0; + auto alloc1 = kstd::tests::tracking_allocator{&allocs1}; + auto alloc2 = kstd::tests::tracking_allocator{&allocs2}; + + auto v1 = kstd::vector>{alloc1}; + v1.push_back(1); + v1.push_back(2); + v1.push_back(3); + + auto v2 = kstd::vector>{alloc2}; + v2.push_back(4); + v2.push_back(5); + + WHEN("move assigning a larger vector to a smaller one without enough capacity") + { + v2.shrink_to_fit(); + v2 = std::move(v1); + THEN("memory is reallocated and elements are moved") + { + REQUIRE(v2.size() == 3); + REQUIRE(allocs2 > 2); + } + } + + auto v3 = kstd::vector>{alloc2}; + v3.reserve(10); + v3.push_back(4); + v3.push_back(5); + WHEN("move assigning a larger vector to a smaller one with enough capacity") + { + v3 = std::move(v1); + THEN("elements are move-assigned over overlap and move-constructed over remainder") + { + REQUIRE(v3.size() == 3); + } + } + + auto v4 = kstd::vector>{alloc2}; + v4.reserve(10); + v4.push_back(4); + v4.push_back(5); + v4.push_back(6); + v4.push_back(7); + WHEN("move assigning a smaller vector to a larger one with enough capacity") + { + v4 = std::move(v1); + THEN("overlap is moved and excess is destroyed") + { + REQUIRE(v4.size() == 3); + } + } + } +} + +SCENARIO("Vector self-assignment operators", "[vector]") +{ + GIVEN("A populated vector") + { + auto v = kstd::vector{1, 2, 3}; + auto const initial_capacity = v.capacity(); + auto const * initial_data = v.data(); + + WHEN("copy assigning to itself") + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wself-assign-overloaded" + v = v; +#pragma GCC diagnostic pop + + THEN("the vector remains unchanged") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() == initial_capacity); + REQUIRE(v.data() == initial_data); + REQUIRE(v[0] == 1); + REQUIRE(v[2] == 3); + } + } + + WHEN("move assigning to itself") + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wself-move" + v = std::move(v); +#pragma GCC diagnostic pop + + THEN("the vector remains unchanged") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() == initial_capacity); + REQUIRE(v.data() == initial_data); + REQUIRE(v[0] == 1); + REQUIRE(v[2] == 3); + } + } + } +} + +SCENARIO("Vector const accessors and copy insertion", "[vector]") +{ + GIVEN("A const populated vector") + { + auto const v = kstd::vector{10, 20, 30}; + + WHEN("calling const accessors") + { + THEN("elements are read correctly as const references") + { + REQUIRE(v.front() == 10); + REQUIRE(v.back() == 30); + REQUIRE(v[1] == 20); + REQUIRE(v.at(1) == 20); + } + } + } + + GIVEN("An empty vector and a const lvalue tracker") + { + auto v = kstd::vector{}; + auto const tracker = kstd::tests::special_member_tracker{42}; + + WHEN("push_back is called with the const lvalue") + { + v.push_back(tracker); + + THEN("the element is gracefully copy-constructed") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back().value == 42); + REQUIRE(v.back().copy_constructed_count == 1); + REQUIRE(v.back().move_constructed_count == 0); + } + } + + WHEN("push_back is called with a const lvalue when capacity is sufficient") + { + v.reserve(10); + auto const current_capacity = v.capacity(); + v.push_back(tracker); + + THEN("the element is copy-constructed without reallocation") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() == current_capacity); + REQUIRE(v.back().value == 42); + REQUIRE(v.back().copy_constructed_count == 1); + REQUIRE(v.back().move_constructed_count == 0); + } + } + } +} diff --git a/libs/kstd/kstd/vformat.cpp b/libs/kstd/kstd/vformat.cpp new file mode 100644 index 0000000..b7c5121 --- /dev/null +++ b/libs/kstd/kstd/vformat.cpp @@ -0,0 +1,209 @@ +#include +#include + +#include +#include +#include +#include + +namespace kstd::bits::format +{ + + auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void + { + auto context = kstd::format_context{buffer, args}; + auto parse_context = kstd::format_parse_context{format, args.size()}; + + auto it = parse_context.begin(); + auto end = parse_context.end(); + + while (it != end) + { + if (*it != '{' && *it != '}') + { + auto start = it; + while (it != end && *it != '{' && *it != '}') + { + std::advance(it, 1); + } + parse_context.advance_to(it); + context.push(std::string_view(start, it - start)); + continue; + } + + if (*it == '{') + { + std::advance(it, 1); + if (it != end && *it == '{') + { + context.push('{'); + std::advance(it, 1); + parse_context.advance_to(it); + continue; + } + + parse_context.advance_to(it); + auto index = 0uz; + + if (it != end && *it >= '0' && *it <= '9') + { + while (it != end && *it >= '0' && *it <= '9') + { + index = index * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + parse_context.check_arg_id(index); + } + else + { + index = parse_context.next_arg_id(); + } + + if (it != end && *it == ':') + { + std::advance(it, 1); + } + + parse_context.advance_to(it); + + if (index < args.size()) + { + auto const & arg = args[index]; + switch (arg.type) + { + case kstd::bits::format::arg_type::boolean: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.boolean, context); + break; + } + case kstd::bits::format::arg_type::character: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.character, context); + break; + } + case kstd::bits::format::arg_type::integer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.integer, context); + break; + } + case kstd::bits::format::arg_type::unsigned_integer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.unsigned_integer, context); + break; + } + case kstd::bits::format::arg_type::string_view: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.string_view, context); + break; + } + case kstd::bits::format::arg_type::c_string: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.c_string, context); + break; + } + case kstd::bits::format::arg_type::pointer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.pointer, context); + break; + } + case kstd::bits::format::arg_type::user_defined: + { + if (arg.value.user_defined.format) + { + arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context); + } + else + { + context.push("{?}"); + } + break; + } + default: + { + context.push("{fmt-err: unknown-type}"); + break; + } + } + } + else + { + context.push("{fmt-err: bound}"); + } + + it = parse_context.begin(); + + if (it != end && *it == '}') + { + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unconsumed}"); + while (it != end && *it != '}') + { + std::advance(it, 1); + } + + if (it != end) + { + std::advance(it, 1); + parse_context.advance_to(it); + } + } + } + else if (*it == '}') + { + std::advance(it, 1); + if (it != end && *it == '}') + { + context.push('}'); + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unescaped}"); + parse_context.advance_to(it); + } + } + } + } + + auto string_writer::push(std::string_view text) -> void + { + m_result.append(text); + } + + auto string_writer::push(char character) -> void + { + m_result.push_back(character); + } + + auto string_writer::release() -> string && + { + return std::move(m_result); + } + +} // namespace kstd::bits::format \ No newline at end of file diff --git a/libs/kstd/src/libc/stdlib.cpp b/libs/kstd/src/libc/stdlib.cpp deleted file mode 100644 index a18fed0..0000000 --- a/libs/kstd/src/libc/stdlib.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -namespace kstd::libc -{ - - extern "C" - { - [[noreturn]] auto abort() -> void - { - kstd::os::abort(); - } - - [[noreturn, gnu::weak]] auto free(void *) -> void - { - kstd::os::panic("Tried to call free."); - } - } - -} // namespace kstd::libc \ No newline at end of file diff --git a/libs/kstd/src/libc/string.cpp b/libs/kstd/src/libc/string.cpp deleted file mode 100644 index c9fada1..0000000 --- a/libs/kstd/src/libc/string.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -namespace kstd::libc -{ - - auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void * - { - auto dest_span = std::span{static_cast(dest), size}; - auto src_span = std::span{static_cast(src), size}; - - for (std::size_t i = 0; i < size; ++i) - { - dest_span[i] = src_span[i]; - } - - return dest; - } - - auto memset(void * dest, int value, std::size_t size) noexcept -> void * - { - auto const byte_value = static_cast(static_cast(value)); - auto dest_span = std::span{static_cast(dest), size}; - - for (std::size_t i = 0; i < size; ++i) - { - dest_span[i] = byte_value; - } - - return dest; - } - - auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int - { - auto left_span = std::span{static_cast(lhs), size}; - auto right_span = std::span{static_cast(rhs), size}; - auto mismatched = std::ranges::mismatch(left_span, right_span); - - if (mismatched.in1 == left_span.end()) - { - return 0; - } - - return std::bit_cast(*mismatched.in1) - std::bit_cast(*mismatched.in2); - } - - auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void * - { - auto dest_span = std::span{static_cast(dest), size}; - auto src_span = std::span{static_cast(src), size}; - if (dest < src) - { - for (std::size_t i = 0; i < size; ++i) - { - dest_span[i] = src_span[i]; - } - } - else - { - for (std::size_t i = size; i > 0; --i) - { - dest_span[i - 1] = src_span[i - 1]; - } - } - return dest; - } - - auto strlen(char const * string) noexcept -> std::size_t - { - return std::distance(string, std::ranges::find(string, nullptr, '\0')); - } - -} // namespace kstd::libc \ No newline at end of file diff --git a/libs/kstd/src/mutex.cpp b/libs/kstd/src/mutex.cpp deleted file mode 100644 index 7387657..0000000 --- a/libs/kstd/src/mutex.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include - -#include - -#include - -namespace kstd -{ - - mutex::mutex() = default; - - mutex::~mutex() - { - if (m_locked.test(std::memory_order_relaxed)) - { - os::panic("[KSTD] Tried to destroy a locked mutex."); - } - } - - auto mutex::lock() -> void - { - while (!try_lock()) - { - asm volatile("nop"); - } - } - - auto mutex::try_lock() -> bool - { - return !m_locked.test_and_set(std::memory_order_acquire); - } - - auto mutex::unlock() -> void - { - m_locked.clear(std::memory_order_release); - } - -} // namespace kstd diff --git a/libs/kstd/src/os/error.cpp b/libs/kstd/src/os/error.cpp deleted file mode 100644 index f969cb5..0000000 --- a/libs/kstd/src/os/error.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include - -namespace kstd::os -{ - - [[gnu::weak, noreturn]] - auto abort() -> void - { - os::panic("Abort called."); - } - -} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/src/vformat.cpp b/libs/kstd/src/vformat.cpp deleted file mode 100644 index b7c5121..0000000 --- a/libs/kstd/src/vformat.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include -#include - -#include -#include -#include -#include - -namespace kstd::bits::format -{ - - auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void - { - auto context = kstd::format_context{buffer, args}; - auto parse_context = kstd::format_parse_context{format, args.size()}; - - auto it = parse_context.begin(); - auto end = parse_context.end(); - - while (it != end) - { - if (*it != '{' && *it != '}') - { - auto start = it; - while (it != end && *it != '{' && *it != '}') - { - std::advance(it, 1); - } - parse_context.advance_to(it); - context.push(std::string_view(start, it - start)); - continue; - } - - if (*it == '{') - { - std::advance(it, 1); - if (it != end && *it == '{') - { - context.push('{'); - std::advance(it, 1); - parse_context.advance_to(it); - continue; - } - - parse_context.advance_to(it); - auto index = 0uz; - - if (it != end && *it >= '0' && *it <= '9') - { - while (it != end && *it >= '0' && *it <= '9') - { - index = index * 10 + static_cast(*it - '0'); - std::advance(it, 1); - } - parse_context.check_arg_id(index); - } - else - { - index = parse_context.next_arg_id(); - } - - if (it != end && *it == ':') - { - std::advance(it, 1); - } - - parse_context.advance_to(it); - - if (index < args.size()) - { - auto const & arg = args[index]; - switch (arg.type) - { - case kstd::bits::format::arg_type::boolean: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.boolean, context); - break; - } - case kstd::bits::format::arg_type::character: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.character, context); - break; - } - case kstd::bits::format::arg_type::integer: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.integer, context); - break; - } - case kstd::bits::format::arg_type::unsigned_integer: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.unsigned_integer, context); - break; - } - case kstd::bits::format::arg_type::string_view: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.string_view, context); - break; - } - case kstd::bits::format::arg_type::c_string: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.c_string, context); - break; - } - case kstd::bits::format::arg_type::pointer: - { - auto fmt = kstd::formatter{}; - auto const parsed = fmt.parse(parse_context); - parse_context.advance_to(parsed); - fmt.format(arg.value.pointer, context); - break; - } - case kstd::bits::format::arg_type::user_defined: - { - if (arg.value.user_defined.format) - { - arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context); - } - else - { - context.push("{?}"); - } - break; - } - default: - { - context.push("{fmt-err: unknown-type}"); - break; - } - } - } - else - { - context.push("{fmt-err: bound}"); - } - - it = parse_context.begin(); - - if (it != end && *it == '}') - { - std::advance(it, 1); - parse_context.advance_to(it); - } - else - { - context.push("{fmt-err: unconsumed}"); - while (it != end && *it != '}') - { - std::advance(it, 1); - } - - if (it != end) - { - std::advance(it, 1); - parse_context.advance_to(it); - } - } - } - else if (*it == '}') - { - std::advance(it, 1); - if (it != end && *it == '}') - { - context.push('}'); - std::advance(it, 1); - parse_context.advance_to(it); - } - else - { - context.push("{fmt-err: unescaped}"); - parse_context.advance_to(it); - } - } - } - } - - auto string_writer::push(std::string_view text) -> void - { - m_result.append(text); - } - - auto string_writer::push(char character) -> void - { - m_result.push_back(character); - } - - auto string_writer::release() -> string && - { - return std::move(m_result); - } - -} // namespace kstd::bits::format \ No newline at end of file diff --git a/libs/kstd/tests/include/kstd/tests/os_panic.hpp b/libs/kstd/tests/include/kstd/tests/os_panic.hpp deleted file mode 100644 index 4396a9f..0000000 --- a/libs/kstd/tests/include/kstd/tests/os_panic.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef KSTD_TESTS_OS_PANIC_HPP -#define KSTD_TESTS_OS_PANIC_HPP - -#include -#include -#include - -namespace kstd::tests -{ - - struct os_panic : std::runtime_error - { - os_panic(std::string message, std::source_location location) - : std::runtime_error{message} - , location(location) - {} - - std::source_location location; - }; - -} // namespace kstd::tests - -#endif \ No newline at end of file diff --git a/libs/kstd/tests/include/kstd/tests/test_types.hpp b/libs/kstd/tests/include/kstd/tests/test_types.hpp deleted file mode 100644 index 8231fd3..0000000 --- a/libs/kstd/tests/include/kstd/tests/test_types.hpp +++ /dev/null @@ -1,313 +0,0 @@ -#ifndef KSTD_TESTS_TEST_TYPES_HPP -#define KSTD_TESTS_TEST_TYPES_HPP - -#include -#include -#include - -namespace kstd::tests -{ - - //! A type tracking copy and move operations - //! - //! This type is designed to test move and copy semantics of standard library containers implemented in kstd. - struct special_member_tracker - { - //! A value indicating that the object was moved from. - constexpr auto static moved_from_v = -1; - - constexpr special_member_tracker() - : default_constructed_count{1} - {} - - //! Construct a new move tracker with the given value, if any. - constexpr special_member_tracker(int v = 0) - : value{v} - , value_constructed_count{1} - {} - - //! Construct a new move tracker by copying an existing one. - constexpr special_member_tracker(special_member_tracker const & other) - : value{other.value} - , copy_constructed_count{1} - {} - - //! Construct a new move tracker by moving from an existing one. - constexpr special_member_tracker(special_member_tracker && other) noexcept - : value{other.value} - , move_constructed_count{1} - { - other.value = moved_from_v; - } - - //! Copy assign a new move tracker from an existing one. - constexpr auto operator=(special_member_tracker const & other) -> special_member_tracker & - { - if (this != &other) - { - value = other.value; - ++copy_assigned_count; - ++other.copied_from_count; - } - return *this; - } - - //! Move assign a new move tracker from an existing one. - //! - //! This function ensures that the moved-from state is marked. - constexpr auto operator=(special_member_tracker && other) noexcept -> special_member_tracker & - { - if (this != &other) - { - value = other.value; - ++move_assigned_count; - other.value = moved_from_v; - ++other.moved_from_count; - } - return *this; - } - - ~special_member_tracker() - { - ++destroyed_count; - } - - auto reset_counts() -> void - { - default_constructed_count = 0; - copy_constructed_count = 0; - move_constructed_count = 0; - value_constructed_count = 0; - copy_assigned_count = 0; - move_assigned_count = 0; - destroyed_count = 0; - copied_from_count = 0; - moved_from_count = 0; - } - - //! A simple value to be able to track the move-from state. - int value{}; - //! A counter to track how many times an instance of this type was default constructed. - std::size_t default_constructed_count{0}; - //! A counter to track how many times an instance of this type was copy constructed. - std::size_t copy_constructed_count{0}; - //! A counter to track how many times an instance of this type was move constructed. - std::size_t move_constructed_count{0}; - //! A counter to track how many times an instance of this type was value constructed. - std::size_t value_constructed_count{0}; - //! A counter to track how many times an instance of this type was copy assigned. - std::size_t copy_assigned_count{0}; - //! A counter to track how many times an instance of this type was move assigned. - std::size_t move_assigned_count{0}; - //! A counter to track how many times an instance of this type was destroyed. - std::size_t destroyed_count{0}; - //! A counter to track how many times an instance of this type was copied from another instance. - mutable std::size_t copied_from_count{0}; - //! A counter to track how many times an instance of this type was moved from another instance. - std::size_t moved_from_count{0}; - }; - - //! A type that is not default constructible. - //! - //! This type is designed to test default construction semantics of standard library containers implemented in kstd. - struct non_default_constructible - { - //! A simple placeholder value. - int value{}; - - //! Construct a new non-default-constructible object with the given value. - constexpr explicit non_default_constructible(int v) - : value{v} - {} - - //! Compare two non-default-constructible objects for equality. - [[nodiscard]] constexpr auto operator==(non_default_constructible const & other) const -> bool - { - return value == other.value; - } - }; - - //! An allocator that tracks the number of allocations. - //! - //! This allocator is designed to test allocation semantics of standard library containers implemented in kstd. - //! - //! @tparam T The type of the elements to be allocated. - template - struct tracking_allocator - { - using value_type = T; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - - //! A pointer to a counter that is incremented on allocation. - //! - //! A pointer is used so that multiple allocators can share the same counter. - int * allocation_count{}; - - //! Construct a new tracking allocator referencing the given counter. - tracking_allocator(int * counter) - : allocation_count{counter} - {} - - //! Construct a new tracking allocator by copying from another tracking allocator. - //! - //! This constructor is templated to allow for conversion from allocators of different types. - //! - //! @tparam U The type of the elements to be allocated by the other allocator. - template - tracking_allocator(tracking_allocator const & other) noexcept - : allocation_count{other.allocation_count} - {} - - //! Allocate memory for n elements of type T. - //! - //! @param n The number of elements to allocate. - //! @return A pointer to the allocated memory. - [[nodiscard]] auto allocate(std::size_t n) -> T * - { - if (allocation_count != nullptr) - { - ++(*allocation_count); - } - return static_cast(::operator new(n * sizeof(T))); - } - - //! Deallocate memory for n elements of type T. - //! - //! @param p A pointer to the memory to deallocate. - //! @param n The number of elements to deallocate. - auto deallocate(T * p, std::size_t n) noexcept -> void - { - ::operator delete(p, n * sizeof(T)); - } - - //! Compare two tracking allocators for equality. - //! - //! Two allocators are considered equal if they reference the same allocation counter. - //! - //! @param other The other tracking allocator to compare to. - //! @return True if the two tracking allocators are equal, false otherwise. - [[nodiscard]] auto operator==(tracking_allocator const & other) const -> bool - { - return allocation_count == other.allocation_count; - } - - //! Compare two tracking_allocators for inequality. - //! - //! @param other The other tracking_allocator to compare to. - //! @return True if the two tracking_allocators are not equal, false otherwise. - [[nodiscard]] auto operator!=(tracking_allocator const & other) const -> bool - { - return allocation_count != other.allocation_count; - } - }; - - //! An allocator that propagates copy assignment. - //! - //! This allocator is designed to test copy assignment semantics of standard library containers implemented in kstd. - //! - //! @tparam T The type of the elements to be allocated. - template - struct propagating_allocator : tracking_allocator - { - //! A flag to indicate that the allocator propagates copy assignment. - using propagate_on_container_copy_assignment = std::true_type; - - //! Construct a new propagating allocator referencing the given counter. - //! - //! @param counter A pointer to a counter that is incremented on allocation. - //! @see tracking_allocator::tracking_allocator(int*) - propagating_allocator(int * counter) - : tracking_allocator{counter} - {} - - //! Construct a new propagating allocator by copying from another propagating allocator. - //! - //! This constructor is templated to allow for conversion from allocators of different types. - //! - //! @tparam U The type of the elements to be allocated by the other allocator. - //! @see tracking_allocator::tracking_allocator(tracking_allocator const&) - template - propagating_allocator(propagating_allocator const & other) noexcept - : tracking_allocator{other.allocation_count} - {} - }; - - //! A test input iterator. - //! - //! This iterator is designed to test input iterator semantics of standard library containers implemented in kstd. - struct test_input_iterator - { - using iterator_concept = std::input_iterator_tag; - using iterator_category = std::input_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = int; - using reference = int const &; - using pointer = int const *; - - //! The current element pointed to by the iterator. - int const * current; - //! The number of elements in the range. - std::size_t count; - - explicit test_input_iterator() - : current{nullptr} - , count{0} - {} - - //! Construct a new test input iterator. - //! - //! @param current The current element pointed to by the iterator. - //! @param count The number of elements in the range. - explicit test_input_iterator(int const * current, std::size_t count) - : current{current} - , count{count} - {} - - //! Dereference the iterator to get the current element. - //! - //! @return The current element pointed to by the iterator. - [[nodiscard]] auto operator*() const -> int - { - return *current; - } - - //! Increment the iterator to point to the next element. - //! - //! @return A reference to the incremented iterator. - auto operator++() -> test_input_iterator & - { - ++current; - --count; - return *this; - } - - //! Increment the iterator to point to the next element. - auto operator++(int) -> void - { - ++*this; - } - - //! Compare two test input iterators for equality. - //! - //! @param other The other test input iterator to compare to. - //! @return True if the two test input iterators are equal, false otherwise. - [[nodiscard]] auto operator==(test_input_iterator const & other) const -> bool - { - if (current == nullptr && other.current == nullptr) - { - return true; - } - - if (current == nullptr || other.current == nullptr) - { - return count == other.count; - } - - return current == other.current && count == other.count; - } - }; - -} // namespace kstd::tests - -#endif diff --git a/libs/kstd/tests/src/flat_map.cpp b/libs/kstd/tests/src/flat_map.cpp deleted file mode 100644 index 2b793d9..0000000 --- a/libs/kstd/tests/src/flat_map.cpp +++ /dev/null @@ -1,314 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include - -SCENARIO("Flat Map initialization and construction", "[flat_map]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto map = kstd::flat_map{}; - - THEN("the Flat Map does not contain elements") - { - REQUIRE_FALSE(map.contains(1)); - } - } - } -} - -SCENARIO("Flat Map modifiers", "[flat_map]") -{ - GIVEN("An empty Flat Map") - { - auto map = kstd::flat_map{}; - - WHEN("emplacing a new element") - { - auto [it, inserted] = map.emplace(1, 100); - - THEN("the map contains the new element") - { - REQUIRE(inserted); - REQUIRE(map.contains(1)); - } - } - - WHEN("emplacing an existing element") - { - map.emplace(1, 100); - auto [it, inserted] = map.emplace(1, 200); - - THEN("the map does not insert the duplicate") - { - REQUIRE_FALSE(inserted); - REQUIRE(map.contains(1)); - } - } - } -} - -SCENARIO("Flat Map element access", "[flat_map]") -{ - GIVEN("A populated Flat Map") - { - auto map = kstd::flat_map{}; - map.emplace(1, 10); - map.emplace(2, 20); - map.emplace(3, 30); - - WHEN("accessing an existing element with at()") - { - auto & val = map.at(2); - - THEN("it returns a reference to the mapped value") - { - REQUIRE(val == 20); - } - - THEN("the mapped value can be modified") - { - val = 200; - REQUIRE(map.at(2) == 200); - } - } - - WHEN("accessing a non-existent element with at()") - { - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); - } - } - } - - GIVEN("A const populated Flat Map") - { - auto map_builder = kstd::flat_map{}; - map_builder.emplace(1, 10); - map_builder.emplace(2, 20); - auto const map = map_builder; - - WHEN("accessing an existing element with const at()") - { - auto const & val = map.at(2); - - THEN("it returns a const reference to the mapped value") - { - REQUIRE(val == 20); - } - } - - WHEN("accessing a non-existent element with const at()") - { - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); - } - } - } -} - -SCENARIO("Flat Map iterators", "[flat_map]") -{ - GIVEN("A populated Flat Map") - { - auto map = kstd::flat_map{}; - map.emplace(1, 10); - map.emplace(2, 20); - map.emplace(3, 30); - - WHEN("using forward iterators") - { - THEN("they navigate the elements in the correct forward order") - { - auto it = map.begin(); - REQUIRE(it != map.end()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it != map.end()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.end()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it == map.end()); - } - - THEN("const forward iterators provide correct access") - { - auto it = map.cbegin(); - REQUIRE(it != map.cend()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it != map.cend()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.cend()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it == map.cend()); - } - - THEN("assignment through the proxy modifies the mapped value") - { - auto it = map.begin(); - - *it = std::pair{1, 100}; - - REQUIRE(it->second == 100); - REQUIRE(map.at(1) == 100); - } - - THEN("structured bindings evaluate correctly") - { - auto it = map.cbegin(); - - auto [key, value] = *it; - - REQUIRE(key == 1); - REQUIRE(value == 10); - - STATIC_REQUIRE(std::is_same_v); - STATIC_REQUIRE(std::is_same_v); - } - } - - WHEN("using reverse iterators") - { - THEN("they navigate the elements in the correct reverse order") - { - auto it = map.rbegin(); - REQUIRE(it != map.rend()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it != map.rend()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.rend()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it == map.rend()); - } - - THEN("const reverse iterators provide correct access") - { - auto it = map.crbegin(); - REQUIRE(it != map.crend()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it != map.crend()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.crend()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it == map.crend()); - } - } - } - - GIVEN("an empty Flat Map") - { - auto map = kstd::flat_map{}; - - WHEN("getting iterators") - { - THEN("begin() equals end() and cbegin() equals cend()") - { - REQUIRE(map.begin() == map.end()); - REQUIRE(map.cbegin() == map.cend()); - } - - THEN("rbegin() equals rend() and crbegin() equals crend()") - { - REQUIRE(map.rbegin() == map.rend()); - REQUIRE(map.crbegin() == map.crend()); - } - } - } -} - -SCENARIO("Flat Map heterogeneous element access", "[flat_map]") -{ - GIVEN("A populated Flat Map with a transparent comparator") - { - auto map = kstd::flat_map>{}; - map.emplace(1, 10); - map.emplace(2, 20); - map.emplace(3, 30); - - WHEN("accessing an existing element with a different key type via at()") - { - long const key = 2L; - auto & val = map.at(key); - - THEN("it returns a reference to the mapped value") - { - REQUIRE(val == 20); - } - - THEN("the mapped value can be modified") - { - val = 200; - REQUIRE(map.at(2L) == 200); - } - } - - WHEN("accessing a non-existent element with a different key type via at()") - { - long const key = 4L; - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); - } - } - } - - GIVEN("A const populated Flat Map with a transparent comparator") - { - auto map_builder = kstd::flat_map>{}; - map_builder.emplace(1, 10); - map_builder.emplace(2, 20); - auto const map = map_builder; - - WHEN("accessing an existing element with a different key type via const at()") - { - long const key = 2L; - auto const & val = map.at(key); - - THEN("it returns a const reference to the mapped value") - { - REQUIRE(val == 20); - } - } - - WHEN("accessing a non-existent element with a different key type via const at()") - { - long const key = 4L; - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); - } - } - } -} diff --git a/libs/kstd/tests/src/format.cpp b/libs/kstd/tests/src/format.cpp deleted file mode 100644 index 624779a..0000000 --- a/libs/kstd/tests/src/format.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include - -#include - -#include -#include - -SCENARIO("Formatting to a new string", "[format]") -{ - GIVEN("a format string without any placeholders") - { - auto const & fmt = "This is a test"; - - WHEN("calling format with without any arguments.") - { - auto result = kstd::format(fmt); - - THEN("the result is the unmodified string") - { - REQUIRE(result == "This is a test"); - } - } - - WHEN("calling format with additional arguments") - { - auto result = kstd::format(fmt, 1, 2, 3); - - THEN("the result is the unmodified string") - { - REQUIRE(result == "This is a test"); - } - } - } - - GIVEN("a format string with placeholders") - { - auto const & fmt = "Here are some placeholders: {} {} {}"; - - WHEN("calling format with the same number of arguments as there are placeholders") - { - auto result = kstd::format(fmt, 1, true, 'a'); - - THEN("the result is the formatted string") - { - REQUIRE(result == "Here are some placeholders: 1 true a"); - } - } - - WHEN("calling format with too many arguments") - { - auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6); - - THEN("the result is the formatted string") - { - REQUIRE(result == "Here are some placeholders: 2 false b"); - } - } - } -} - -SCENARIO("Formatting to an output iterator", "[format]") -{ - auto buffer = std::ostringstream{}; - - GIVEN("a format string without any placeholders") - { - auto const & fmt = "This is a test"; - - WHEN("calling format with without any arguments.") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt); - - THEN("the unmodified string is written to the iterator") - { - REQUIRE(buffer.str() == "This is a test"); - } - } - - WHEN("calling format with additional arguments") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, 2, 3); - - THEN("the unmodified string is written to the iterator") - { - REQUIRE(buffer.str() == "This is a test"); - } - } - } - - GIVEN("a format string with placeholders") - { - auto const & fmt = "Here are some placeholders: {} {} {}"; - - WHEN("calling format with the same number of arguments as there are placeholders") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, true, -100); - - THEN("the formatted string is written to the iterator") - { - REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100"); - } - } - - WHEN("calling format with too many arguments") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt, 2, false, -200, 4, 5, 6); - - THEN("the formatted string is written to the iterator") - { - REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200"); - } - } - } -} \ No newline at end of file diff --git a/libs/kstd/tests/src/observer_ptr.cpp b/libs/kstd/tests/src/observer_ptr.cpp deleted file mode 100644 index 0c94892..0000000 --- a/libs/kstd/tests/src/observer_ptr.cpp +++ /dev/null @@ -1,360 +0,0 @@ -#include -#include - -#include - -#include -#include -#include -#include - -namespace -{ - struct Base - { - }; - - struct Derived : Base - { - }; - - struct Element - { - int value{}; - - constexpr auto operator<=>(Element const &) const noexcept = default; - }; -} // namespace - -SCENARIO("Observer Pointer initialization and construction", "[observer_ptr]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto ptr = kstd::observer_ptr{}; - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - } - - WHEN("constructing from a nullptr") - { - auto ptr = kstd::observer_ptr{nullptr}; - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - } - - WHEN("constructing from a raw pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - - THEN("the observer pointer is not null") - { - REQUIRE(ptr); - } - - THEN("the observer pointer points to the correct object") - { - REQUIRE(&*ptr == &value); - } - } - - WHEN("copy constructing from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = ptr; - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("copy constructing from an existing observer pointer with a compatible type") - { - auto value = Derived{}; - auto ptr = kstd::observer_ptr(&value); - kstd::observer_ptr copy = ptr; - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("copy assigning from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = ptr; - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("move constructing from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = std::move(ptr); - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("move assigning from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = std::move(ptr); - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("constructing an observer pointer using make_observer") - { - auto value = 1; - auto ptr = kstd::make_observer(&value); - - THEN("the observer pointer points to the correct object") - { - REQUIRE(&*ptr == &value); - } - - THEN("the observe pointer has the correct element type") - { - STATIC_REQUIRE(std::is_same_v>); - } - } - } -} - -SCENARIO("Observer pointer modifiers", "[observer_ptr]") -{ - GIVEN("A non-null observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - - WHEN("releasing the observer pointer") - { - auto raw_ptr = ptr.release(); - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - - THEN("the returned pointer points to the correct object") - { - REQUIRE(raw_ptr == &value); - } - } - - WHEN("resetting the observer pointer to nullptr") - { - ptr.reset(); - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - } - - WHEN("resetting the observer pointer to a new object") - { - auto other_value = 2; - ptr.reset(&other_value); - - THEN("the observer pointer points to the new object") - { - REQUIRE(&*ptr == &other_value); - } - } - - WHEN("swapping it with another observer pointer") - { - auto other_value = 2; - auto other_ptr = kstd::observer_ptr{&other_value}; - ptr.swap(other_ptr); - - THEN("the observer pointer points to the other object") - { - REQUIRE(&*ptr == &other_value); - } - - THEN("the other observer pointer points to the original object") - { - REQUIRE(&*other_ptr == &value); - } - } - - WHEN("using namespace-level swap to swap it with another observer pointer") - { - using std::swap; - auto other_value = 2; - auto other_ptr = kstd::observer_ptr{&other_value}; - swap(ptr, other_ptr); - - THEN("the observer pointer points to the other object") - { - REQUIRE(&*ptr == &other_value); - } - - THEN("the other observer pointer points to the original object") - { - REQUIRE(&*other_ptr == &value); - } - } - } -} - -SCENARIO("Observer pointer observers", "[observer_ptr]") -{ - GIVEN("A non-null observer pointer") - { - auto value = Element{1}; - auto ptr = kstd::observer_ptr{&value}; - - WHEN("getting the raw pointer") - { - auto raw_ptr = ptr.get(); - - THEN("the raw pointer points to the correct object") - { - REQUIRE(raw_ptr == &value); - } - } - - WHEN("dereferencing the observer pointer") - { - auto dereferenced = *ptr; - - THEN("the dereferenced value is the correct value") - { - REQUIRE(dereferenced == value); - } - } - - WHEN("writing through the observer pointer with the arrow operator") - { - ptr->value = 2; - - THEN("the value is updated") - { - REQUIRE(value.value == 2); - } - } - - WHEN("converting the observer pointer to a raw pointer") - { - auto raw_ptr = static_cast(ptr); - - THEN("the raw pointer points to the correct object") - { - REQUIRE(raw_ptr == &value); - } - } - - WHEN("checking the observer pointer as a boolean") - { - THEN("it returns true") - { - REQUIRE(static_cast(ptr)); - } - } - } - - GIVEN("A null observer pointer") - { - auto ptr = kstd::observer_ptr{}; - - WHEN("checking the observer pointer as a boolean") - { - THEN("it returns false") - { - REQUIRE_FALSE(static_cast(ptr)); - } - } - - WHEN("dereferencing the observer pointer") - { - THEN("the observer pointer panics") - { - REQUIRE_THROWS_AS(*ptr, kstd::tests::os_panic); - } - } - - WHEN("writing through the observer pointer with the arrow operator") - { - THEN("the observer pointer panics") - { - REQUIRE_THROWS_AS(ptr->value = 2, kstd::tests::os_panic); - } - } - } -} - -SCENARIO("Observer pointer comparisons", "[observer_ptr]") -{ - GIVEN("Observer pointers to elements of an array") - { - auto arr = std::array{1, 2}; - auto ptr1 = kstd::observer_ptr{&arr[0]}; - auto ptr2 = kstd::observer_ptr{&arr[1]}; - - WHEN("comparing the same observer pointer") - { - THEN("they are equal") - { - REQUIRE(ptr1 == ptr1); - REQUIRE((ptr1 <=> ptr1) == std::strong_ordering::equal); - } - } - - WHEN("comparing different observer pointers") - { - THEN("they are ordered correctly") - { - REQUIRE(ptr1 != ptr2); - REQUIRE(ptr1 < ptr2); - REQUIRE(ptr1 <= ptr2); - REQUIRE(ptr2 > ptr1); - REQUIRE(ptr2 >= ptr1); - REQUIRE((ptr1 <=> ptr2) == std::strong_ordering::less); - REQUIRE((ptr2 <=> ptr1) == std::strong_ordering::greater); - } - } - } - - GIVEN("A null observer pointer") - { - auto ptr = kstd::observer_ptr{}; - - WHEN("comparing with another null observer pointer") - { - auto other_ptr = kstd::observer_ptr{}; - - THEN("they are equal") - { - REQUIRE(ptr == other_ptr); - REQUIRE((ptr <=> other_ptr) == std::strong_ordering::equal); - } - } - } -} \ No newline at end of file diff --git a/libs/kstd/tests/src/os_panic.cpp b/libs/kstd/tests/src/os_panic.cpp deleted file mode 100644 index 0759763..0000000 --- a/libs/kstd/tests/src/os_panic.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - -#include -#include -#include - -namespace kstd::os -{ - - auto panic(std::string_view message, std::source_location location) - { - throw kstd::tests::os_panic{std::string{message}, location}; - } - -} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp deleted file mode 100644 index 53d7c9a..0000000 --- a/libs/kstd/tests/src/string.cpp +++ /dev/null @@ -1,445 +0,0 @@ -#include - -#include - -#include -#include -#include - -SCENARIO("String initialization and construction", "[string]") -{ - GIVEN("Nothing") - { - WHEN("constructing an empty string") - { - auto str = kstd::string{}; - - THEN("the size is zero and therefore the string is empty") - { - REQUIRE(str.empty()); - REQUIRE(str.size() == 0); - } - - THEN("the string is empty") - { - REQUIRE(str.view() == std::string_view{}); - } - } - } - - GIVEN("A string view") - { - auto view = std::string_view{"Blub Blub"}; - - WHEN("constructing a string from string_view") - { - auto str = kstd::string{view}; - - THEN("the string is not empty and has the same size as the view") - { - REQUIRE(!str.empty()); - REQUIRE(str.size() == view.size()); - } - - THEN("the string contains the same characters as the view") - { - REQUIRE(str.view() == view); - } - } - } - - GIVEN("A C-style string") - { - auto c_str = "Blub Blub"; - - WHEN("constructing a string from the C-style string") - { - auto str = kstd::string{c_str}; - - THEN("the string is not empty and has the same size as the C-style string") - { - REQUIRE(!str.empty()); - REQUIRE(str.size() == std::strlen(c_str)); - } - - THEN("the string contains the same characters as the C-style string") - { - REQUIRE(str.view() == c_str); - } - } - } - - GIVEN("A character") - { - auto ch = 'x'; - - WHEN("constructing a string from the character") - { - auto str = kstd::string{ch}; - - THEN("the string is not empty and has size 1") - { - REQUIRE(!str.empty()); - REQUIRE(str.size() == 1); - } - - THEN("the string contains the same character as the given character") - { - REQUIRE(str.view() == std::string_view{&ch, 1}); - } - } - } - - GIVEN("Another string") - { - auto other = kstd::string{"Blub Blub"}; - - WHEN("copy constructing a new string") - { - auto str = kstd::string{other}; - - THEN("the new string contains the same characters as the original") - { - REQUIRE(str.view() == other.view()); - } - } - - auto str = kstd::string{"Blub"}; - - WHEN("copy assigning another string") - { - auto other = kstd::string{"Blub Blub"}; - str = other; - - THEN("the string contains the same characters as the assigned string") - { - REQUIRE(str.view() == other.view()); - } - } - } - - GIVEN("A string") - { - auto str = kstd::string{"Hello"}; - - WHEN("copy assigning a string view") - { - auto view = std::string_view{"Hello, world!"}; - str = view; - - THEN("the string contains the same characters as the assigned view") - { - REQUIRE(str.view() == view); - } - } - } - - GIVEN("A string") - { - auto str = kstd::string{"Hello"}; - - WHEN("copy assigning a C-style string") - { - auto c_str = "Hello, world!"; - str = c_str; - - THEN("the string contains the same characters as the assigned C-style string") - { - REQUIRE(str.view() == c_str); - } - } - } -} - -SCENARIO("String concatenation", "[string]") -{ - GIVEN("Two strings") - { - auto str1 = kstd::string{"Blub"}; - auto str2 = kstd::string{" Blub"}; - - WHEN("appending the second string to the first string") - { - str1.append(str2); - - THEN("the first string contains the characters of both strings concatenated") - { - REQUIRE(str1.view() == "Blub Blub"); - } - - THEN("the size of the first string is the sum of the sizes of both strings") - { - REQUIRE(str1.size() == str2.size() + 4); - } - } - - WHEN("using operator+= to append the second string to the first string") - { - str1 += str2; - - THEN("the first string contains the characters of both strings concatenated") - { - REQUIRE(str1.view() == "Blub Blub"); - } - - THEN("the size of the first string is the sum of the sizes of both strings") - { - REQUIRE(str1.size() == str2.size() + 4); - } - } - - WHEN("using operator+ to concatenate the two strings into a new string") - { - auto str3 = str1 + str2; - - THEN("the new string contains the characters of both strings concatenated") - { - REQUIRE(str3.view() == "Blub Blub"); - } - - THEN("the size of the new string is the sum of the sizes of both strings") - { - REQUIRE(str3.size() == str1.size() + str2.size()); - } - } - } - - GIVEN("A string and a string view") - { - auto str = kstd::string{"Blub"}; - auto view = std::string_view{" Blub"}; - - WHEN("appending the string view to the string") - { - str.append(view); - - THEN("the string contains the characters of both the original string and the appended view concatenated") - { - REQUIRE(str.view() == "Blub Blub"); - } - - THEN("the size of the string is the sum of the sizes of the original string and the appended view") - { - REQUIRE(str.size() == view.size() + 4); - } - } - } - - GIVEN("A string and a character") - { - auto str = kstd::string{"Blub"}; - auto ch = '!'; - - WHEN("appending the character to the string") - { - str.push_back(ch); - - THEN("the string contains the original characters followed by the appended character") - { - REQUIRE(str.view() == "Blub!"); - } - - THEN("the size of the string is one more than the original size") - { - REQUIRE(str.size() == 5); - } - } - - WHEN("using operator+= to append the character to the string") - { - str += ch; - - THEN("the string contains the original characters followed by the appended character") - { - REQUIRE(str.view() == "Blub!"); - } - - THEN("the size of the string is one more than the original size") - { - REQUIRE(str.size() == 5); - } - } - } -} - -SCENARIO("String conversion and comparison", "[string]") -{ - GIVEN("An unsigned integer") - { - constexpr auto value1 = 12345u; - constexpr auto value2 = 0u; - - WHEN("converting the unsigned integer to a string") - { - auto str1 = kstd::to_string(value1); - auto str2 = kstd::to_string(value2); - - THEN("the string contains the decimal representation of the unsigned integer") - { - REQUIRE(str1.view() == "12345"); - REQUIRE(str2.view() == "0"); - } - } - } - - GIVEN("Two strings with the same characters") - { - auto str1 = kstd::string{"Blub Blub"}; - auto str2 = kstd::string{"Blub Blub"}; - - THEN("the strings are equal") - { - REQUIRE(str1 == str2); - } - - THEN("the strings are not unequal") - { - REQUIRE(!(str1 != str2)); - } - } - - GIVEN("A string and a string view with the same characters") - { - auto str = kstd::string{"Blub Blub"}; - auto view = std::string_view{"Blub Blub"}; - - THEN("the string and the string view are equal") - { - REQUIRE(str == view); - REQUIRE(view == str); - } - - THEN("the string and the string view are not unequal") - { - REQUIRE(!(str != view)); - REQUIRE(!(view != str)); - } - } -} - -SCENARIO("String clearing", "[string]") -{ - GIVEN("A non-empty string") - { - auto str = kstd::string{"Blub Blub"}; - - WHEN("clearing the string") - { - str.clear(); - - THEN("the string is empty and has size zero") - { - REQUIRE(str.empty()); - REQUIRE(str.size() == 0); - } - - THEN("the string contains no characters") - { - REQUIRE(str.view() == std::string_view{}); - } - } - } -} - -SCENARIO("String iteration", "[string]") -{ - GIVEN("A string") - { - auto str = kstd::string{"Blub"}; - - WHEN("iterating over the characters of the string as string_view using a range-based for loop") - { - kstd::string result; - - for (auto ch : str.view()) - { - result.push_back(ch); - } - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == str); - } - } - - WHEN("using std::ranges::for_each to iterate over the characters of the string") - { - kstd::string result; - - std::ranges::for_each(str, [&result](auto ch) { result.push_back(ch); }); - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == str); - } - } - - WHEN("using front and back to access the first and last characters of the string") - { - THEN("front returns the first character of the string") - { - REQUIRE(str.front() == 'B'); - } - - THEN("back returns the last character of the string") - { - REQUIRE(str.back() == 'b'); - } - } - } - - GIVEN("A const string") - { - auto const str = kstd::string{"Blub"}; - - WHEN("iterating over the characters of the string as string_view using a range-based for loop") - { - kstd::string result; - - for (auto ch : str.view()) - { - result.push_back(ch); - } - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == str.view()); - } - } - - WHEN("using front and back to access the first and last characters of the string") - { - THEN("front returns the first character of the string") - { - REQUIRE(str.front() == 'B'); - } - - THEN("back returns the last character of the string") - { - REQUIRE(str.back() == 'b'); - } - } - } - - GIVEN("An empty string") - { - auto str = kstd::string{}; - - WHEN("iterating over the characters of an empty string") - { - kstd::string result; - - for (auto ch : str.view()) - { - result.push_back(ch); - } - - THEN("no characters are iterated and the result is an empty string") - { - REQUIRE(result.empty()); - REQUIRE(result.size() == 0); - REQUIRE(result.view() == std::string_view{}); - } - } - } -} diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp deleted file mode 100644 index 4fb3559..0000000 --- a/libs/kstd/tests/src/vector.cpp +++ /dev/null @@ -1,2003 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include - -SCENARIO("Vector initialization and construction", "[vector]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto v = kstd::vector{}; - - THEN("the vector is empty") - { - REQUIRE(v.empty()); - } - - THEN("the size and capacity are zero") - { - REQUIRE(v.size() == 0); - REQUIRE(v.capacity() == 0); - } - } - - WHEN("constructing with a specific size") - { - auto v = kstd::vector(10); - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size is and capacity match the specified value") - { - REQUIRE(v.size() == 10); - REQUIRE(v.capacity() == 10); - } - } - - WHEN("constructing from an initializer list") - { - auto v = kstd::vector{1, 2, 3, 4, 5}; - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size is and capacity match the specified value") - { - REQUIRE(v.size() == 5); - REQUIRE(v.capacity() == 5); - } - - THEN("the elements are correctly initialized") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - REQUIRE(v[3] == 4); - REQUIRE(v[4] == 5); - } - } - } - - GIVEN("A non-empty range") - { - auto range = std::array{1, 2, 3}; - - WHEN("constructing from a random-access iterator range") - { - auto v = kstd::vector{std::begin(range), std::end(range)}; - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size and capacity match the range size") - { - REQUIRE(v.size() == std::size(range)); - REQUIRE(v.capacity() == std::size(range)); - } - - THEN("the elements are correctly initialized") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - - WHEN("constructing from a range") - { - auto v = kstd::vector{kstd::from_range, range}; - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size and capacity match the range size") - { - REQUIRE(v.size() == std::ranges::size(range)); - REQUIRE(v.capacity() == std::ranges::size(range)); - } - - THEN("the elements are correctly initialized") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - } - - GIVEN("A populated vector") - { - auto source = kstd::vector{1, 2, 3, 4, 5}; - - WHEN("copy constructing a new vector") - { - auto copy = kstd::vector(source); - - THEN("the copy matches the original") - { - REQUIRE(copy.size() == source.size()); - REQUIRE(copy.capacity() == source.capacity()); - REQUIRE(copy[0] == 1); - REQUIRE(copy[1] == 2); - REQUIRE(copy[2] == 3); - REQUIRE(copy[3] == 4); - REQUIRE(copy[4] == 5); - } - - THEN("the original is left unchanged") - { - REQUIRE(source.size() == 5); - REQUIRE(source.capacity() == 5); - REQUIRE(source[0] == 1); - REQUIRE(source[1] == 2); - REQUIRE(source[2] == 3); - REQUIRE(source[3] == 4); - REQUIRE(source[4] == 5); - } - } - - WHEN("move constructing a new vector") - { - auto moved = kstd::vector(std::move(source)); - - THEN("The new vector has the original elements") - { - REQUIRE(moved.size() == 5); - REQUIRE(moved.capacity() == 5); - REQUIRE(moved[0] == 1); - REQUIRE(moved[1] == 2); - REQUIRE(moved[2] == 3); - REQUIRE(moved[3] == 4); - REQUIRE(moved[4] == 5); - } - - THEN("The original vector is left in a valid but unspecified state") - { - REQUIRE(source.empty()); - REQUIRE(source.size() == 0); - REQUIRE(source.capacity() == 0); - } - } - } -} - -SCENARIO("Vector element access", "[vector]") -{ - GIVEN("A populated vector") - { - auto v = kstd::vector{10, 20, 30}; - - WHEN("accessing elements for reading") - { - THEN("operator[] and at() return the correct elements") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - - REQUIRE(v.at(0) == 10); - REQUIRE(v.at(1) == 20); - REQUIRE(v.at(2) == 30); - } - - THEN("front() and back() return the first and last elements") - { - REQUIRE(v.front() == 10); - REQUIRE(v.back() == 30); - } - - THEN("data() return a pointer to the contiguous storage") - { - auto ptr = v.data(); - REQUIRE(ptr); - REQUIRE(ptr[0] == 10); - REQUIRE(ptr[1] == 20); - REQUIRE(ptr[2] == 30); - } - - THEN("accessing out of bounds elements panics") - { - REQUIRE_THROWS_AS(v.at(3), kstd::tests::os_panic); - } - } - - WHEN("accessing elements for writing") - { - v[0] = 100; - v.at(1) = 200; - v.back() = 300; - - THEN("the elements are correctly modified") - { - REQUIRE(v[0] == 100); - REQUIRE(v[1] == 200); - REQUIRE(v[2] == 300); - } - } - } -} - -SCENARIO("Vector iterators", "[vector]") -{ - GIVEN("A populated vector") - { - auto v = kstd::vector{1, 2, 3}; - - WHEN("using forward iterators") - { - THEN("they navigate the elements in the correct forward order") - { - auto it = v.begin(); - REQUIRE(it != v.end()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it != v.end()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.end()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it == v.end()); - } - - THEN("const forward iterators provide correct access") - { - auto it = v.cbegin(); - REQUIRE(it != v.cend()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it != v.cend()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.cend()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it == v.cend()); - } - } - - WHEN("using reverse iterators") - { - THEN("they navigate the elements in the correct reverse order") - { - auto it = v.rbegin(); - REQUIRE(it != v.rend()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it != v.rend()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.rend()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it == v.rend()); - } - - THEN("const reverse iterators provide correct access") - { - auto it = v.crbegin(); - REQUIRE(it != v.crend()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it != v.crend()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.crend()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it == v.crend()); - } - } - } - - GIVEN("an empty vector") - { - auto v = kstd::vector{}; - - WHEN("getting iterators") - { - THEN("begin() equals end() and cbegin() equals cend()") - { - REQUIRE(v.begin() == v.end()); - REQUIRE(v.cbegin() == v.cend()); - } - - THEN("rbegin() equals rend() and crbegin() equals crend()") - { - REQUIRE(v.rbegin() == v.rend()); - REQUIRE(v.crbegin() == v.crend()); - } - } - } -} - -SCENARIO("Vector capacity management", "[vector]") -{ - GIVEN("An empty vector") - { - auto v = kstd::vector{}; - - WHEN("reserving space") - { - v.reserve(10); - - THEN("the capacity is at least the reserved amount") - { - REQUIRE(v.capacity() >= 10); - } - - THEN("the size is still zero") - { - REQUIRE(v.size() == 0); - } - - THEN("the vector is still empty") - { - REQUIRE(v.empty()); - } - } - - WHEN("reserving space less than or equal to current capacity") - { - v.reserve(10); - auto const current_capacity = v.capacity(); - v.reserve(5); - - THEN("the capacity remains unchanged") - { - REQUIRE(v.capacity() == current_capacity); - } - } - - WHEN("reserving space greater than max_size") - { - THEN("a panic is triggered") - { - REQUIRE_THROWS_AS(v.reserve(v.max_size() + 1), kstd::tests::os_panic); - } - } - } - - GIVEN("A populated vector with excess capacity") - { - auto v = kstd::vector{1, 2, 3}; - v.reserve(10); - - REQUIRE(v.capacity() == 10); - - WHEN("calling shrink_to_fit") - { - v.shrink_to_fit(); - - THEN("the capacity is reduced to match the size") - { - REQUIRE(v.capacity() == 3); - REQUIRE(v.size() == 3); - } - - THEN("the elements remain unchanged") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - } -} - -SCENARIO("Vector modifiers", "[vector]") -{ - GIVEN("An empty vector") - { - auto v = kstd::vector{}; - - WHEN("push_back is called with a value") - { - v.push_back(10); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v.back() == 10); - } - } - - WHEN("emplace_back is called with constructor arguments") - { - v.emplace_back(20); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v.back() == 20); - } - } - - WHEN("elements are added while capacity is sufficient") - { - v.reserve(10); - auto const capacity = v.capacity(); - - v.push_back(10); - v.emplace_back(20); - - THEN("the elements are added without reallocation") - { - REQUIRE(v.size() == 2); - REQUIRE(v.capacity() == capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - } - } - - WHEN("emplace is called with the end iterator and constructor arguments") - { - v.emplace(v.end(), 20); - - THEN("the element is appended and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v.back() == 20); - } - } - - WHEN("emplace is called while capacity is sufficient") - { - v.reserve(10); - auto const capacity = v.capacity(); - - v.emplace(v.end(), 20); - - THEN("the element is appended without reallocation") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() == capacity); - REQUIRE(v.back() == 20); - } - } - - WHEN("inserting an element") - { - auto it = v.insert(v.cbegin(), 40); - - THEN("the size and capacity increase and the element is inserted") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v[0] == 40); - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting an lvalue element") - { - auto const value = 40; - auto it = v.insert(v.cbegin(), value); - - THEN("the size and capacity increase and the element is inserted") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v[0] == 40); - REQUIRE(it == v.begin()); - } - } - - WHEN("appending a range") - { - auto const range = std::views::iota(0, 3); - v.append_range(range); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[0] == 0); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 2); - } - } - - WHEN("appending from an input range") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - - v.append_range(std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - - WHEN("appending from an input range with sufficient capacity") - { - v.reserve(3); - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - - v.append_range(std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity stays the same") - { - REQUIRE(v.capacity() == 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - - WHEN("resizing the vector to a greater size") - { - v.resize(3); - - THEN("the size and capacity increase and the elements are value initialized") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() >= 3); - REQUIRE(v[0] == 0); - REQUIRE(v[1] == 0); - REQUIRE(v[2] == 0); - } - } - - WHEN("resizing the vector to a greater size with initial value") - { - v.resize(3, 2); - - THEN("the size and capacity increase and the elements are initialized to the given value") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() >= 3); - REQUIRE(v[0] == 2); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 2); - } - } - - WHEN("inserting a range at the beginning") - { - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.begin(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting a range at the end") - { - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.end(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting from an input range without sufficient capacity") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - } - - GIVEN("A populated vector") - { - auto v = kstd::vector{10, 20, 30}; - auto initial_capacity = v.capacity(); - - WHEN("push_back is called") - { - v.push_back(40); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - } - } - - WHEN("emplace_back is called with constructor arguments") - { - v.emplace_back(40); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - } - } - - WHEN("emplace is called with an iterator and constructor arguments") - { - auto it = v.emplace(v.begin() + 2, 25); - - THEN("the element is inserted and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v.at(2) == 25); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.cbegin() + 2); - REQUIRE(*it == 25); - } - } - - WHEN("emplace is called with an iterator and sufficient capacity") - { - v.reserve(v.size() + 1); - - auto it = v.emplace(v.begin() + 2, 25); - - THEN("the element is inserted and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v.at(2) == 25); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.cbegin() + 2); - REQUIRE(*it == 25); - } - } - - WHEN("push_back is called with a reference to an internal element") - { - v.shrink_to_fit(); - auto const original_value = v[0]; - - v.push_back(v[0]); - - THEN("reallocation handles the internal reference safely without dangling") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == original_value); - REQUIRE(v.back() == original_value); - } - } - - WHEN("pop_back is called") - { - v.pop_back(); - - THEN("the last element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v.capacity() == initial_capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - } - } - - WHEN("clear is called") - { - v.clear(); - - THEN("the vector is empty") - { - REQUIRE(v.empty()); - REQUIRE(v.size() == 0); - REQUIRE(v.capacity() == initial_capacity); - } - } - - WHEN("inserting at the beginning") - { - auto it = v.insert(v.cbegin(), 5); - - THEN("the element is inserted at the front") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 5); - REQUIRE(v[1] == 10); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting in the middle") - { - auto it = v.insert(v.cbegin() + 1, 15); - - THEN("the element is inserted in the middle") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 15); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting at the end") - { - auto it = v.insert(v.cend(), 40); - - THEN("the element is inserted at the back") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - REQUIRE(it == v.begin() + 3); - } - } - - WHEN("inserting an lvalue at the end") - { - auto const value = 40; - auto it = v.insert(v.cend(), value); - - THEN("the element is inserted at the back") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - REQUIRE(it == v.begin() + 3); - } - } - - WHEN("inserting when capacity is sufficient") - { - v.reserve(10); - auto const capacity = v.capacity(); - - auto it = v.insert(v.cbegin() + 1, 15); - - THEN("the element is added without reallocation") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() == capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 15); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting a reference to an existing element with reallocation") - { - v.shrink_to_fit(); - REQUIRE(v.capacity() == v.size()); - auto it = v.insert(v.cbegin() + 1, v[2]); - - THEN("the element is correctly copied and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting a reference to an existing element without reallocation") - { - v.reserve(10); - REQUIRE(v.capacity() > v.size()); - auto it = v.insert(v.cbegin() + 1, v[2]); - - THEN("the element is correctly copied and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting an rvalue reference to an existing element with reallocation") - { - v.shrink_to_fit(); - REQUIRE(v.capacity() == v.size()); - auto it = v.insert(v.cbegin() + 1, std::move(v[2])); - - THEN("the element is correctly moved and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting an rvalue reference to an existing element without reallocation") - { - v.reserve(10); - REQUIRE(v.capacity() > v.size()); - auto it = v.insert(v.cbegin() + 1, std::move(v[2])); - - THEN("the element is correctly moved and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing the first element") - { - auto it = v.erase(v.cbegin()); - - THEN("the first element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 20); - REQUIRE(v[1] == 30); - REQUIRE(it == v.begin()); - } - } - - WHEN("erasing a middle element") - { - auto it = v.erase(v.cbegin() + 1); - - THEN("the middle element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing the last element") - { - auto it = v.erase(v.cend() - 1); - - THEN("the last element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(it == v.end()); - } - } - - WHEN("erasing the end() iterator") - { - THEN("a panic is triggered") - { - REQUIRE_THROWS_AS(v.erase(v.end()), kstd::tests::os_panic); - } - } - - WHEN("erasing a range of elements") - { - auto it = v.erase(v.cbegin() + 1, v.cend() - 1); - - THEN("the specified range is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing an empty range") - { - auto it = v.erase(v.cbegin() + 1, v.cbegin() + 1); - - THEN("the vector is unchanged") - { - REQUIRE(v.size() == 3); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("appending a range") - { - auto initial_size = v.size(); - v.append_range(std::views::iota(0, 3)); - - THEN("capacity is increased") - { - REQUIRE(v.capacity() >= initial_capacity); - } - - THEN("size is increased") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[initial_size + 0] == 0); - REQUIRE(v[initial_size + 1] == 1); - REQUIRE(v[initial_size + 2] == 2); - } - } - - WHEN("resizing the vector to a greater size") - { - auto initial_size = v.size(); - v.resize(initial_size + 3); - - THEN("the size and capacity increase and the elements are value initialized") - { - REQUIRE(v.size() == initial_size + 3); - REQUIRE(v.capacity() >= initial_size + 3); - REQUIRE(v[initial_size + 0] == 0); - REQUIRE(v[initial_size + 1] == 0); - REQUIRE(v[initial_size + 2] == 0); - } - } - - WHEN("resizing the vector to a greater size with initial value") - { - auto initial_size = v.size(); - v.resize(initial_size + 3, 2); - - THEN("the size and capacity increase and the elements are initialized to the given value") - { - REQUIRE(v.size() == initial_size + 3); - REQUIRE(v.capacity() >= initial_size + 3); - REQUIRE(v[initial_size + 0] == 2); - REQUIRE(v[initial_size + 1] == 2); - REQUIRE(v[initial_size + 2] == 2); - } - } - - WHEN("resizing the vector to a smaller size") - { - v.resize(1); - - THEN("the size decreases and the elements are destroyed") - { - REQUIRE(v.size() == 1); - REQUIRE(v[0] == 10); - } - } - - WHEN("inserting an empty range") - { - auto initial_size = v.size(); - auto it = v.insert_range(v.begin(), std::views::empty); - - THEN("the size does not change") - { - REQUIRE(v.size() == initial_size); - } - - THEN("the capacity does not change") - { - REQUIRE(v.capacity() == initial_capacity); - } - - THEN("the content is unchanged") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - } - - THEN("the returned iterator points to the position of insertion") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting a range at the beginning") - { - auto initial_size = v.size(); - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.begin(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= initial_size + 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - REQUIRE(v[3] == 10); - REQUIRE(v[4] == 20); - REQUIRE(v[5] == 30); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting a range at the end") - { - auto initial_size = v.size(); - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.end(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= initial_size + 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 1); - REQUIRE(v[4] == 2); - REQUIRE(v[5] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin() + initial_size); - } - } - - WHEN("inserting a range that causes reallocation") - { - auto initial_size = v.size(); - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.begin() + 1, arr); - - THEN("the size increases") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= initial_size + 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 2); - REQUIRE(v[3] == 3); - REQUIRE(v[4] == 20); - REQUIRE(v[5] == 30); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting fewer elements than the suffix without reallocation") - { - v.reserve(10); - auto const capacity = v.capacity(); - auto const arr = std::array{1}; - auto it = v.insert_range(v.begin() + 1, arr); - - THEN("the elements are correctly placed") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - } - - THEN("no reallocation occurs") - { - REQUIRE(v.capacity() == capacity); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting fewer elements than the suffix without sufficient capacity") - { - v.shrink_to_fit(); - auto const arr = std::array{1}; - auto it = v.insert_range(v.begin() + 1, arr); - - THEN("the elements are correctly placed") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting from an input range without sufficient capacity") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 6); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 6); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - REQUIRE(v[3] == 10); - REQUIRE(v[4] == 20); - REQUIRE(v[5] == 30); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - } -} - -SCENARIO("Vector comparison", "[vector]") -{ - GIVEN("Two identical vectors") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{1, 2, 3}; - - WHEN("comparing for equality") - { - THEN("the vectors are equal") - { - REQUIRE(v1 == v2); - } - - THEN("the vectors and not not-equal") - { - REQUIRE_FALSE(v1 != v2); - } - } - - WHEN("comparing using the spaceship operator") - { - THEN("the vectors are equivalent") - { - REQUIRE((v1 <=> v2) == 0); - REQUIRE(v1 <= v2); - REQUIRE(v1 >= v2); - } - } - } - - GIVEN("Two vectors of different sizes") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{1, 2, 3, 4}; - - WHEN("comparing for equality") - { - THEN("the vectors are not equal") - { - REQUIRE_FALSE(v1 == v2); - } - - THEN("the vectors are not-equal") - { - REQUIRE(v1 != v2); - } - } - - WHEN("comparing for ordering") - { - THEN("the shorter vector evaluates as less than the longer vector") - { - REQUIRE(v1 < v2); - REQUIRE(v2 > v1); - } - } - } - - GIVEN("Two vectors of the same size but different elements") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{1, 2, 4}; - - WHEN("comparing for ordering") - { - THEN("they are ordered lexicographically") - { - REQUIRE(v1 < v2); - REQUIRE(v2 > v1); - } - } - } -} - -SCENARIO("Vector with non-default-constructible types", "[vector]") -{ - GIVEN("A type without a default constructor") - { - WHEN("constructing an empty vector") - { - auto v = kstd::vector{}; - - THEN("the vector is empty") - { - REQUIRE(v.empty()); - } - } - - WHEN("using emplace_back") - { - auto v = kstd::vector{}; - v.emplace_back(40); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back() == kstd::tests::non_default_constructible{40}); - } - } - } -} - -SCENARIO("Vector with custom allocator", "[vector]") -{ - GIVEN("a tracking allocator acting as the vector's memory manager") - { - auto allocations = 0; - auto allocator = kstd::tests::tracking_allocator{&allocations}; - - WHEN("a vector uses this allocator to allocate memory") - { - auto v = kstd::vector>(allocator); - REQUIRE(allocations == 0); - - v.reserve(10); - - THEN("the allocator was used to allocate memory") - { - REQUIRE(allocations > 0); - } - } - } -} - -SCENARIO("Vector modifier move semantics", "[vector]") -{ - GIVEN("An empty vector and a move tracker element") - { - auto v = kstd::vector{}; - auto tracker = kstd::tests::special_member_tracker{40}; - - WHEN("push_back is called with the move tracker") - { - v.push_back(std::move(tracker)); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back().move_constructed_count == 1); - REQUIRE(v.back().copy_constructed_count == 0); - REQUIRE(v.back().value == 40); - } - - THEN("the original tracker is left in a valid but unspecified state") - { - REQUIRE(tracker.value == -1); - } - } - } - - GIVEN("An empty vector") - { - auto v = kstd::vector{}; - - WHEN("emplace_back is called with constructor arguments") - { - v.emplace_back(40); - - THEN("the element is constructed directly, without moves or copies") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back().move_constructed_count == 0); - REQUIRE(v.back().copy_constructed_count == 0); - REQUIRE(v.back().value == 40); - } - } - } - - GIVEN("A populated vector of move trackers") - { - auto v = kstd::vector{}; - v.reserve(10); - v.emplace_back(10); - v.emplace_back(20); - v.emplace_back(30); - - WHEN("inserting an element in the middle with sufficient capacity") - { - auto const tracker = kstd::tests::special_member_tracker{15}; - v.insert(v.cbegin() + 1, tracker); - - THEN("the shifted elements are move-assigned and the new element is copy-assigned") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - REQUIRE(v[1].value == 15); - REQUIRE(v[1].move_assigned_count == 0); - REQUIRE(v[1].copy_assigned_count == 1); - REQUIRE(tracker.copied_from_count == 1); - REQUIRE(v[2].value == 20); - REQUIRE(v[2].move_assigned_count == 1); - REQUIRE(v[2].copy_assigned_count == 0); - REQUIRE(v[3].value == 30); - REQUIRE(v[3].move_constructed_count == 1); - } - } - - WHEN("inserting an rvalue element in the middle with sufficient capacity") - { - auto tracker = kstd::tests::special_member_tracker{15}; - v.insert(v.cbegin() + 1, std::move(tracker)); - - THEN("the shifted elements are move-assigned and the new element is move-assigned") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - REQUIRE(v[1].value == 15); - REQUIRE(v[1].move_assigned_count == 1); - REQUIRE(v[1].copy_assigned_count == 0); - REQUIRE(tracker.moved_from_count == 1); - REQUIRE(v[2].value == 20); - REQUIRE(v[2].move_assigned_count == 1); - REQUIRE(v[3].value == 30); - REQUIRE(v[3].move_constructed_count == 1); - } - } - - WHEN("erasing an element in the middle") - { - for (auto & elem : v) - { - elem.reset_counts(); - } - - auto it = v.erase(v.cbegin() + 1); - - THEN("the subsequent elements are move-assigned leftwards") - { - REQUIRE(v.size() == 2); - - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - - REQUIRE(v[1].value == 30); - REQUIRE(v[1].move_assigned_count == 1); - REQUIRE(v[1].copy_assigned_count == 0); - - REQUIRE(v.data()[2].destroyed_count == 1); - REQUIRE(v.data()[2].moved_from_count == 1); - - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing the last element") - { - for (auto & elem : v) - { - elem.reset_counts(); - } - - auto it = v.erase(v.cend() - 1); - - THEN("no elements are moved, just the last element destroyed") - { - REQUIRE(v.size() == 2); - - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_constructed_count == 0); - REQUIRE(v[0].copy_constructed_count == 0); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - - REQUIRE(v[1].value == 20); - REQUIRE(v[1].move_constructed_count == 0); - REQUIRE(v[1].copy_constructed_count == 0); - REQUIRE(v[1].move_assigned_count == 0); - REQUIRE(v[1].copy_assigned_count == 0); - - REQUIRE(v.data()[2].destroyed_count == 1); - REQUIRE(v.data()[2].moved_from_count == 0); - REQUIRE(v.data()[2].copied_from_count == 0); - - REQUIRE(it == v.end()); - } - } - - WHEN("erasing a range of elements in the middle") - { - v.emplace_back(40); - v.emplace_back(50); - - for (auto & elem : v) - { - elem.reset_counts(); - } - - auto it = v.erase(v.cbegin() + 1, v.cbegin() + 3); - - THEN("the specified elements are destroyed and subsequent elements are move-assigned leftwards") - { - REQUIRE(v.size() == 3); - - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_constructed_count == 0); - REQUIRE(v[0].copy_constructed_count == 0); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - - REQUIRE(v[1].value == 40); - REQUIRE(v[1].move_constructed_count == 0); - REQUIRE(v[1].copy_constructed_count == 0); - REQUIRE(v[1].move_assigned_count == 1); - REQUIRE(v[1].copy_assigned_count == 0); - - REQUIRE(v[2].value == 50); - REQUIRE(v[2].move_constructed_count == 0); - REQUIRE(v[2].copy_constructed_count == 0); - REQUIRE(v[2].move_assigned_count == 1); - REQUIRE(v[2].copy_assigned_count == 0); - - REQUIRE(v.data()[3].destroyed_count == 1); - REQUIRE(v.data()[3].moved_from_count == 1); - REQUIRE(v.data()[3].copied_from_count == 0); - - REQUIRE(v.data()[4].destroyed_count == 1); - REQUIRE(v.data()[4].moved_from_count == 1); - REQUIRE(v.data()[4].copied_from_count == 0); - - REQUIRE(it == v.begin() + 1); - } - } - } -} - -SCENARIO("Vector advanced construction", "[vector]") -{ - GIVEN("A count and a default value") - { - WHEN("constructing with count and default argument") - { - auto const count = 5uz; - auto const value = 42; - auto v = kstd::vector(count, value); - - THEN("the vector is initialized with count copies of value") - { - REQUIRE(v.size() == 5); - REQUIRE(v.capacity() == 5); - REQUIRE(v.front() == 42); - REQUIRE(v.back() == 42); - } - } - } - - GIVEN("A pure input iterator range") - { - WHEN("constructing from input iterators") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - - auto v = kstd::vector(first, last); - - THEN("the vector is generated dynamically and initialized correctly") - { - REQUIRE(v.size() == 3); - REQUIRE(v[0] == 1); - REQUIRE(v[2] == 3); - } - } - } - - GIVEN("A tracking allocator and a source vector") - { - auto allocs = 0; - auto allocator = kstd::tests::tracking_allocator{&allocs}; - auto source = kstd::vector>{allocator}; - source.push_back(1); - source.push_back(2); - source.push_back(3); - - allocs = 0; - - WHEN("copy constructing with an allocator") - { - auto copy = kstd::vector>(source, allocator); - - THEN("the copy succeeds and the allocator is used") - { - REQUIRE(copy.size() == 3); - REQUIRE(allocs > 0); - REQUIRE(copy[0] == 1); - REQUIRE(copy[2] == 3); - } - } - - WHEN("move constructing with an identically comparing allocator") - { - auto moved = kstd::vector>(std::move(source), allocator); - - THEN("the move succeeds and no new allocations are made (memory is stolen)") - { - REQUIRE(moved.size() == 3); - REQUIRE(allocs == 0); - REQUIRE(moved[0] == 1); - REQUIRE(moved[2] == 3); - } - } - - WHEN("move constructing with a non-equal allocator") - { - auto allocs2 = 0; - auto allocator2 = kstd::tests::tracking_allocator{&allocs2}; - - auto moved = kstd::vector>(std::move(source), allocator2); - - THEN("the move allocates new memory and moves elements") - { - REQUIRE(allocs2 > 0); - REQUIRE(moved.size() == 3); - REQUIRE(source.empty()); - } - } - } -} - -SCENARIO("Vector assignment operators", "[vector]") -{ - GIVEN("A source vector and an empty target vector") - { - auto source = kstd::vector{1, 2, 3}; - auto target = kstd::vector{}; - - WHEN("copy assigning") - { - target = source; - - THEN("the target matches the source") - { - REQUIRE(target.size() == 3); - REQUIRE(target[0] == 1); - REQUIRE(target[2] == 3); - REQUIRE(source.size() == 3); - } - } - - WHEN("move assigning") - { - target = std::move(source); - - THEN("the target assumes the source's data") - { - REQUIRE(target.size() == 3); - REQUIRE(target[0] == 1); - REQUIRE(target[2] == 3); - REQUIRE(source.empty()); - } - } - } - - GIVEN("Vectors with propagating copy allocator") - { - auto allocs1 = 0; - auto allocs2 = 0; - auto alloc1 = kstd::tests::propagating_allocator{&allocs1}; - auto alloc2 = kstd::tests::propagating_allocator{&allocs2}; - - auto v1 = kstd::vector>{alloc1}; - v1.push_back(1); - v1.push_back(2); - auto v2 = kstd::vector>{alloc2}; - - WHEN("copy assigning") - { - v2 = v1; - THEN("the allocator propagates") - { - REQUIRE(v2.get_allocator() == v1.get_allocator()); - } - } - } - - GIVEN("Vectors for copy assignment overlap") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{4, 5}; - v2.reserve(10); - - WHEN("copy assigning a larger vector to a smaller one with enough capacity") - { - v2 = v1; - THEN("elements are copied and size is updated") - { - REQUIRE(v2.size() == 3); - REQUIRE(v2.capacity() >= 10); - REQUIRE(v2[2] == 3); - } - } - - auto v3 = kstd::vector{1, 2, 3, 4}; - v3.reserve(10); - WHEN("copy assigning a smaller vector to a larger one") - { - v3 = v1; - THEN("excess elements are destroyed") - { - REQUIRE(v3.size() == 3); - REQUIRE(v3[0] == 1); - } - } - } - - GIVEN("Vectors with the same tracking allocator") - { - auto allocs = 0; - auto alloc = kstd::tests::tracking_allocator{&allocs}; - auto v1 = kstd::vector>{alloc}; - v1.push_back(1); - auto v2 = kstd::vector>{alloc}; - - WHEN("move assigning") - { - v2 = std::move(v1); - THEN("memory is stolen without allocation") - { - REQUIRE(v2.size() == 1); - REQUIRE(allocs == 1); - } - } - } - - GIVEN("Vectors with different non-propagating tracking allocators") - { - auto allocs1 = 0; - auto allocs2 = 0; - auto alloc1 = kstd::tests::tracking_allocator{&allocs1}; - auto alloc2 = kstd::tests::tracking_allocator{&allocs2}; - - auto v1 = kstd::vector>{alloc1}; - v1.push_back(1); - v1.push_back(2); - v1.push_back(3); - - auto v2 = kstd::vector>{alloc2}; - v2.push_back(4); - v2.push_back(5); - - WHEN("move assigning a larger vector to a smaller one without enough capacity") - { - v2.shrink_to_fit(); - v2 = std::move(v1); - THEN("memory is reallocated and elements are moved") - { - REQUIRE(v2.size() == 3); - REQUIRE(allocs2 > 2); - } - } - - auto v3 = kstd::vector>{alloc2}; - v3.reserve(10); - v3.push_back(4); - v3.push_back(5); - WHEN("move assigning a larger vector to a smaller one with enough capacity") - { - v3 = std::move(v1); - THEN("elements are move-assigned over overlap and move-constructed over remainder") - { - REQUIRE(v3.size() == 3); - } - } - - auto v4 = kstd::vector>{alloc2}; - v4.reserve(10); - v4.push_back(4); - v4.push_back(5); - v4.push_back(6); - v4.push_back(7); - WHEN("move assigning a smaller vector to a larger one with enough capacity") - { - v4 = std::move(v1); - THEN("overlap is moved and excess is destroyed") - { - REQUIRE(v4.size() == 3); - } - } - } -} - -SCENARIO("Vector self-assignment operators", "[vector]") -{ - GIVEN("A populated vector") - { - auto v = kstd::vector{1, 2, 3}; - auto const initial_capacity = v.capacity(); - auto const * initial_data = v.data(); - - WHEN("copy assigning to itself") - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" -#pragma GCC diagnostic ignored "-Wunknown-warning-option" -#pragma GCC diagnostic ignored "-Wself-assign-overloaded" - v = v; -#pragma GCC diagnostic pop - - THEN("the vector remains unchanged") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() == initial_capacity); - REQUIRE(v.data() == initial_data); - REQUIRE(v[0] == 1); - REQUIRE(v[2] == 3); - } - } - - WHEN("move assigning to itself") - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" -#pragma GCC diagnostic ignored "-Wunknown-warning-option" -#pragma GCC diagnostic ignored "-Wself-move" - v = std::move(v); -#pragma GCC diagnostic pop - - THEN("the vector remains unchanged") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() == initial_capacity); - REQUIRE(v.data() == initial_data); - REQUIRE(v[0] == 1); - REQUIRE(v[2] == 3); - } - } - } -} - -SCENARIO("Vector const accessors and copy insertion", "[vector]") -{ - GIVEN("A const populated vector") - { - auto const v = kstd::vector{10, 20, 30}; - - WHEN("calling const accessors") - { - THEN("elements are read correctly as const references") - { - REQUIRE(v.front() == 10); - REQUIRE(v.back() == 30); - REQUIRE(v[1] == 20); - REQUIRE(v.at(1) == 20); - } - } - } - - GIVEN("An empty vector and a const lvalue tracker") - { - auto v = kstd::vector{}; - auto const tracker = kstd::tests::special_member_tracker{42}; - - WHEN("push_back is called with the const lvalue") - { - v.push_back(tracker); - - THEN("the element is gracefully copy-constructed") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back().value == 42); - REQUIRE(v.back().copy_constructed_count == 1); - REQUIRE(v.back().move_constructed_count == 0); - } - } - - WHEN("push_back is called with a const lvalue when capacity is sufficient") - { - v.reserve(10); - auto const current_capacity = v.capacity(); - v.push_back(tracker); - - THEN("the element is copy-constructed without reallocation") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() == current_capacity); - REQUIRE(v.back().value == 42); - REQUIRE(v.back().copy_constructed_count == 1); - REQUIRE(v.back().move_constructed_count == 0); - } - } - } -} -- cgit v1.2.3