From 09bf8eba8dbc76dc9c46ec1486cbf2f9530233a8 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 21 Mar 2026 10:44:05 +0100 Subject: implement simple weak_ptr and enable_shared_from_this --- libs/kstd/include/kstd/bits/shared_ptr.hpp | 320 ++++++++++++++++++++++++++--- 1 file changed, 286 insertions(+), 34 deletions(-) (limited to 'libs/kstd') diff --git a/libs/kstd/include/kstd/bits/shared_ptr.hpp b/libs/kstd/include/kstd/bits/shared_ptr.hpp index 6bce83f..674807d 100644 --- a/libs/kstd/include/kstd/bits/shared_ptr.hpp +++ b/libs/kstd/include/kstd/bits/shared_ptr.hpp @@ -10,9 +10,199 @@ 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 @@ -31,12 +221,23 @@ namespace kstd 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) - , ref_count(nullptr) + , control(nullptr) {} /** @@ -44,11 +245,33 @@ namespace kstd * * @param pointer A pointer to an object to manage (default is nullptr). */ - explicit shared_ptr(T * pointer = nullptr) + template + requires(std::is_convertible_v) + explicit shared_ptr(U * pointer = nullptr) : pointer(pointer) - , ref_count(pointer != nullptr ? new std::atomic(1) : nullptr) + , 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) { - // Nothing to do. + if (other.control != nullptr && other.control->shared_count != 0) + { + pointer = other.pointer; + control = other.control; + ++(control->shared_count); + } } /** @@ -58,11 +281,11 @@ namespace kstd */ shared_ptr(shared_ptr const & other) : pointer(other.pointer) - , ref_count(other.ref_count) + , control(other.control) { - if (ref_count != nullptr) + if (control != nullptr) { - ++(*ref_count); + ++(control->shared_count); } } @@ -76,11 +299,11 @@ namespace kstd requires(std::is_convertible_v) shared_ptr(shared_ptr const & other) : pointer(other.pointer) - , ref_count(other.ref_count) + , control(other.control) { - if (ref_count != nullptr) + if (control != nullptr) { - ++(*ref_count); + ++(control->shared_count); } } @@ -91,10 +314,10 @@ namespace kstd */ shared_ptr(shared_ptr && other) noexcept : pointer(other.pointer) - , ref_count(other.ref_count) + , control(other.control) { other.pointer = nullptr; - other.ref_count = nullptr; + other.control = nullptr; } /** @@ -107,10 +330,10 @@ namespace kstd requires(std::is_convertible_v) shared_ptr(shared_ptr && other) noexcept : pointer(other.pointer) - , ref_count(other.ref_count) + , control(other.control) { other.pointer = nullptr; - other.ref_count = nullptr; + other.control = nullptr; } /** @@ -127,11 +350,11 @@ namespace kstd { cleanup(); pointer = other.pointer; - ref_count = other.ref_count; + control = other.control; - if (ref_count != nullptr) + if (control != nullptr) { - ++(*ref_count); + ++(control->shared_count); } } @@ -151,11 +374,11 @@ namespace kstd { cleanup(); pointer = other.pointer; - ref_count = other.ref_count; + control = other.control; - if (ref_count != nullptr) + if (control != nullptr) { - ++(*ref_count); + ++(control->shared_count); } return *this; @@ -174,9 +397,9 @@ namespace kstd { cleanup(); pointer = other.pointer; - ref_count = other.ref_count; + control = other.control; other.pointer = nullptr; - other.ref_count = nullptr; + other.control = nullptr; } return *this; @@ -195,9 +418,9 @@ namespace kstd { cleanup(); pointer = other.pointer; - ref_count = other.ref_count; + control = other.control; other.pointer = nullptr; - other.ref_count = nullptr; + other.control = nullptr; return *this; } @@ -209,7 +432,7 @@ namespace kstd { cleanup(); pointer = nullptr; - ref_count = nullptr; + control = nullptr; return *this; } @@ -230,7 +453,8 @@ namespace kstd { cleanup(); pointer = ptr; - ref_count = ptr != nullptr ? new std::atomic(1) : nullptr; + control = ptr != nullptr ? new shared_control_block() : nullptr; + assign_enable_shared_from_this(ptr); } /** @@ -242,7 +466,7 @@ namespace kstd void swap(shared_ptr & other) { std::swap(pointer, other.pointer); - std::swap(ref_count, other.ref_count); + std::swap(control, other.control); } /** @@ -288,9 +512,9 @@ namespace kstd */ [[nodiscard]] auto use_count() const -> std::size_t { - if (ref_count != nullptr) + if (control != nullptr) { - return *ref_count; + return control->shared_count; } return 0; @@ -328,20 +552,48 @@ namespace kstd [[nodiscard]] auto operator<=>(shared_ptr const & other) const = default; 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 (ref_count != nullptr && --(*ref_count) == 0) + if (control != nullptr) { - delete pointer; - delete ref_count; + if (--(control->shared_count) == 0) + { + delete pointer; + pointer = nullptr; + + if (control->weak_count == 0) + { + delete control; + } + } } } - T * pointer; ///< The managed object. - std::atomic * ref_count; ///< Reference count. + T * pointer; ///< The managed object. + shared_control_block * control; ///< Shared control block. }; /** -- cgit v1.2.3 From 10b77e5c9741211f99cefecd50bcec76dc046d84 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 21 Mar 2026 19:07:02 +0100 Subject: implement simple kstd::string --- libs/kstd/include/kstd/string | 264 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 libs/kstd/include/kstd/string (limited to 'libs/kstd') diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string new file mode 100644 index 0000000..62126a9 --- /dev/null +++ b/libs/kstd/include/kstd/string @@ -0,0 +1,264 @@ +#ifndef KSTD_STRING_HPP +#define KSTD_STRING_HPP + +#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 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 c The character to concatenate. + * @return A new string that is the result of concatenating @p lhs and @p c. + */ + [[nodiscard]] constexpr auto inline operator+(string const & lhs, char const c) -> string + { + string result{lhs}; + result += c; + return result; + } +} // namespace kstd + +#endif \ No newline at end of file -- cgit v1.2.3 From c470ca76ce7801a2a4efb03c9ed606b34b368ded Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 21 Mar 2026 23:57:21 +0100 Subject: implement simple conversion function from unsigned integral values to kstd::string --- libs/kstd/include/kstd/string | 61 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) (limited to 'libs/kstd') diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string index 62126a9..f9583d5 100644 --- a/libs/kstd/include/kstd/string +++ b/libs/kstd/include/kstd/string @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -54,6 +55,29 @@ namespace kstd 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. @@ -250,13 +274,42 @@ namespace kstd /** * @brief Concatenates a strings and a character and returns the result as a new string. * @param lhs The string to concatenate. - * @param c The character to concatenate. - * @return A new string that is the result of concatenating @p lhs and @p c. + * @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, char const c) -> string + [[nodiscard]] constexpr auto inline operator+(string const & lhs, string const & rhs) -> string { string result{lhs}; - result += c; + 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; } } // namespace kstd -- cgit v1.2.3 From 336b25458b75e28c93c0bab23ccd359042f9df41 Mon Sep 17 00:00:00 2001 From: "marcel.braun" Date: Mon, 23 Mar 2026 21:40:34 +0100 Subject: Implement == and != operators for string and string_view --- libs/kstd/include/kstd/string | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'libs/kstd') diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string index f9583d5..075422e 100644 --- a/libs/kstd/include/kstd/string +++ b/libs/kstd/include/kstd/string @@ -312,6 +312,37 @@ namespace kstd 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); + } + } // namespace kstd #endif \ No newline at end of file -- cgit v1.2.3 From 2eb086d516f20a0b5cef9881a3459adb389c6ee8 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 24 Mar 2026 19:06:50 +0100 Subject: implement == and <=> operator in shared_ptr --- libs/kstd/include/kstd/bits/shared_ptr.hpp | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) (limited to 'libs/kstd') diff --git a/libs/kstd/include/kstd/bits/shared_ptr.hpp b/libs/kstd/include/kstd/bits/shared_ptr.hpp index 674807d..8930095 100644 --- a/libs/kstd/include/kstd/bits/shared_ptr.hpp +++ b/libs/kstd/include/kstd/bits/shared_ptr.hpp @@ -546,11 +546,6 @@ namespace kstd return ptr.pointer == nullptr; } - /** - * @brief Defaulted three-way comparator operator. - */ - [[nodiscard]] auto operator<=>(shared_ptr const & other) const = default; - private: /** * @brief If the candidate type inherits from enable_shared_from_this, assigns the internal weak pointer to this @@ -624,6 +619,30 @@ namespace kstd { 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 -- cgit v1.2.3 From 610707e896504a33fa82db4905e57a4822d3bb9d Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Fri, 27 Mar 2026 19:39:55 +0100 Subject: add string tests --- libs/kstd/CMakeLists.txt | 1 + libs/kstd/tests/src/string.cpp | 390 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 391 insertions(+) create mode 100644 libs/kstd/tests/src/string.cpp (limited to 'libs/kstd') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 06543ab..d4f415f 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -46,6 +46,7 @@ if(NOT CMAKE_CROSSCOMPILING) add_executable("kstd_tests" "tests/src/vector.cpp" "tests/src/os_panic.cpp" + "tests/src/string.cpp" ) target_include_directories("kstd_tests" PRIVATE diff --git a/libs/kstd/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp new file mode 100644 index 0000000..f7f629d --- /dev/null +++ b/libs/kstd/tests/src/string.cpp @@ -0,0 +1,390 @@ +#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() == 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") + { + auto value1 = 12345u; + 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'); + } + } + } +} -- cgit v1.2.3 From 4f7ae11655807acf68f49637cc9dd01a03af36d5 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Fri, 27 Mar 2026 20:31:48 +0100 Subject: add some more tests --- libs/kstd/tests/src/string.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'libs/kstd') diff --git a/libs/kstd/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp index f7f629d..43e9a6b 100644 --- a/libs/kstd/tests/src/string.cpp +++ b/libs/kstd/tests/src/string.cpp @@ -387,4 +387,59 @@ SCENARIO("String iteration", "[string]") } } } + + 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{}); + } + } + } } -- cgit v1.2.3 From 1f0d290bc303ac8f039963c4eb6421536d36827c Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 28 Mar 2026 17:29:31 +0100 Subject: string tests --- libs/kstd/tests/src/string.cpp | 84 ++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 35 deletions(-) (limited to 'libs/kstd') diff --git a/libs/kstd/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp index 43e9a6b..a94f0f6 100644 --- a/libs/kstd/tests/src/string.cpp +++ b/libs/kstd/tests/src/string.cpp @@ -386,60 +386,74 @@ SCENARIO("String iteration", "[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") + GIVEN("A non-empty string") { - kstd::string result; - - for (auto ch : str.view()) - { - result.push_back(ch); - } + auto str = kstd::string{"Hello"}; - THEN("the iterated characters are the same as the characters in the string") + WHEN("using const end()") { - REQUIRE(result == str.view()); + auto it = str.end(); // calls const end() + THEN("it points past the last character") + { + REQUIRE(*std::prev(it) == 'o'); + REQUIRE(std::distance(str.begin(), it) == str.size()); + } } } - WHEN("using front and back to access the first and last characters of the string") + GIVEN("A const string") { - THEN("front returns the first character of the string") + auto const str = kstd::string{"Blub"}; + + WHEN("iterating over the characters of the string as string_view using a range-based for loop") { - REQUIRE(str.front() == 'B'); + 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()); + } } - THEN("back returns the last character of the string") + WHEN("using front and back to access the first and last characters of the string") { - REQUIRE(str.back() == 'b'); + 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") + GIVEN("An empty string") { - kstd::string result; + auto str = kstd::string{}; - for (auto ch : str.view()) + WHEN("iterating over the characters of an empty string") { - result.push_back(ch); - } + kstd::string result; - 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{}); + 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{}); + } } } } -} -- cgit v1.2.3 From ddfa0cd69ec06b2ccaae36cb8dd676a324742b9c Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 28 Mar 2026 17:37:17 +0100 Subject: Revert "string tests" This reverts commit 1f0d290bc303ac8f039963c4eb6421536d36827c. --- libs/kstd/tests/src/string.cpp | 84 ++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 49 deletions(-) (limited to 'libs/kstd') diff --git a/libs/kstd/tests/src/string.cpp b/libs/kstd/tests/src/string.cpp index a94f0f6..43e9a6b 100644 --- a/libs/kstd/tests/src/string.cpp +++ b/libs/kstd/tests/src/string.cpp @@ -386,74 +386,60 @@ SCENARIO("String iteration", "[string]") REQUIRE(str.back() == 'b'); } } + } + + GIVEN("A const string") + { + auto const str = kstd::string{"Blub"}; - GIVEN("A non-empty string") + WHEN("iterating over the characters of the string as string_view using a range-based for loop") { - auto str = kstd::string{"Hello"}; + kstd::string result; + + for (auto ch : str.view()) + { + result.push_back(ch); + } - WHEN("using const end()") + THEN("the iterated characters are the same as the characters in the string") { - auto it = str.end(); // calls const end() - THEN("it points past the last character") - { - REQUIRE(*std::prev(it) == 'o'); - REQUIRE(std::distance(str.begin(), it) == str.size()); - } + REQUIRE(result == str.view()); } } - GIVEN("A const string") + WHEN("using front and back to access the first and last characters of the string") { - auto const str = kstd::string{"Blub"}; - - WHEN("iterating over the characters of the string as string_view using a range-based for loop") + THEN("front returns the first character of the string") { - 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()); - } + REQUIRE(str.front() == 'B'); } - WHEN("using front and back to access the first and last characters of the string") + THEN("back returns the last character 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'); - } + REQUIRE(str.back() == 'b'); } } + } + + GIVEN("An empty string") + { + auto str = kstd::string{}; - GIVEN("An empty string") + WHEN("iterating over the characters of an empty string") { - auto str = kstd::string{}; + kstd::string result; - WHEN("iterating over the characters of an empty string") + for (auto ch : str.view()) { - kstd::string result; - - for (auto ch : str.view()) - { - result.push_back(ch); - } + 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{}); - } + 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{}); } } } +} -- cgit v1.2.3