aboutsummaryrefslogtreecommitdiff
path: root/libs/kstd
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-04-02 17:46:49 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-04-02 17:47:03 +0200
commitb2c3f25b453f1b71552fd93de8d11efbda36fcd1 (patch)
treeb9f097cbcf14e264a6818103a73c48a31830ffe4 /libs/kstd
parent1f010078983e6ab4e74ee3d1efcfb3284620b002 (diff)
parentc5afb5c1ce1c084c840dbb58d73af6fe2b235ec7 (diff)
downloadteachos-b2c3f25b453f1b71552fd93de8d11efbda36fcd1.tar.xz
teachos-b2c3f25b453f1b71552fd93de8d11efbda36fcd1.zip
Merge branch 'fmorgner/develop-BA-FS26/pit-device' into develop-BA-FS26
This changeset introduces a central device management system. The system comprises the following components: - kapi::devices::bus (type) A bus can be both a real (physically present inside the machine) or virtual (purely conceptual) bus. Busses form a hierarchy rooted in the virtual root_bus (accessible via `kapi::devices::get_root_bus`). Busses are derived from `kapi::devices::device`, and own devices. This means that a bus can be attached to another bus. This facilitates a uniform structure for device ownership. Each device needs to be attached to exactly one bus. Virtual devices, e.g. RAM disks, should be attached to the root root bus (via the `add_child` member function). Once a device, or bus, has been attached to a bus, it will be initialized (by means of a call to the `init` member function). Busses are responsible to initialize their children. If a bus has already been initialized, any newly attached children will automatically be initialized. During initialization, the `probe` member function of the bus under initialization weill be invoked. This allows a bus implementation to determine if the hardware, if any, is ready to be initialized and to also perform any required initialization steps. IMPORTANT: busses take ownership of their children in the form of a `unique_ptr`. - kapi::devices (namespace) The functions `allocate_major_number`, `register_device`, `unregister_device`, and `find_device` form the device manager. The device manager is responsible for keeping a record of active devices, allowing O(log n) lookup of devices by major and minor, and O(n) lookup by name. Additionally, the device manager is responsible for handing out major numbers (via `allocate_major_number`) and keeping track of active devices. When a child is attached to a bus, it will also automatically be registered with the device manager. Lookup of devices via the device manager can be achieved by means of either overload of `find_device`. Devices should never ask the manager directly for a major number. Instead, a controller should be used, which requests a major number per device "class" (as in kind, not language class). For example, a RAM disk controller may request a single major number for all RAM disks it is going to create. When the controller creates a new device of a given "class", e.g. a RAM disk, it should then supply that major number to the new device instance. IMPORTANT: the device manager does not own the active devices. Rather it holds a `flat_map` indexed by `pair{major, minor}` and resolving to `kstd::observer_ptr<device>`. - kstd::observer_ptr (type) An observer pointer is a simple vocabulary type to express the concept of having no ownership of the pointed-to object. In essence it is equivalent to a standard C-style pointer, but expresses semantics by virtue of being a separate type.
Diffstat (limited to 'libs/kstd')
-rw-r--r--libs/kstd/CMakeLists.txt1
-rw-r--r--libs/kstd/include/kstd/bits/flat_map.hpp2
-rw-r--r--libs/kstd/include/kstd/bits/observer_ptr.hpp163
-rw-r--r--libs/kstd/include/kstd/bits/unique_ptr.hpp25
-rw-r--r--libs/kstd/include/kstd/memory9
-rw-r--r--libs/kstd/include/kstd/string13
-rw-r--r--libs/kstd/tests/include/kstd/tests/test_types.hpp71
-rw-r--r--libs/kstd/tests/src/observer_ptr.cpp359
-rw-r--r--libs/kstd/tests/src/vector.cpp119
9 files changed, 684 insertions, 78 deletions
diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt
index ec0f441..240118e 100644
--- a/libs/kstd/CMakeLists.txt
+++ b/libs/kstd/CMakeLists.txt
@@ -44,6 +44,7 @@ else()
add_executable("kstd_tests"
"tests/src/flat_map.cpp"
"tests/src/vector.cpp"
+ "tests/src/observer_ptr.cpp"
"tests/src/os_panic.cpp"
"tests/src/string.cpp"
)
diff --git a/libs/kstd/include/kstd/bits/flat_map.hpp b/libs/kstd/include/kstd/bits/flat_map.hpp
index 9455549..fe46203 100644
--- a/libs/kstd/include/kstd/bits/flat_map.hpp
+++ b/libs/kstd/include/kstd/bits/flat_map.hpp
@@ -45,7 +45,7 @@ namespace kstd::bits
template<std::size_t Index>
requires(Index >= 0 && Index <= 1)
- constexpr auto get() const noexcept -> decltype(auto)
+ [[nodiscard]] constexpr auto get() const noexcept -> decltype(auto)
{
if constexpr (Index == 0)
{
diff --git a/libs/kstd/include/kstd/bits/observer_ptr.hpp b/libs/kstd/include/kstd/bits/observer_ptr.hpp
new file mode 100644
index 0000000..1c5da15
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/observer_ptr.hpp
@@ -0,0 +1,163 @@
+#ifndef KSTD_OBSERVER_PTR_HPP
+#define KSTD_OBSERVER_PTR_HPP
+
+// IWYU pragma: private, include <kstd/memory>
+
+#include "kstd/os/error.hpp"
+
+#include <compare>
+#include <concepts>
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+namespace kstd
+{
+
+ template<typename ElementType>
+ 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<typename OtherElementType>
+ requires std::convertible_to<OtherElementType *, ElementType *>
+ constexpr observer_ptr(observer_ptr<OtherElementType> 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<element_type>
+ {
+ 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<typename ElementType>
+ constexpr auto swap(observer_ptr<ElementType> & lhs, observer_ptr<ElementType> & 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<typename ElementType>
+ constexpr auto make_observer(ElementType * pointer) noexcept -> observer_ptr<ElementType>
+ {
+ return observer_ptr<ElementType>{pointer};
+ }
+
+} // 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
index e0870b1..3d803b4 100644
--- a/libs/kstd/include/kstd/bits/unique_ptr.hpp
+++ b/libs/kstd/include/kstd/bits/unique_ptr.hpp
@@ -16,6 +16,9 @@ namespace kstd
template<typename T>
struct unique_ptr
{
+ template<typename U>
+ friend struct unique_ptr;
+
/**
* @brief Constructor.
*
@@ -40,6 +43,12 @@ namespace kstd
*/
unique_ptr(unique_ptr const &) = delete;
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ unique_ptr(unique_ptr<U> && other) noexcept
+ : pointer{std::exchange(other.pointer, nullptr)}
+ {}
+
/**
* @brief Deleted copy assignment operator to enforce unique ownership.
*/
@@ -51,10 +60,8 @@ namespace kstd
* @param other Unique pointer to move from.
*/
unique_ptr(unique_ptr && other) noexcept
- : pointer(other.pointer)
- {
- other.pointer = nullptr;
- }
+ : pointer{std::exchange(other.pointer, nullptr)}
+ {}
/**
* @brief Move assignment operator. Transfers ownership from other to *this as if by calling reset(r.release()).
@@ -67,8 +74,7 @@ namespace kstd
if (this != &other)
{
delete pointer;
- pointer = other.pointer;
- other.pointer = nullptr;
+ pointer = std::exchange(other.pointer, nullptr);
}
return *this;
}
@@ -123,9 +129,7 @@ namespace kstd
*/
auto release() -> T *
{
- T * temp = pointer;
- pointer = nullptr;
- return temp;
+ return std::exchange(pointer, nullptr);
}
/**
@@ -139,8 +143,7 @@ namespace kstd
*/
auto reset(T * ptr = nullptr) -> void
{
- delete pointer;
- pointer = ptr;
+ delete std::exchange(pointer, ptr);
}
/**
diff --git a/libs/kstd/include/kstd/memory b/libs/kstd/include/kstd/memory
index cab2fba..493f49a 100644
--- a/libs/kstd/include/kstd/memory
+++ b/libs/kstd/include/kstd/memory
@@ -1,7 +1,8 @@
-#ifndef KSTD_SHARED_POINTER_HPP
-#define KSTD_SHARED_POINTER_HPP
+#ifndef KSTD_MEMORY_HPP
+#define KSTD_MEMORY_HPP
-#include "kstd/bits/shared_ptr.hpp" // IWYU pragma: export
-#include "kstd/bits/unique_ptr.hpp" // IWYU pragma: export
+#include "kstd/bits/observer_ptr.hpp" // IWYU pragma: export
+#include "kstd/bits/shared_ptr.hpp" // IWYU pragma: export
+#include "kstd/bits/unique_ptr.hpp" // IWYU pragma: export
#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string
index 075422e..4ce19ce 100644
--- a/libs/kstd/include/kstd/string
+++ b/libs/kstd/include/kstd/string
@@ -1,6 +1,10 @@
#ifndef KSTD_STRING_HPP
#define KSTD_STRING_HPP
+#include "kstd/bits/format/context.hpp"
+#include "kstd/bits/format/formatter.hpp"
+#include "kstd/bits/format/formatter/string_view.hpp"
+
#include <kstd/cstring>
#include <kstd/os/error.hpp>
#include <kstd/vector>
@@ -343,6 +347,15 @@ namespace kstd
return !(lhs == rhs);
}
+ template<>
+ struct formatter<string> : formatter<std::string_view>
+ {
+ auto format(string const & str, format_context & context) const -> void
+ {
+ formatter<std::string_view>::format(str.view(), context);
+ }
+ };
+
} // namespace kstd
#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
index 6a06311..9207ee9 100644
--- a/libs/kstd/tests/include/kstd/tests/test_types.hpp
+++ b/libs/kstd/tests/include/kstd/tests/test_types.hpp
@@ -11,44 +11,43 @@ 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 move_tracker
+ struct special_member_tracker
{
//! A value indicating that the object was moved from.
constexpr auto static moved_from_v = -1;
- //! A simple value to be able to track the move-from state.
- int value{};
- //! A flag to track if an instance of this type was either copy constructed or copy assigned.
- bool was_copied{false};
- //! A flag to track if an instance of this type was either move constructed or move assigned.
- bool was_moved{false};
+ constexpr special_member_tracker()
+ : default_constructed_count{1}
+ {}
//! Construct a new move tracker with the given value, if any.
- constexpr move_tracker(int v = 0)
+ constexpr special_member_tracker(int v = 0)
: value{v}
+ , value_constructed_count{1}
{}
//! Construct a new move tracker by copying an existing one.
- constexpr move_tracker(move_tracker const & other)
+ constexpr special_member_tracker(special_member_tracker const & other)
: value{other.value}
- , was_copied{true}
+ , copy_constructed_count{1}
{}
//! Construct a new move tracker by moving from an existing one.
- constexpr move_tracker(move_tracker && other) noexcept
+ constexpr special_member_tracker(special_member_tracker && other) noexcept
: value{other.value}
- , was_moved{true}
+ , move_constructed_count{1}
{
other.value = moved_from_v;
}
//! Copy assign a new move tracker from an existing one.
- constexpr auto operator=(move_tracker const & other) -> move_tracker &
+ constexpr auto operator=(special_member_tracker const & other) -> special_member_tracker &
{
if (this != &other)
{
value = other.value;
- was_copied = true;
+ ++copy_assigned_count;
+ ++other.copied_from_count;
}
return *this;
}
@@ -56,16 +55,56 @@ namespace kstd::tests
//! Move assign a new move tracker from an existing one.
//!
//! This function ensures that the moved-from state is marked.
- constexpr auto operator=(move_tracker && other) noexcept -> move_tracker &
+ constexpr auto operator=(special_member_tracker && other) noexcept -> special_member_tracker &
{
if (this != &other)
{
value = other.value;
- was_moved = true;
+ ++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.
diff --git a/libs/kstd/tests/src/observer_ptr.cpp b/libs/kstd/tests/src/observer_ptr.cpp
new file mode 100644
index 0000000..006ebde
--- /dev/null
+++ b/libs/kstd/tests/src/observer_ptr.cpp
@@ -0,0 +1,359 @@
+#include <kstd/memory>
+#include <kstd/tests/os_panic.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <compare>
+#include <type_traits>
+#include <utility>
+
+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<int>{};
+
+ THEN("the observer pointer is null")
+ {
+ REQUIRE_FALSE(ptr);
+ }
+ }
+
+ WHEN("constructing from a nullptr")
+ {
+ auto ptr = kstd::observer_ptr<int>{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<Derived>(&value);
+ kstd::observer_ptr<Base> 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<decltype(ptr), kstd::observer_ptr<int>>);
+ }
+ }
+ }
+}
+
+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<Element *>(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<bool>(ptr));
+ }
+ }
+ }
+
+ GIVEN("A null observer pointer")
+ {
+ auto ptr = kstd::observer_ptr<Element>{};
+
+ WHEN("checking the observer pointer as a boolean")
+ {
+ THEN("it returns false")
+ {
+ REQUIRE_FALSE(static_cast<bool>(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")
+ {
+ int arr[] = {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<int>{};
+
+ WHEN("comparing with another null observer pointer")
+ {
+ auto other_ptr = kstd::observer_ptr<int>{};
+
+ 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/vector.cpp b/libs/kstd/tests/src/vector.cpp
index b7971f4..81bf32f 100644
--- a/libs/kstd/tests/src/vector.cpp
+++ b/libs/kstd/tests/src/vector.cpp
@@ -919,8 +919,8 @@ SCENARIO("Vector modifier move semantics", "[vector]")
{
GIVEN("An empty vector and a move tracker element")
{
- auto v = kstd::vector<kstd::tests::move_tracker>{};
- auto tracker = kstd::tests::move_tracker{42};
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
+ auto tracker = kstd::tests::special_member_tracker{42};
WHEN("push_back is called with the move tracker")
{
@@ -929,8 +929,8 @@ SCENARIO("Vector modifier move semantics", "[vector]")
THEN("the element is added and the size and capacity increase")
{
REQUIRE(v.size() == 1);
- REQUIRE(v.back().was_moved);
- REQUIRE_FALSE(v.back().was_copied);
+ REQUIRE(v.back().move_constructed_count == 1);
+ REQUIRE(v.back().copy_constructed_count == 0);
REQUIRE(v.back().value == 42);
}
@@ -943,7 +943,7 @@ SCENARIO("Vector modifier move semantics", "[vector]")
GIVEN("An empty vector")
{
- auto v = kstd::vector<kstd::tests::move_tracker>{};
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
WHEN("emplace_back is called with constructor arguments")
{
@@ -952,8 +952,8 @@ SCENARIO("Vector modifier move semantics", "[vector]")
THEN("the element is constructed directly, without moves or copies")
{
REQUIRE(v.size() == 1);
- REQUIRE_FALSE(v.back().was_moved);
- REQUIRE_FALSE(v.back().was_copied);
+ REQUIRE(v.back().move_constructed_count == 0);
+ REQUIRE(v.back().copy_constructed_count == 0);
REQUIRE(v.back().value == 42);
}
}
@@ -961,7 +961,7 @@ SCENARIO("Vector modifier move semantics", "[vector]")
GIVEN("A populated vector of move trackers")
{
- auto v = kstd::vector<kstd::tests::move_tracker>{};
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
v.reserve(10);
v.emplace_back(10);
v.emplace_back(20);
@@ -969,41 +969,46 @@ SCENARIO("Vector modifier move semantics", "[vector]")
WHEN("inserting an element in the middle with sufficient capacity")
{
- auto const tracker = kstd::tests::move_tracker{15};
+ 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_FALSE(v[0].was_moved);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
REQUIRE(v[1].value == 15);
- REQUIRE(v[1].was_copied);
- REQUIRE_FALSE(v[1].was_moved);
+ 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].was_moved);
+ REQUIRE(v[2].move_assigned_count == 1);
+ REQUIRE(v[2].copy_assigned_count == 0);
REQUIRE(v[3].value == 30);
- REQUIRE(v[3].was_moved);
+ REQUIRE(v[3].move_constructed_count == 1);
}
}
WHEN("inserting an rvalue element in the middle with sufficient capacity")
{
- auto tracker = kstd::tests::move_tracker{15};
+ 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_FALSE(v[0].was_moved);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
REQUIRE(v[1].value == 15);
- REQUIRE_FALSE(v[1].was_copied);
- REQUIRE(v[1].was_moved);
+ 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].was_moved);
+ REQUIRE(v[2].move_assigned_count == 1);
REQUIRE(v[3].value == 30);
- REQUIRE(v[3].was_moved);
+ REQUIRE(v[3].move_constructed_count == 1);
}
}
@@ -1011,8 +1016,7 @@ SCENARIO("Vector modifier move semantics", "[vector]")
{
for (auto & elem : v)
{
- elem.was_copied = false;
- elem.was_moved = false;
+ elem.reset_counts();
}
auto it = v.erase(v.cbegin() + 1);
@@ -1022,12 +1026,15 @@ SCENARIO("Vector modifier move semantics", "[vector]")
REQUIRE(v.size() == 2);
REQUIRE(v[0].value == 10);
- REQUIRE_FALSE(v[0].was_moved);
- REQUIRE_FALSE(v[0].was_copied);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
REQUIRE(v[1].value == 30);
- REQUIRE(v[1].was_moved);
- REQUIRE_FALSE(v[1].was_copied);
+ 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);
}
@@ -1037,8 +1044,7 @@ SCENARIO("Vector modifier move semantics", "[vect