aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-tidy2
-rw-r--r--arch/x86_64/CMakeLists.txt7
-rw-r--r--arch/x86_64/include/arch/bus/isa.hpp18
-rw-r--r--arch/x86_64/include/arch/device_io/port_io.hpp5
-rw-r--r--arch/x86_64/include/arch/devices/legacy_pit.hpp29
-rw-r--r--arch/x86_64/kapi/devices.cpp35
-rw-r--r--arch/x86_64/src/bus/isa.cpp14
-rw-r--r--arch/x86_64/src/cpu/initialization.cpp2
-rw-r--r--arch/x86_64/src/devices/legacy_pit.cpp60
-rw-r--r--kapi/include/kapi/devices.hpp36
-rw-r--r--kapi/include/kapi/devices/bus.hpp95
-rw-r--r--kapi/include/kapi/devices/manager.hpp53
-rw-r--r--kernel/CMakeLists.txt2
-rw-r--r--kernel/include/kernel/devices/root_bus.hpp16
-rw-r--r--kernel/kapi/devices.cpp87
-rw-r--r--kernel/src/devices/root_bus.cpp12
-rw-r--r--kernel/src/main.cpp11
-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
26 files changed, 1165 insertions, 81 deletions
diff --git a/.clang-tidy b/.clang-tidy
index 8fa3943..61ae9c9 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -60,7 +60,7 @@ CheckOptions:
modernize-use-std-print.ReplacementPrintlnFunction: 'kstd::println'
modernize-use-std-print.PrintHeader: 'kstd/print'
modernize-use-trailing-return-type.TransformLambdas: none
- readability-magic-numbers.IgnoredIntegerValues: '1;2;3;4;5;6;7;10'
+ readability-magic-numbers.IgnoredIntegerValues: '1;2;3;4;5;6;7;10;255'
readability-magic-numbers.IgnorePowersOf2IntegerValues: true
readability-magic-numbers.IgnoreBitFieldsWidths: true
readability-magic-numbers.IgnoreTypeAliases: true
diff --git a/arch/x86_64/CMakeLists.txt b/arch/x86_64/CMakeLists.txt
index 4427e4c..83cae0b 100644
--- a/arch/x86_64/CMakeLists.txt
+++ b/arch/x86_64/CMakeLists.txt
@@ -15,6 +15,7 @@ target_sources("x86_64" PRIVATE
"kapi/boot_modules.cpp"
"kapi/cio.cpp"
"kapi/cpu.cpp"
+ "kapi/devices.cpp"
"kapi/interrupts.cpp"
"kapi/memory.cpp"
"kapi/system.cpp"
@@ -24,6 +25,9 @@ target_sources("x86_64" PRIVATE
"src/cpu/interrupts.cpp"
"src/cpu/interrupt_stubs.S"
+ # Bus Initialization
+ "src/bus/isa.cpp"
+
# Low-level bootstrap
"src/boot/boot32.S"
"src/boot/entry64.s"
@@ -33,6 +37,9 @@ target_sources("x86_64" PRIVATE
# Debug interfaces
"src/debug/qemu_output.cpp"
+ # Devices
+ "src/devices/legacy_pit.cpp"
+
# Memory management
"src/memory/kernel_mapper.cpp"
"src/memory/higher_half_mapper.cpp"
diff --git a/arch/x86_64/include/arch/bus/isa.hpp b/arch/x86_64/include/arch/bus/isa.hpp
new file mode 100644
index 0000000..5deed25
--- /dev/null
+++ b/arch/x86_64/include/arch/bus/isa.hpp
@@ -0,0 +1,18 @@
+#ifndef TEACHOS_X86_64_BUS_ISA_HPP
+#define TEACHOS_X86_64_BUS_ISA_HPP
+
+#include "kapi/devices/bus.hpp"
+
+#include <cstddef>
+
+namespace arch::bus
+{
+
+ struct isa final : public kapi::devices::bus
+ {
+ isa(std::size_t major);
+ };
+
+} // namespace arch::bus
+
+#endif // TEACHOS_X86_64_BUS_ISA_HPP
diff --git a/arch/x86_64/include/arch/device_io/port_io.hpp b/arch/x86_64/include/arch/device_io/port_io.hpp
index 70773dd..4c8d66a 100644
--- a/arch/x86_64/include/arch/device_io/port_io.hpp
+++ b/arch/x86_64/include/arch/device_io/port_io.hpp
@@ -102,6 +102,11 @@ namespace arch::io
: std::string_view{"eax"};
};
+ auto inline wait() -> void
+ {
+ port<0x80, std::uint8_t, port_write>::write<std::uint8_t>(0);
+ }
+
} // namespace arch::io
#endif \ No newline at end of file
diff --git a/arch/x86_64/include/arch/devices/legacy_pit.hpp b/arch/x86_64/include/arch/devices/legacy_pit.hpp
new file mode 100644
index 0000000..de742ae
--- /dev/null
+++ b/arch/x86_64/include/arch/devices/legacy_pit.hpp
@@ -0,0 +1,29 @@
+#ifndef TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP
+#define TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP
+
+#include "kapi/devices/device.hpp"
+#include "kapi/interrupts.hpp"
+
+#include <cstddef>
+#include <cstdint>
+
+namespace arch::devices
+{
+
+ struct legacy_pit : kapi::devices::device, kapi::interrupts::handler
+ {
+ legacy_pit(std::size_t major, std::uint32_t frequency_in_hz);
+
+ auto init() -> bool override;
+
+ auto handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status override;
+
+ private:
+ std::uint32_t m_irq_number{};
+ std::uint32_t m_frequency_in_hz{};
+ std::uint64_t m_ticks{};
+ };
+
+} // namespace arch::devices
+
+#endif \ No newline at end of file
diff --git a/arch/x86_64/kapi/devices.cpp b/arch/x86_64/kapi/devices.cpp
new file mode 100644
index 0000000..b15503d
--- /dev/null
+++ b/arch/x86_64/kapi/devices.cpp
@@ -0,0 +1,35 @@
+#include "kapi/devices.hpp"
+
+#include "arch/bus/isa.hpp"
+#include "arch/devices/legacy_pit.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <cstdint>
+#include <utility>
+
+namespace kapi::devices
+{
+
+ namespace
+ {
+ constexpr auto pit_frequency_in_hz = std::uint32_t{100u};
+ }
+
+ auto init_platform_devices() -> void
+ {
+ kstd::println("[x86_64:devices] Initializing ISA bus...");
+
+ auto isa_major_number = kapi::devices::allocate_major_number();
+ auto isa_bus = kstd::make_unique<arch::bus::isa>(isa_major_number);
+
+ auto pit_major_number = kapi::devices::allocate_major_number();
+ auto pit = kstd::make_unique<arch::devices::legacy_pit>(pit_major_number, pit_frequency_in_hz);
+ isa_bus->add_child(std::move(pit));
+
+ auto & root_bus = get_root_bus();
+ root_bus.add_child(std::move(isa_bus));
+ }
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/arch/x86_64/src/bus/isa.cpp b/arch/x86_64/src/bus/isa.cpp
new file mode 100644
index 0000000..ff4ad71
--- /dev/null
+++ b/arch/x86_64/src/bus/isa.cpp
@@ -0,0 +1,14 @@
+#include "arch/bus/isa.hpp"
+
+#include "kapi/devices.hpp"
+
+#include <cstddef>
+
+namespace arch::bus
+{
+
+ isa::isa(std::size_t major)
+ : kapi::devices::bus{major, 0, "isa"}
+ {}
+
+} // namespace arch::bus \ No newline at end of file
diff --git a/arch/x86_64/src/cpu/initialization.cpp b/arch/x86_64/src/cpu/initialization.cpp
index 878fa07..b808c76 100644
--- a/arch/x86_64/src/cpu/initialization.cpp
+++ b/arch/x86_64/src/cpu/initialization.cpp
@@ -139,7 +139,7 @@ namespace arch::cpu
constexpr auto pic_cascade_address = std::uint8_t{0x04};
constexpr auto pic_cascade_slave_identity = std::uint8_t{0x02};
constexpr auto pic_use_8086_mode = std::uint8_t{0x01};
- constexpr auto pic_master_mask = std::uint8_t{0x01};
+ constexpr auto pic_master_mask = std::uint8_t{0x00};
constexpr auto pic_slave_mask = std::uint8_t{0x00};
pic_master_control_port::write(pic_init_command);
diff --git a/arch/x86_64/src/devices/legacy_pit.cpp b/arch/x86_64/src/devices/legacy_pit.cpp
new file mode 100644
index 0000000..a8df3c3
--- /dev/null
+++ b/arch/x86_64/src/devices/legacy_pit.cpp
@@ -0,0 +1,60 @@
+#include "arch/devices/legacy_pit.hpp"
+
+#include "kapi/devices.hpp"
+#include "kapi/devices/device.hpp"
+#include "kapi/interrupts.hpp"
+
+#include "arch/device_io/port_io.hpp"
+
+#include <cstddef>
+#include <cstdint>
+
+namespace arch::devices
+{
+
+ namespace
+ {
+ using command_port = io::port<0x43, std::uint8_t, io::port_write>;
+ using channel_0_port = io::port<0x40, std::uint8_t, io::port_write>;
+ using channel_1_port = io::port<0x41, std::uint8_t, io::port_write>;
+ using channel_2_port = io::port<0x42, std::uint8_t, io::port_write>;
+
+ constexpr auto base_frequency = 1'193'182u;
+ constexpr auto square_wave_mode = 0x36;
+ } // namespace
+
+ legacy_pit::legacy_pit(std::size_t major, std::uint32_t frequency_in_hz)
+ : kapi::devices::device{major, 0, "legacy_pit"}
+ , m_irq_number{0}
+ , m_frequency_in_hz{frequency_in_hz}
+ {}
+
+ auto legacy_pit::init() -> bool
+ {
+ auto divisor = static_cast<std::uint16_t>(base_frequency / m_frequency_in_hz);
+
+ kapi::interrupts::register_handler(m_irq_number, *this);
+
+ command_port::write<std::uint8_t>(square_wave_mode);
+ io::wait();
+ channel_0_port::write<std::uint8_t>(divisor & 0xff);
+ io::wait();
+ channel_0_port::write<std::uint8_t>(divisor >> 8 & 0xff);
+ io::wait();
+
+ return true;
+ }
+
+ auto legacy_pit::handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status
+ {
+ if (irq_number != m_irq_number)
+ {
+ return kapi::interrupts::status::unhandled;
+ }
+
+ ++m_ticks;
+
+ return kapi::interrupts::status::handled;
+ }
+
+} // namespace arch::devices \ No newline at end of file
diff --git a/kapi/include/kapi/devices.hpp b/kapi/include/kapi/devices.hpp
new file mode 100644
index 0000000..5c01b2f
--- /dev/null
+++ b/kapi/include/kapi/devices.hpp
@@ -0,0 +1,36 @@
+#ifndef TEACHOS_KAPI_DEVICES_HPP
+#define TEACHOS_KAPI_DEVICES_HPP
+
+#include "kapi/devices/bus.hpp" // IWYU pragma: export
+#include "kapi/devices/device.hpp" // IWYU pragma: export
+#include "kapi/devices/manager.hpp" // IWYU pragma: export
+
+namespace kapi::devices
+{
+
+ //! @addtogroup kernel-defined
+ //! @{
+
+ //! Initialize the kernel's device management subsystem.
+ auto init() -> void;
+
+ //! Get the virtual system root bus.
+ //!
+ //! @warning This function will panic if the root bus has not been initialized.
+ //!
+ //! @return a reference to the root bus.
+ auto get_root_bus() -> bus &;
+
+ //! @}
+
+ //! @addtogroup platform-defined
+ //! @{
+
+ //! Initialize the platform's device tree.
+ auto init_platform_devices() -> void;
+
+ //! @}
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/include/kapi/devices/bus.hpp b/kapi/include/kapi/devices/bus.hpp
new file mode 100644
index 0000000..ee774b7
--- /dev/null
+++ b/kapi/include/kapi/devices/bus.hpp
@@ -0,0 +1,95 @@
+#ifndef TEACHOS_KAPI_DEVICES_BUS_HPP
+#define TEACHOS_KAPI_DEVICES_BUS_HPP
+
+#include "kapi/devices/device.hpp"
+#include "kapi/devices/manager.hpp"
+#include "kapi/system.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <atomic>
+#include <cstddef>
+#include <utility>
+
+namespace kapi::devices
+{
+ //! A bus device that represents a logical/physical tree of devices and busses.
+ struct bus : device
+ {
+ //! Construct a bus with the given major number, minor number, and name.
+ //!
+ //! @param major The major number of the bus.
+ //! @param minor The minor number of the bus.
+ //! @param name The name of the bus.
+ bus(std::size_t major, std::size_t minor, kstd::string const & name)
+ : device(major, minor, name)
+ {}
+
+ //! Initialize the bus and all of its children.
+ //!
+ //! @return true iff. the bus and all of its children are healthy, false otherwise.
+ auto init() -> bool final
+ {
+ if (m_initialized.test_and_set())
+ {
+ return true;
+ }
+
+ if (!probe())
+ {
+ return false;
+ }
+
+ return std::ranges::fold_left(m_devices, true, [&](bool acc, auto & child) -> bool {
+ kstd::println("[kAPI:BUS] Initializing child device {}@{}", child->name(), name());
+ return child->init() && acc;
+ });
+ }
+
+ //! Attach a child device to this bus.
+ //!
+ //! Whenever a device is attached to a bus, the bus takes sole ownership of the device.
+ //!
+ //! @param child The child device to attach.
+ auto add_child(kstd::unique_ptr<device> child) -> void
+ {
+ auto observer = m_observers.emplace_back(child.get());
+ m_devices.push_back(std::move(child));
+ kapi::devices::register_device(*observer);
+
+ if (m_initialized.test())
+ {
+ kstd::println("[kAPI:BUS] Initializing child device {}@{}", observer->name(), name());
+ if (!observer->init())
+ {
+ kapi::system::panic("[kAPI:BUS] Failed to initialize child device");
+ }
+ }
+ }
+
+ [[nodiscard]] auto children() const -> kstd::vector<kstd::observer_ptr<device>> const &
+ {
+ return m_observers;
+ }
+
+ protected:
+ //! Probe the bus hardware state.
+ //!
+ //! @return true iff. the bus hardware is healthy, false otherwise.
+ auto virtual probe() -> bool
+ {
+ return true;
+ }
+
+ private:
+ kstd::vector<kstd::unique_ptr<device>> m_devices;
+ kstd::vector<kstd::observer_ptr<device>> m_observers;
+ std::atomic_flag m_initialized{};
+ };
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/include/kapi/devices/manager.hpp b/kapi/include/kapi/devices/manager.hpp
new file mode 100644
index 0000000..7817fbc
--- /dev/null
+++ b/kapi/include/kapi/devices/manager.hpp
@@ -0,0 +1,53 @@
+#ifndef TEACHOS_KAPI_DEVICES_MANAGER_HPP
+#define TEACHOS_KAPI_DEVICES_MANAGER_HPP
+
+// IWYU pragma: private, include "kapi/devices.hpp"
+
+#include "kapi/devices/device.hpp"
+
+#include <kstd/memory>
+
+#include <cstddef>
+#include <string_view>
+
+namespace kapi::devices
+{
+
+ //! @addtogroup kernel-defined
+ //! @{
+
+ //! Ask the kernel to allocate a new major number.
+ //!
+ //! @return a new, unused major number.
+ auto allocate_major_number() -> std::size_t;
+
+ //! Register a new device with the kernel's device manager.
+ //!
+ //! @param device The device to register.
+ //! @return true if the device was registered successfully, false otherwise.
+ auto register_device(device & device) -> bool;
+
+ //! Unregister a device from the kernel's device manager.
+ //!
+ //! @param device The device to unregister.
+ //! @return true if the device was unregistered successfully, false otherwise.
+ auto unregister_device(device & device) -> bool;
+
+ //! Find a device by its major and minor numbers.
+ //!
+ //! @param major the major number of the device.
+ //! @param minor the minor number of the device.
+ //! @return a pointer to the device iff. the device was found, nullptr otherwise.
+ auto find_device(std::size_t major, std::size_t minor) -> kstd::observer_ptr<device>;
+
+ //! Find a device by its name.
+ //!
+ //! @param name the name of the device.
+ //! @return a pointer to the device iff. the device was found, nullptr otherwise.
+ auto find_device(std::string_view name) -> kstd::observer_ptr<device>;
+
+ //! @}
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt
index f283588..9868eb9 100644
--- a/kernel/CMakeLists.txt
+++ b/kernel/CMakeLists.txt
@@ -3,6 +3,7 @@ add_library("kernel_objs" OBJECT
"kapi/boot_modules.cpp"
"kapi/cio.cpp"
"kapi/cpu.cpp"
+ "kapi/devices.cpp"
"kapi/interrupts.cpp"
"kapi/memory.cpp"
"kapi/system.cpp"
@@ -17,6 +18,7 @@ add_library("kernel_objs" OBJECT
"src/memory.cpp"
"src/devices/block_device.cpp"
"src/devices/block_device_utils.cpp"
+ "src/devices/root_bus.cpp"
"src/devices/storage/controller.cpp"
"src/devices/storage/management.cpp"
"src/devices/storage/ram_disk/controller.cpp"
diff --git a/kernel/include/kernel/devices/root_bus.hpp b/kernel/include/kernel/devices/root_bus.hpp
new file mode 100644
index 0000000..660b715
--- /dev/null
+++ b/kernel/include/kernel/devices/root_bus.hpp
@@ -0,0 +1,16 @@
+#ifndef TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP
+#define TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP
+
+#include "kapi/devices/bus.hpp"
+
+namespace kernel::devices
+{
+
+ struct root_bus final : kapi::devices::bus
+ {
+ root_bus();
+ };
+
+} // namespace kernel::devices
+
+#endif \ No newline at end of file
diff --git a/kernel/kapi/devices.cpp b/kernel/kapi/devices.cpp
new file mode 100644
index 0000000..031f2c9
--- /dev/null
+++ b/kernel/kapi/devices.cpp
@@ -0,0 +1,87 @@
+#include "kapi/devices.hpp"
+
+#include "kapi/system.hpp"
+
+#include "kernel/devices/root_bus.hpp"
+
+#include <kstd/flat_map>
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <atomic>
+#include <cstddef>
+#include <optional>
+#include <string_view>
+#include <utility>
+
+namespace kapi::devices
+{
+
+ namespace
+ {
+ auto constinit next_major_number = std::atomic_size_t{1};
+ auto constinit root_bus = std::optional<kernel::devices::root_bus>{};
+ auto constinit device_tree = kstd::flat_map<std::pair<std::size_t, std::size_t>, kstd::observer_ptr<device>>{};
+ } // namespace
+
+ auto init() -> void
+ {
+ auto static is_initialized = std::atomic_flag{};
+ if (is_initialized.test_and_set())
+ {
+ return;
+ }
+
+ auto & bus = root_bus.emplace();
+ register_device(bus);
+ bus.init();
+ }
+
+ auto get_root_bus() -> bus &
+ {
+ if (!root_bus.has_value())
+ {
+ kapi::system::panic("[OS:DEV] Root bus not initialized!");
+ }
+ return *root_bus;
+ }
+
+ auto allocate_major_number() -> std::size_t
+ {
+ return next_major_number++;
+ }
+
+ auto register_device(device & device) -> bool
+ {
+ kstd::println("[OS:DEV] Registering device {}@{}:{}", device.name(), device.major(), device.minor());
+ return device_tree.emplace(std::pair{device.major(), device.minor()}, &device).second;
+ }
+
+ auto unregister_device(device &) -> bool
+ {
+ kstd::println("[OS:DEV] TODO: implement device deregistration");
+ return false;
+ }
+
+ auto find_device(std::size_t major, std::size_t minor) -> kstd::observer_ptr<device>
+ {
+ if (device_tree.contains(std::pair{major, minor}))
+ {
+ return device_tree.at(std::pair{major, minor});
+ }
+ return nullptr;
+ }
+
+ auto find_device(std::string_view name) -> kstd::observer_ptr<device>
+ {
+ for (auto const & [key, value] : device_tree)
+ {
+ if (value->name() == name)
+ {
+ return value;
+ }
+ }
+ return nullptr;
+ }
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/kernel/src/devices/root_bus.cpp b/kernel/src/devices/root_bus.cpp
new file mode 100644
index 0000000..43a35bf
--- /dev/null
+++ b/kernel/src/devices/root_bus.cpp
@@ -0,0 +1,12 @@
+#include "kernel/devices/root_bus.hpp"
+
+#include "kapi/devices.hpp"
+
+namespace kernel::devices
+{
+
+ root_bus::root_bus()
+ : kapi::devices::bus{0, 0, "system"}
+ {}
+
+} // namespace kernel::devices \ No newline at end of file
diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp
index 37b4c5b..2eaa2d8 100644
--- a/kernel/src/main.cpp
+++ b/kernel/src/main.cpp
@@ -1,6 +1,7 @@
#include "kapi/boot_modules.hpp"
#include "kapi/cio.hpp"
#include "kapi/cpu.hpp"
+#include "kapi/devices.hpp"
#include "kapi/interrupts.hpp"
#include "kapi/memory.hpp"
#include "kapi/system.hpp"
@@ -172,13 +173,21 @@ auto main() -> int
kstd::println("[OS] IO subsystem initialized.");
kapi::cpu::init();
- kapi::interrupts::enable();
kapi::memory::init();
kernel::memory::init_heap(kapi::memory::heap_base);
kstd::println("[OS] Memory subsystem initialized.");
kapi::system::memory_initialized();
+ kapi::devices::init();
+ kstd::println("[OS] System root bus initialized.");
+
+ kapi::devices::init_platform_devices();
+ kstd::println("[OS] Platform devices initialized.");
+
+ kapi::interrupts::enable();
+ kstd::println("[OS] Interrupts enabled.");
+
kapi::boot_modules::init();
kstd::println("[OS] Boot module registry initialized.");
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", "[vector]")
{
for (auto & elem : v)
{
- elem.was_copied = false;
- elem.was_moved = false;
+ elem.reset_counts();
}
auto it = v.erase(v.cend() - 1);
@@ -1048,12 +1054,20 @@ 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_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_FALSE(v[1].was_moved);
- REQUIRE_FALSE(v[1].was_copied);
+ 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());
}
@@ -1066,8 +1080,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, v.cbegin() + 3);
@@ -1077,16 +1090,30 @@ SCENARIO("Vector modifier move semantics", "[vector]")
REQUIRE(v.size() == 3);
REQUIRE(v[0].value == 10);
- REQUIRE_FALSE(v[0].was_moved);
- REQUIRE_FALSE(v[0].was_copied);
+ 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].was_moved);
- REQUIRE_FALSE(v[1].was_copied);
+ 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].was_moved);
- REQUIRE_FALSE(v[2].was_copied);
+ 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);
}
@@ -1417,8 +1444,8 @@ SCENARIO("Vector const accessors and copy insertion", "[vector]")
GIVEN("An empty vector and a const lvalue tracker")
{
- auto v = kstd::vector<kstd::tests::move_tracker>{};
- auto const tracker = kstd::tests::move_tracker{42};
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
+ auto const tracker = kstd::tests::special_member_tracker{42};
WHEN("push_back is called with the const lvalue")
{
@@ -1428,8 +1455,8 @@ SCENARIO("Vector const accessors and copy insertion", "[vector]")
{
REQUIRE(v.size() == 1);
REQUIRE(v.back().value == 42);
- REQUIRE(v.back().was_copied);
- REQUIRE_FALSE(v.back().was_moved);
+ REQUIRE(v.back().copy_constructed_count == 1);
+ REQUIRE(v.back().move_constructed_count == 0);
}
}
@@ -1444,8 +1471,8 @@ SCENARIO("Vector const accessors and copy insertion", "[vector]")
REQUIRE(v.size() == 1);
REQUIRE(v.capacity() == current_capacity);
REQUIRE(v.back().value == 42);
- REQUIRE(v.back().was_copied);
- REQUIRE_FALSE(v.back().was_moved);
+ REQUIRE(v.back().copy_constructed_count == 1);
+ REQUIRE(v.back().move_constructed_count == 0);
}
}
}