aboutsummaryrefslogtreecommitdiff
path: root/libs/kstd
diff options
context:
space:
mode:
authorLukas Oesch <lukas.oesch@ost.ch>2026-06-10 10:40:46 +0200
committerLukas Oesch <lukas.oesch@ost.ch>2026-06-10 10:40:46 +0200
commit33abd5cf264cb9e34121082105b0bc17b3cf7a36 (patch)
tree36b15d53fea04f4f9d9af817100f7ad013bd9b5c /libs/kstd
parentd01caf1c4aef3c89c68b9d1cc9fe56445f0860b5 (diff)
parent7e27130c342b7299a1d2188a7192a7f17b5ac2ad (diff)
downloadkernel-33abd5cf264cb9e34121082105b0bc17b3cf7a36.tar.xz
kernel-33abd5cf264cb9e34121082105b0bc17b3cf7a36.zip
Merge branch 'develop-BA-FS26' into 'develop'HEADdevelop
Merge of BA-FS26 branch into develop See merge request teachos/kernel!49
Diffstat (limited to 'libs/kstd')
-rw-r--r--libs/kstd/CMakeLists.txt117
-rw-r--r--libs/kstd/gdb/__init__.py23
-rw-r--r--libs/kstd/gdb/smart_pointers.py68
-rw-r--r--libs/kstd/gdb/string.py27
-rw-r--r--libs/kstd/gdb/units.py0
-rw-r--r--libs/kstd/gdb/vector.py41
-rw-r--r--libs/kstd/include/kstd/bits/format_context.hpp31
-rw-r--r--libs/kstd/include/kstd/bits/format_specs.hpp104
-rw-r--r--libs/kstd/include/kstd/bits/format_string.hpp131
-rw-r--r--libs/kstd/include/kstd/bits/formatter.hpp300
-rw-r--r--libs/kstd/include/kstd/bits/shared_ptr.hpp258
-rw-r--r--libs/kstd/include/kstd/format8
-rw-r--r--libs/kstd/include/kstd/memory7
-rw-r--r--libs/kstd/include/kstd/vector569
-rw-r--r--libs/kstd/kstd/allocator64
-rw-r--r--libs/kstd/kstd/asm_ptr (renamed from libs/kstd/include/kstd/asm_ptr)0
-rw-r--r--libs/kstd/kstd/bits/concepts.hpp15
-rw-r--r--libs/kstd/kstd/bits/flat_map.hpp186
-rw-r--r--libs/kstd/kstd/bits/format/arg.hpp75
-rw-r--r--libs/kstd/kstd/bits/format/args.hpp160
-rw-r--r--libs/kstd/kstd/bits/format/context.hpp65
-rw-r--r--libs/kstd/kstd/bits/format/error.hpp24
-rw-r--r--libs/kstd/kstd/bits/format/formatter.hpp92
-rw-r--r--libs/kstd/kstd/bits/format/formatter/bool.hpp83
-rw-r--r--libs/kstd/kstd/bits/format/formatter/byte.hpp23
-rw-r--r--libs/kstd/kstd/bits/format/formatter/char.hpp94
-rw-r--r--libs/kstd/kstd/bits/format/formatter/cstring.hpp29
-rw-r--r--libs/kstd/kstd/bits/format/formatter/integral.hpp204
-rw-r--r--libs/kstd/kstd/bits/format/formatter/ordering.hpp111
-rw-r--r--libs/kstd/kstd/bits/format/formatter/pointer.hpp42
-rw-r--r--libs/kstd/kstd/bits/format/formatter/range.hpp32
-rw-r--r--libs/kstd/kstd/bits/format/formatter/string_view.hpp41
-rw-r--r--libs/kstd/kstd/bits/format/fwd.hpp23
-rw-r--r--libs/kstd/kstd/bits/format/output_buffer.hpp32
-rw-r--r--libs/kstd/kstd/bits/format/parse_context.hpp109
-rw-r--r--libs/kstd/kstd/bits/format/specifiers.hpp205
-rw-r--r--libs/kstd/kstd/bits/format/string.hpp148
-rw-r--r--libs/kstd/kstd/bits/format/vformat.hpp99
-rw-r--r--libs/kstd/kstd/bits/observer_ptr.hpp163
-rw-r--r--libs/kstd/kstd/bits/observer_ptr.test.cpp360
-rw-r--r--libs/kstd/kstd/bits/print_sink.hpp (renamed from libs/kstd/include/kstd/bits/print_sink.hpp)2
-rw-r--r--libs/kstd/kstd/bits/shared_ptr.hpp648
-rw-r--r--libs/kstd/kstd/bits/unique_ptr.hpp (renamed from libs/kstd/include/kstd/bits/unique_ptr.hpp)25
-rw-r--r--libs/kstd/kstd/cstring21
-rw-r--r--libs/kstd/kstd/ext/bitfield_enum (renamed from libs/kstd/include/kstd/ext/bitfield_enum)0
-rw-r--r--libs/kstd/kstd/flat_map406
-rw-r--r--libs/kstd/kstd/flat_map.test.cpp351
-rw-r--r--libs/kstd/kstd/format22
-rw-r--r--libs/kstd/kstd/format.test.cpp114
-rw-r--r--libs/kstd/kstd/libc/stdlib.cpp (renamed from libs/kstd/src/libc/stdlib.cpp)2
-rw-r--r--libs/kstd/kstd/libc/string.cpp78
-rw-r--r--libs/kstd/kstd/memory8
-rw-r--r--libs/kstd/kstd/mutex (renamed from libs/kstd/include/kstd/mutex)0
-rw-r--r--libs/kstd/kstd/mutex.cpp (renamed from libs/kstd/src/mutex.cpp)4
-rw-r--r--libs/kstd/kstd/os/error.cpp (renamed from libs/kstd/src/os/error.cpp)2
-rw-r--r--libs/kstd/kstd/os/error.hpp (renamed from libs/kstd/include/kstd/os/error.hpp)0
-rw-r--r--libs/kstd/kstd/os/print.hpp (renamed from libs/kstd/include/kstd/os/print.hpp)4
-rw-r--r--libs/kstd/kstd/print (renamed from libs/kstd/include/kstd/print)24
-rw-r--r--libs/kstd/kstd/ranges21
-rw-r--r--libs/kstd/kstd/stack (renamed from libs/kstd/include/kstd/stack)3
-rw-r--r--libs/kstd/kstd/string369
-rw-r--r--libs/kstd/kstd/string.test.cpp445
-rw-r--r--libs/kstd/kstd/test_support/os_panic.hpp23
-rw-r--r--libs/kstd/kstd/test_support/os_panic.test.cpp15
-rw-r--r--libs/kstd/kstd/test_support/test_types.hpp313
-rw-r--r--libs/kstd/kstd/unikstd.h12
-rw-r--r--libs/kstd/kstd/units149
-rw-r--r--libs/kstd/kstd/vector1079
-rw-r--r--libs/kstd/kstd/vector.test.cpp2003
-rw-r--r--libs/kstd/kstd/vformat.cpp209
-rw-r--r--libs/kstd/src/libc/string.cpp53
71 files changed, 9038 insertions, 1530 deletions
diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt
index 1f140f6..0f64761 100644
--- a/libs/kstd/CMakeLists.txt
+++ b/libs/kstd/CMakeLists.txt
@@ -1,49 +1,100 @@
-add_library("kstd" STATIC)
-add_library("libs::kstd" ALIAS "kstd")
+cmake_minimum_required(VERSION "3.27.0")
-set(KSTD_LIBC_SYMBOLS
- "abort"
- "strlen"
- "memcmp"
+project("kstd"
+ DESCRIPTION "A kernel STL implementation"
+ VERSION "0.0.1"
+ LANGUAGES CXX
)
-target_sources("kstd" PRIVATE
- "src/libc/stdlib.cpp"
- "src/libc/string.cpp"
+include("CTest")
+
+#[============================================================================[
+# Library
+#]============================================================================]
+
+add_library("kstd" STATIC)
+add_library("kstd::lib" ALIAS "kstd")
- "src/os/error.cpp"
+target_sources("kstd" PRIVATE
+ "kstd/os/error.cpp"
+ "kstd/mutex.cpp"
+ "kstd/vformat.cpp"
+)
- "src/mutex.cpp"
+file(GLOB_RECURSE KSTD_HEADERS
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_DEPENDS
+ "kstd/*"
)
+list(FILTER KSTD_HEADERS EXCLUDE REGEX ".*\.cpp")
+
target_sources("kstd" PUBLIC
FILE_SET HEADERS
- BASE_DIRS "include"
+ BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}"
FILES
- "include/kstd/bits/format_context.hpp"
- "include/kstd/bits/format_specs.hpp"
- "include/kstd/bits/format_string.hpp"
- "include/kstd/bits/formatter.hpp"
- "include/kstd/bits/shared_ptr.hpp"
- "include/kstd/bits/unique_ptr.hpp"
-
- "include/kstd/ext/bitfield_enum"
-
- "include/kstd/os/error.hpp"
- "include/kstd/os/print.hpp"
-
- "include/kstd/asm_ptr"
- "include/kstd/format"
- "include/kstd/memory"
- "include/kstd/mutex"
- "include/kstd/stack"
- "include/kstd/vector"
+ ${KSTD_HEADERS}
)
target_include_directories("kstd" PUBLIC
- "include"
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+)
+
+set_target_properties("kstd" PROPERTIES
+ VERIFY_INTERFACE_HEADER_SETS YES
)
-list(TRANSFORM KSTD_LIBC_SYMBOLS PREPEND "-Wl,--undefined=")
+if(NOT BUILD_TESTING)
+ target_sources("kstd" PRIVATE
+ "kstd/libc/stdlib.cpp"
+ "kstd/libc/string.cpp"
+ )
+
+ set(KSTD_LIBC_SYMBOLS
+ "abort"
+ "strlen"
+ "memcmp"
+ "memcpy"
+ )
+
+ list(TRANSFORM KSTD_LIBC_SYMBOLS PREPEND "-Wl,--undefined=")
+
+ target_link_options("kstd" INTERFACE ${KSTD_LIBC_SYMBOLS})
+endif()
+
+#[============================================================================[
+# Tests
+#]============================================================================]
+
+if(BUILD_TESTING)
+ find_package("Catch2")
+ include("Catch")
+
+ add_executable("kstd_tests")
+ add_executable("kstd::tests" ALIAS "kstd_tests")
+
+ target_sources("kstd_tests" PRIVATE
+ "kstd/flat_map.test.cpp"
+ "kstd/format.test.cpp"
+ "kstd/vector.test.cpp"
+ "kstd/bits/observer_ptr.test.cpp"
+ "kstd/test_support/os_panic.test.cpp"
+ "kstd/string.test.cpp"
+ )
+
+ target_link_libraries("kstd_tests" PRIVATE
+ "Catch2::Catch2WithMain"
+ "kstd::lib"
+ )
+
+ set_target_properties("kstd_tests" PROPERTIES
+ EXCLUDE_FROM_ALL NO
+ )
+
+ if(COMMAND "enable_coverage")
+ enable_coverage("kstd")
+ enable_coverage("kstd_tests")
+ endif()
-target_link_options("kstd" INTERFACE ${KSTD_LIBC_SYMBOLS}) \ No newline at end of file
+ catch_discover_tests("kstd::tests" ${CATCH_TEST_ARGS})
+endif() \ No newline at end of file
diff --git a/libs/kstd/gdb/__init__.py b/libs/kstd/gdb/__init__.py
new file mode 100644
index 0000000..c5d1e53
--- /dev/null
+++ b/libs/kstd/gdb/__init__.py
@@ -0,0 +1,23 @@
+import gdb.printing
+
+from .vector import KstdVectorPrinter
+from .string import KstdStringPrinter
+from .smart_pointers import (
+ KstdUniquePtrPrinter,
+ KstdSharedPtrPrinter,
+ KstdObserverPtrPrinter,
+)
+
+
+def build_pretty_printers():
+ pp = gdb.printing.RegexpCollectionPrettyPrinter("kstd")
+ pp.add_printer("vector", "^kstd::vector<.*>$", KstdVectorPrinter)
+ pp.add_printer("string", "^kstd::string$", KstdStringPrinter)
+ pp.add_printer("unique_ptr", "^kstd::unique_ptr<.*>$", KstdUniquePtrPrinter)
+ pp.add_printer("shared_ptr", "^kstd::shared_ptr<.*>$", KstdSharedPtrPrinter)
+ pp.add_printer("observer_ptr", "^kstd::observer_ptr<.*>$", KstdObserverPtrPrinter)
+ return pp
+
+
+def register_printers(objfile):
+ gdb.printing.register_pretty_printer(objfile, build_pretty_printers(), replace=True)
diff --git a/libs/kstd/gdb/smart_pointers.py b/libs/kstd/gdb/smart_pointers.py
new file mode 100644
index 0000000..f6e8a45
--- /dev/null
+++ b/libs/kstd/gdb/smart_pointers.py
@@ -0,0 +1,68 @@
+import gdb
+
+
+class KstdUniquePtrPrinter(gdb.ValuePrinter):
+ def __init__(self, val):
+ self.__val = val
+ self.__type = val.type.template_argument(0)
+
+ def to_string(self):
+ pointer = self.__val["pointer"]
+ if int(pointer) == 0:
+ return f"kstd::unique_ptr<{self.__type}> (empty)"
+ return f"kstd::unique_ptr<{self.__type}>"
+
+ def children(self):
+ pointer = self.__val["pointer"]
+ if int(pointer) != 0:
+ yield ("get()", pointer.dereference())
+
+ def display_hint(self):
+ return None
+
+
+class KstdSharedPtrPrinter(gdb.ValuePrinter):
+ def __init__(self, val):
+ self.__val = val
+ self.__type = val.type.template_argument(0)
+
+ def to_string(self):
+ pointer = self.__val["pointer"]
+ control_block = self.__val["control"]
+
+ if int(pointer) == 0 or int(control_block) == 0:
+ return f"shared_ptr<{self.__type}> (empty)"
+
+ strong_refs = int(control_block["shared_count"]["_M_i"])
+ weak_refs = int(control_block["weak_count"]["_M_i"])
+
+ return (
+ f"shared_ptr<{self.__type}> use count {strong_refs}, weak count {weak_refs}"
+ )
+
+ def children(self):
+ pointer = self.__val["pointer"]
+ control_block = self.__val["control"]
+
+ if int(pointer) != 0:
+ yield ("get()", pointer.dereference())
+
+ def display_hint(self):
+ return None
+
+
+class KstdObserverPtrPrinter(gdb.ValuePrinter):
+ def __init__(self, val):
+ self.__val = val
+ self.__type = val.type.template_argument(0)
+ self.__pointer = val["m_ptr"]
+
+ def to_string(self):
+ return f"{(self.__pointer)}"
+
+ def children(self):
+ if int(self.__pointer) != 0:
+ yield ("get()", self.__pointer.dereference())
+
+ def display_hint(self):
+ return None
diff --git a/libs/kstd/gdb/string.py b/libs/kstd/gdb/string.py
new file mode 100644
index 0000000..73c22d6
--- /dev/null
+++ b/libs/kstd/gdb/string.py
@@ -0,0 +1,27 @@
+import gdb
+
+
+class KstdStringPrinter(gdb.ValuePrinter):
+
+ def __init__(self, val):
+ self.__val = val
+
+ def to_string(self):
+ storage = self.__val["m_storage"]
+ storage_size = int(storage["m_size"])
+
+ if storage_size <= 0:
+ return '""'
+
+ data_pointer = storage["m_data"]
+ string_length = storage_size - 1
+
+ try:
+ if hasattr(data_pointer, "lazy_string"):
+ return data_pointer.lazy_string(encoding="utf-8", length=string_length)
+ return data_pointer.string(encoding="utf-8", length=string_length)
+ except gdb.error:
+ return "<unreadable memory>"
+
+ def display_hint(self):
+ return "string"
diff --git a/libs/kstd/gdb/units.py b/libs/kstd/gdb/units.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libs/kstd/gdb/units.py
diff --git a/libs/kstd/gdb/vector.py b/libs/kstd/gdb/vector.py
new file mode 100644
index 0000000..f11e064
--- /dev/null
+++ b/libs/kstd/gdb/vector.py
@@ -0,0 +1,41 @@
+import gdb
+
+
+class KstdVectorPrinter(gdb.ValuePrinter):
+ class Iterator:
+ def __init__(self, begin: gdb.Value, end: gdb.Value):
+ self._item = begin
+ self._end = end
+ self._count = 0
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ count = self._count
+ self._count = count + 1
+
+ if self._item == self._end:
+ raise StopIteration
+
+ element = self._item.dereference()
+ self._item = self._item + 1
+ return (f"[{count}]", element)
+
+ def __init__(self, val: gdb.Value):
+ self.__val = val
+ self.__size = int(val["m_size"])
+ self.__capacity = int(val["m_capacity"])
+ self.__data = val["m_data"]
+
+ def to_string(self):
+ return f"vector of length {self.__size}, capacity {self.__capacity}"
+
+ def children(self):
+ return self.Iterator(self.__data, self.__data + self.__size)
+
+ def display_hint(self):
+ return "array"
+
+ def num_children(self):
+ return self.__size
diff --git a/libs/kstd/include/kstd/bits/format_context.hpp b/libs/kstd/include/kstd/bits/format_context.hpp
deleted file mode 100644
index b5c7d21..0000000
--- a/libs/kstd/include/kstd/bits/format_context.hpp
+++ /dev/null
@@ -1,31 +0,0 @@
-#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP
-#define KSTD_BITS_FORMAT_CONTEXT_HPP
-
-// IWYU pragma: private, include <kstd/format>
-
-#include <string_view>
-
-namespace kstd
-{
-
- struct format_context
- {
- using writer_function = void(void *, std::string_view);
-
- writer_function * writer;
- void * user_data;
-
- constexpr auto push(std::string_view string) -> void
- {
- writer(user_data, string);
- }
-
- constexpr auto push(char character) -> void
- {
- writer(user_data, std::string_view(&character, 1));
- }
- };
-
-} // namespace kstd
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/format_specs.hpp b/libs/kstd/include/kstd/bits/format_specs.hpp
deleted file mode 100644
index 092a875..0000000
--- a/libs/kstd/include/kstd/bits/format_specs.hpp
+++ /dev/null
@@ -1,104 +0,0 @@
-#ifndef KSTD_BITS_FORMAT_SPECS_HPP
-#define KSTD_BITS_FORMAT_SPECS_HPP
-
-// IWYU pragma: private
-
-#include <cstddef>
-#include <iterator>
-#include <string_view>
-
-namespace kstd::bits
-{
-
- struct format_specs
- {
- std::size_t width{};
- char fill{' '};
- char type{};
- bool align_left{};
- bool sign_plus{};
- bool sign_space{};
- bool alternative_form{};
- bool zero_pad{};
- };
-
- constexpr auto parse_specs(std::string_view string, format_specs & specs) -> std::string_view
- {
- auto current = string.begin();
- auto end = string.end();
-
- if (current == end || *current != ':')
- {
- return {current, end};
- }
-
- std::advance(current, 1);
-
- if (current != end && std::next(current) != end && (*std::next(current) == '<' || *std::next(current) == '>'))
- {
- specs.fill = *current;
- specs.align_left = *std::next(current) == '<';
- std::advance(current, 2);
- }
- else if (current != end)
- {
- if (*current == '<')
- {
- specs.align_left = true;
- std::advance(current, 1);
- }
- else if (*current == '>')
- {
- specs.align_left = false;
- std::advance(current, 1);
- }
- }
-
- if (current != end)
- {
- if (*current == '+')
- {
- specs.sign_plus = true;
- std::advance(current, 1);
- }
- else if (*current == ' ')
- {
- specs.sign_space = true;
- std::advance(current, 1);
- }
- else if (*current == '-')
- {
- std::advance(current, 1);
- }
- }
-
- if (current != end && *current == '#')
- {
- specs.alternative_form = true;
- std::advance(current, 1);
- }
-
- if (current != end && *current == '0')
- {
- specs.zero_pad = true;
- std::advance(current, 1);
- }
-
- while (current != end && *current >= '0' && *current <= '9')
- {
- specs.width = specs.width * 10 + (*current - '0');
- std::advance(current, 1);
- }
-
- if (current != end && *current != '}')
- {
- specs.type = *current;
- std::advance(current, 1);
- }
-
- return {current, end};
- }
-
-} // namespace kstd::bits
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/format_string.hpp b/libs/kstd/include/kstd/bits/format_string.hpp
deleted file mode 100644
index 3a15bf0..0000000
--- a/libs/kstd/include/kstd/bits/format_string.hpp
+++ /dev/null
@@ -1,131 +0,0 @@
-#ifndef KSTD_BITS_FORMAT_STRING_HPP
-#define KSTD_BITS_FORMAT_STRING_HPP
-
-// IWYU pragma: private, include <kstd/format>
-
-#include <algorithm>
-#include <cstddef>
-#include <iterator>
-#include <string_view>
-
-namespace kstd
-{
-
- namespace bits
- {
- auto invalid_format_string(char const *) -> void;
-
- consteval auto validate_format_string(std::string_view string, std::size_t argument_count) -> void
- {
- auto next_automatic_index = 0uz;
- auto placeholder_count = 0uz;
- auto has_manual_index = false;
- auto has_automatic_index = false;
-
- auto current = string.begin();
- auto end = string.end();
-
- while (current != end)
- {
- if (*current == '{')
- {
- if (std::next(current) != end && *std::next(current) == '{')
- {
- std::advance(current, 2);
- continue;
- }
-
- std::advance(current, 1);
-
- auto index = 0uz;
- auto is_manual_index = false;
-
- if (current != end && *current >= '0' && *current <= '9')
- {
- is_manual_index = true;
- while (current != end && *current >= '0' && *current <= '9')
- {
- index = index * 10 + (*current - '0');
- std::advance(current, 1);
- }
- }
-
- if (is_manual_index)
- {
- placeholder_count = std::max(placeholder_count, index + 1);
- if (has_automatic_index)
- {
- invalid_format_string("Cannot mix automatic and manual indexing.");
- }
- has_manual_index = true;
- if (index >= argument_count)
- {
- invalid_format_string("Argument index out of range");
- }
- }
- else
- {
- if (has_manual_index)
- {
- invalid_format_string("Cannot mix automatic and manual indexing.");
- }
- has_automatic_index = true;
- ++placeholder_count;
- if (next_automatic_index >= argument_count)
- {
- invalid_format_string("Not enough arguments provided for format string.");
- }
- index = next_automatic_index++;
- }
-
- while (current != end && *current != '}')
- {
- std::advance(current, 1);
- }
-
- if (current == end)
- {
- invalid_format_string("Unexpected end of format string.");
- }
- std::advance(current, 1);
- }
- else if (*current == '}')
- {
- if (std::next(current) != end && *std::next(current) == '}')
- {
- std::advance(current, 2);
- continue;
- }
- invalid_format_string("Unexpected '}' in format string.");
- }
- else
- {
- std::advance(current, 1);
- }
- }
- if (argument_count < placeholder_count)
- {
- invalid_format_string("Not enough arguments provided for format string.");
- }
- else if (argument_count > placeholder_count)
- {
- invalid_format_string("Too many arguments provided for format string.");
- }
- }
- } // namespace bits
-
- template<typename... Args>
- struct format_string
- {
- std::string_view str;
-
- consteval format_string(char const * format)
- : str{format}
- {
- bits::validate_format_string(str, sizeof...(Args));
- }
- };
-
-} // namespace kstd
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp
deleted file mode 100644
index eadc0ef..0000000
--- a/libs/kstd/include/kstd/bits/formatter.hpp
+++ /dev/null
@@ -1,300 +0,0 @@
-#ifndef KSTD_BITS_FORMATTER_HPP
-#define KSTD_BITS_FORMATTER_HPP
-
-// IWYU pragma: private, include <kstd/format>
-
-#include <kstd/bits/format_context.hpp>
-#include <kstd/bits/format_specs.hpp>
-
-#include <array>
-#include <bit>
-#include <concepts>
-#include <cstddef>
-#include <cstdint>
-#include <iterator>
-#include <string_view>
-#include <type_traits>
-#include <utility>
-
-namespace kstd
-{
-
- template<typename T>
- struct formatter;
-
- template<std::integral T>
- struct formatter<T>
- {
- bits::format_specs specs{};
-
- constexpr auto parse(std::string_view context) -> std::string_view
- {
- return bits::parse_specs(context, specs);
- }
-
- auto format(T value, format_context & context) const -> void
- {
- enum struct base
- {
- bin = 2,
- oct = 8,
- dec = 10,
- hex = 16,
- };
-
- constexpr auto static maximum_digits = 80;
-
- using unsigned_T = std::make_unsigned_t<T>;
- auto absolute_value = static_cast<unsigned_T>(value);
- auto is_negative = false;
-
- if constexpr (std::is_signed_v<T>)
- {
- if (value < 0)
- {
- is_negative = true;
- absolute_value = 0 - value;
- }
- }
-
- auto const base = [type = specs.type] -> auto {
- switch (type)
- {
- case 'x':
- case 'X':
- case 'p':
- return base::hex;
- case 'b':
- case 'B':
- return base::bin;
- case 'o':
- return base::oct;
- default:
- return base::dec;
- }
- }();
-
- auto buffer = std::array<char, maximum_digits>{};
- auto digits = (specs.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef";
- auto current = buffer.rbegin();
-
- if (absolute_value == 0)
- {
- *current = '0';
- std::advance(current, 1);
- }
- else
- {
- while (absolute_value != 0)
- {
- *current = digits[absolute_value % std::to_underlying(base)];
- std::advance(current, 1);
- absolute_value /= std::to_underlying(base);
- }
- }
-
- auto prefix = std::array<char, 2>{'0', '\0'};
- auto prefix_length = 0uz;
- if (specs.alternative_form && value != 0)
- {
- switch (base)
- {
- case base::bin:
- prefix[1] = specs.type == 'B' ? 'B' : 'b';
- prefix_length = 2;
- break;
- case base::oct:
- prefix_length = 1;
- break;
- case base::hex:
- prefix[1] = specs.type == 'X' ? 'X' : 'x';
- prefix_length = 2;
- break;
- default:
- break;
- }
- }
-
- auto sign_character = '\0';
- if (is_negative)
- {
- sign_character = '-';
- }
- else if (specs.sign_plus)
- {
- sign_character = '+';
- }
- else if (specs.sign_space)
- {
- sign_character = ' ';
- }
-
- auto const content_length = static_cast<std::size_t>(std::distance(buffer.rbegin(), current));
- auto const total_length = content_length + prefix_length + (sign_character != '\0');
- auto const padding_length = (specs.width > total_length) ? (specs.width - total_length) : 0;
-
- if (!specs.align_left && !specs.zero_pad)
- {
- for (auto i = 0uz; i < padding_length; ++i)
- {
- context.push(specs.fill);
- }
- }
-
- if (sign_character)
- {
- context.push(sign_character);
- }
-
- if (prefix_length)
- {
- context.push({prefix.data(), prefix_length});
- }
-
- if (!specs.align_left && specs.zero_pad)
- {
- for (auto i = 0uz; i < padding_length; ++i)
- {
- context.push('0');
- }
- }
-
- context.push({current.base(), content_length});
-
- if (specs.align_left)
- {
- for (auto i = 0uz; i < padding_length; ++i)
- {
- context.push(specs.fill);
- }
- }
- }
- };
-
- template<typename T>
- struct formatter<T const *> : formatter<std::uintptr_t>
- {
- constexpr auto parse(std::string_view context) -> std::string_view
- {
- auto result = formatter<std::uintptr_t>::parse(context);
- if (!this->specs.type)
- {
- this->specs.type = 'p';
- this->specs.alternative_form = true;
- }
- return result;
- }
-
- auto format(T const * pointer, format_context & context) const -> void
- {
- formatter<std::uintptr_t>::format(std::bit_cast<std::uintptr_t>(pointer), context);
- }
- };
-
- template<typename T>
- struct formatter<T *> : formatter<T const *>
- {
- };
-
- template<>
- struct formatter<std::string_view>
- {
- bits::format_specs specs{};
-
- constexpr auto parse(std::string_view context) -> std::string_view
- {
- return bits::parse_specs(context, specs);
- }
-
- auto format(std::string_view string, format_context & context) const -> void
- {
- auto const content_length = string.size();
- auto const padding_length = (specs.width > content_length) ? (specs.width - content_length) : 0;
-
- if (!specs.align_left)
- {
- for (auto i = 0uz; i < padding_length; ++i)
- {
- context.push(specs.fill);
- }
- }
-
- context.push(string);
-
- if (specs.align_left)
- {
- for (auto i = 0uz; i < padding_length; ++i)
- {
- context.push(specs.fill);
- }
- }
- }
- };
-
- template<>
- struct formatter<char const *>
- {
- bits::format_specs specs{};
-
- constexpr auto parse(std::string_view context) -> std::string_view
- {
- return bits::parse_specs(context, specs);
- }
-
- auto format(char const * string, format_context & context) const -> void
- {
- if (string)
- {
- formatter<std::string_view>{specs}.format(string, context);
- }
- else
- {
- formatter<std::string_view>{specs}.format("(null)", context);
- }
- }
- };
-
- template<>
- struct formatter<char *> : formatter<char const *>
- {
- };
-
- struct format_arg
- {
- using formatting_function = std::string_view(void const *, std::string_view, format_context &);
-
- void const * value;
- formatting_function * format;
- };
-
- struct format_args
- {
- constexpr format_args(format_arg const * args, std::size_t number_of_args)
- : m_args(args)
- , m_number_of_args(number_of_args)
- {}
-
- [[nodiscard]] constexpr auto get(std::size_t index) const -> format_arg
- {
- if (index >= m_number_of_args)
- return {.value = nullptr, .format = nullptr};
- return m_args[index];
- }
-
- private:
- format_arg const * m_args;
- std::size_t m_number_of_args;
- };
-
- template<typename T>
- auto format_dispatcher(void const * value, std::string_view format_spec, format_context & context) -> std::string_view
- {
- auto formatter_for_T = formatter<T>{};
- auto const remainder = formatter_for_T.parse(format_spec);
- formatter_for_T.format(*static_cast<T const *>(value), context);
- return remainder;
- }
-
-} // namespace kstd
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/shared_ptr.hpp b/libs/kstd/include/kstd/bits/shared_ptr.hpp
deleted file mode 100644
index cfe5d18..0000000
--- a/libs/kstd/include/kstd/bits/shared_ptr.hpp
+++ /dev/null
@@ -1,258 +0,0 @@
-#ifndef KSTD_BITS_SHARED_PTR_HPP
-#define KSTD_BITS_SHARED_PTR_HPP
-
-#include <atomic>
-#include <cstddef>
-#include <utility>
-
-// IWYU pragma: private, include <kstd/memory>
-
-namespace kstd
-{
- /**
- * @brief Shared_pointer is a smart pointer that retains shared ownership of an object through a pointer. Several
- * shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of
- * the following happens: the last remaining shared_ptr owning the object is destroyed; the last remaining
- * shared_ptr owning the object is assigned another pointer via operator= or reset(). A
- * shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used
- * to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(),
- * the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count
- * reaches zero.
- *
- * @tparam T The type of the managed object.
- */
- template<typename T>
- struct shared_ptr
- {
- /**
- * @brief Constructor.
- *
- * @param pointer A pointer to an object to manage (default is nullptr).
- */
- explicit shared_ptr(T * pointer = nullptr)
- : pointer(pointer)
- , ref_count(new std::atomic<std::size_t>(pointer != nullptr ? 1 : 0))
- {
- // Nothing to do.
- }
-
- /**
- * @brief Copy constructor.
- *
- * @param other The shared_ptr to copy from.
- */
- shared_ptr(shared_ptr const & other)
- : pointer(other.pointer)
- , ref_count(other.ref_count)
- {
- if (pointer != nullptr)
- {
- ++(*ref_count);
- }
- }
-
- /**
- * @brief Move constructor.
- *
- * @param other The shared_ptr to move from.
- */
- shared_ptr(shared_ptr && other) noexcept
- : pointer(other.pointer)
- , ref_count(other.ref_count)
- {
- other.pointer = nullptr;
- other.ref_count = nullptr;
- }
-
- /**
- * @brief Copy assignment operator. Replaces the managed object with the one managed by r. Shares ownership of the
- * object managed by r. If r manages no object, *this manages no object too. Equivalent to
- * shared_ptr<T>(r).swap(*this).
- *
- * @param other Another smart pointer to share the ownership with.
- * @return Reference to this shared pointer.
- */
- auto operator=(shared_ptr const & other) -> shared_ptr &
- {
- if (this != &other)
- {
- cleanup();
- pointer = other.pointer;
- ref_count = other.ref_count;
-
- if (pointer != nullptr)
- {
- ++(*ref_count);
- }
- }
-
- return *this;
- }
-
- /**
- * @brief Move assignment operator. Move-assigns a shared_ptr from r. After the assignment, *this contains a copy of
- * the previous state of r, and r is empty. Equivalent to shared_ptr<T>(std::move(r)).swap(*this).
- *
- * @param other Another smart pointer to acquire the ownership from.
- * @return Reference to this shared pointer.
- */
- auto operator=(shared_ptr && other) noexcept -> shared_ptr &
- {
- if (this != &other)
- {
- cleanup();
- pointer = other.pointer;
- ref_count = other.ref_count;
- other.pointer = nullptr;
- other.ref_count = nullptr;
- }
-
- return *this;
- }
-
- /**
- * @brief Destructor. Cleans up resources if necessary.
- */
- ~shared_ptr()
- {
- cleanup();
- }
-
- /**
- * @brief Replaces the managed object.
- *
- * @param ptr Pointer to a new object to manage (default = nullptr).
- */
- void reset(T * ptr = nullptr)
- {
- cleanup();
- pointer = ptr;
- ref_count = new std::atomic<std::size_t>(ptr != nullptr ? 1 : 0);
- }
-
- /**
- * @brief Exchanges the stored pointer values and the ownerships of *this and r. Reference counts, if any, are not
- * adjusted.
- *
- * @param other The shared_ptr to swap with.
- */
- void swap(shared_ptr & other)
- {
- std::swap(pointer, other.pointer);
- std::swap(ref_count, other.ref_count);
- }
-
- /**
- * @brief Dereference operator. If get() is a null pointer, the behavior is undefined.
- *
- * @return Returns the object owned by *this, equivalent to *get().
- */
- auto operator*() const -> T &
- {
- return *pointer;
- }
-
- /**
- * @brief Member access operator.
- *
- * @return Returns a pointer to the object owned by *this, i.e. get().
- */
- auto operator->() const -> T *
- {
- return pointer;
- }
-
- /**
- * @brief Returns a pointer to the managed object or nullptr if no object is owned.
- *
- * @return Pointer to the managed object or nullptr if no object is owned.
- */
- auto get() const -> T *
- {
- return pointer;
- }
-
- /**
- * @brief Returns the number of different shared_ptr instances (*this included) managing the current object. If
- * there is no managed object, ​0​ is returned.
- *
- * @note Common use cases include comparison with ​0​. If use_count returns zero, the shared pointer is empty
- * and manages no objects (whether or not its stored pointer is nullptr). Comparison with 1. If use_count returns 1,
- * there are no other owners.
- *
- * @return The number of Shared_pointer instances managing the current object or ​0​ if there is no managed
- * object.
- */
- [[nodiscard]] auto use_count() const -> std::size_t
- {
- if (pointer != nullptr)
- {
- return *ref_count;
- }
-
- return 0;
- }
-
- /**
- * @brief Checks whether *this owns an object, i.e. whether get() != nullptr.
- *
- * @return true if *this owns an object, false otherwise.
- */
- explicit operator bool() const
- {
- return pointer != nullptr;
- }
-
- /**
- * @brief Defaulted three-way comparator operator.
- */
- auto operator<=>(shared_ptr const & other) const = default;
-
- private:
- /**
- * @brief Releases ownership and deletes the object if this was the last reference to the owned managed object.
- */
- auto cleanup() -> void
- {
- if (pointer != nullptr && ref_count != nullptr && --(*ref_count) == 0)
- {
- delete pointer;
- delete ref_count;
- }
- }
-
- T * pointer; ///< The managed object.
- std::atomic<std::size_t> * ref_count; ///< Reference count.
- };
-
- /**
- * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls
- * lhs.swap(rhs).
- *
- * @tparam T Type of the managed object.
- * @param lhs, rhs Smart pointers whose contents to swap.
- */
- template<typename T>
- auto swap(shared_ptr<T> & lhs, shared_ptr<T> & rhs) -> void
- {
- lhs.swap(rhs);
- }
-
- /**
- * @brief Constructs an object of type T and wraps it in a shared_ptr. Constructs a non-array type T. The
- * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is
- * not an array type. The function is equivalent to: shared_ptr<T>(new T(std::forward<Args>(args)...)).
- *
- * @tparam T Type of the managed object.
- * @tparam Args Argument types for T's constructor.
- * @param args List of arguments with which an instance of T will be constructed.
- * @returns Shared_pointer of an instance of type T.
- */
- template<typename T, typename... Args>
- auto make_shared(Args &&... args) -> shared_ptr<T>
- {
- return shared_ptr<T>(new T(std::forward<Args>(args)...));
- }
-} // namespace kstd
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format
deleted file mode 100644
index 0a1bcd1..0000000
--- a/libs/kstd/include/kstd/format
+++ /dev/null
@@ -1,8 +0,0 @@
-#ifndef KSTD_FORMAT_HPP
-#define KSTD_FORMAT_HPP
-
-#include "bits/format_context.hpp" // IWYU pragma: export
-#include "bits/format_string.hpp" // IWYU pragma: export
-#include "bits/formatter.hpp" // IWYU pragma: export
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/memory b/libs/kstd/include/kstd/memory
deleted file mode 100644
index cab2fba..0000000
--- a/libs/kstd/include/kstd/memory
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef KSTD_SHARED_POINTER_HPP
-#define KSTD_SHARED_POINTER_HPP
-
-#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/vector b/libs/kstd/include/kstd/vector
deleted file mode 100644
index 7568cb6..0000000
--- a/libs/kstd/include/kstd/vector
+++ /dev/null
@@ -1,569 +0,0 @@
-#ifndef KSTD_VECTOR_HPP
-#define KSTD_VECTOR_HPP
-
-#include <kstd/os/error.hpp>
-
-#include <algorithm>
-#include <cstddef>
-#include <initializer_list>
-
-namespace kstd
-{
- /**
- * @brief Custom vector implementation mirroring the std::vector to allow for the usage of STL functionality with our
- * custom memory management.
- *
- * @tparam T Element the vector instance should contain.
- */
- template<typename T>
- struct vector
- {
- using value_type = T; ///< Type of the elements contained in the container.
- using size_type = std::size_t; ///< Type of the size in the container.
- using reference = value_type &; ///< Type of reference to the elements.
- using const_reference = value_type const &; ///< Type of constant reference to the elements.
- using pointer = value_type *; ///< Type of pointer to the elements.
- using const_pointer = value_type const *; ///< Type of constant pointer to the elements.
-
- /**
- * @brief Default Constructor.
- */
- vector() = default;
-
- /**
- * @brief Constructs data with the given amount of elements containing the given value or alternatively the default
- * constructed value.
- *
- * @param n Amount of elements we want to create and set the given value for.
- * @param initial Inital value of all elements in the underlying data array.
- */
- explicit vector(size_type n, value_type initial = value_type{})
- : _size(n)
- , _capacity(n)
- , _data(new value_type[_capacity]{})
- {
- std::ranges::fill(*this, initial);
- }
-
- /**
- * @brief Constructs data by copying all element from the given exclusive range.
- *
- * @tparam InputIterator Template that should have atleast input iterator characteristics.
- * @param first Input iterator to the first element in the range we want to copy from.
- * @param last Input iterator to one past the last element in the range we want to copy from.
- */
- template<typename InputIterator>
- explicit vector(InputIterator first, InputIterator last)
- : _size(std::distance(first, last))
- , _capacity(std::distance(first, last))
- , _data(new value_type[_capacity]{})
- {
- std::ranges::copy(first, last, _data);
- }
-
- /**
- * @brief Construct data by copying all elements from the initializer list.
- *
- * @param initializer_list List we want to copy all elements from.
- */
- explicit vector(std::initializer_list<value_type> initializer_list)
- : _size(initializer_list.size())
- , _capacity(initializer_list.size())
- , _data(new value_type[_capacity]{})
- {
- std::ranges::copy(initializer_list, _data);
- }
-
- /**
- * @brief Copy constructor.
- *
- * @note Allocates underlying data container with the same capacity as vector we are copying from and copies all
- * elements from it.
- *
- * @param other Other instance of vector we want to copy the data from.
- */
- vector(vector<value_type> const & other)
- : _size(other._size)
- , _capacity(other._capacity)
- , _data(new value_type[_capacity]{})
- {
- std::ranges::copy(other, _data);
- }
-
- /**
- * @brief Copy assignment operator.
- *
- * @note Allocates underlying data container with the same capacity as vector we are copying from and copies all
- * elements from it.
- *
- * @param other Other instance of vector we want to copy the data from.
- * @return Newly created copy.
- */
- auto operator=(vector const & other) -> vector<value_type> &
- {
- delete[] _data;
- _size = other._size;
- _capacity = other._capacity;
- _data = new value_type[_capacity]{};
- std::ranges::copy(other, _data);
- return *this;
- }
-
- /**
- * @brief Destructor.
- */
- ~vector()
- {
- delete[] _data;
- }
-
- /**
- * @brief Amount of elements currently contained in this vector, will fill up until we have reached the capacity. If
- * that is the case the capacity is increased automatically.
- *
- * @return Current amount of elements.
- */
- [[nodiscard]] auto size() const -> size_type
- {
- return _size;
- }
-
- /**
- * @brief Amount of space the vector currently has, can be different than the size, because we allocate more than we
- * exactly require to decrease the amount of allocations and deallocation to improve speed.
- *
- * @return Current amount of space the vector has for elements.
- */
- [[nodiscard]] auto capacity() const -> size_type
- {
- return _capacity;
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Does not do any bounds checks use at() for that.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- auto operator[](size_type index) -> reference
- {
- return _data[index];
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Does not do any bounds checks use at() for that.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- auto operator[](size_type index) const -> const_reference
- {
- return _data[index];
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is halted.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- auto at(size_type index) -> reference
- {
- throw_if_out_of_range(index);
- return this->operator[](index);
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is halted.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- [[nodiscard]] auto at(size_type index) const -> const_reference
- {
- throw_if_out_of_range(index);
- return this->operator[](index);
- }
-
- /**
- * @brief Appends the given element value to the end of the container. The element is assigned through the
- * assignment operator of the template type. The value is forwarded to the constructor as
- * std::forward<U>(value), meaning it is either moved (rvalue) or copied (lvalue).
- *
- * @note If after the operation the new size() is greater than old capacity() a reallocation takes place,
- * in which case all iterators (including the end() iterator) and all references to the elements are invalidated.
- * Otherwise only the end() iterator is invalidated. Uses a forward reference for the actual value passed, which
- * allows the template method to be used by both lvalue and rvalues and compile a different implementation.
- *
- * @param value The value of the element to append.
- */
- template<class U>
- auto push_back(U && value) -> void
- {
- increase_capacity_if_full();
- _data[_size] = std::forward<U>(value);
- (void)_size++;
- }
-
- /**
- * @brief Appends a new element to the end of the container. The element is constructed through a constructor of the
- * template type. The arguments args... are forwarded to the constructor as std::forward<Args>(args)....
- *
- * If after the operation the new size() is greater than old capacity() a reallocation takes place, in which case
- * all iterators (including the end() iterator) and all references to the elements are invalidated. Otherwise only
- * the end() iterator is invalidated. Uses a forward reference for the actual value passed, which
- * allows the template method to be used by both lvalue and rvalues and compile a different implementation.
- *
- * @tparam Args
- * @param args Arguments to forward to the constructor of the element
- * @return value_type&
- */
- template<class... Args>
- auto emplace_back(Args &&... args) -> value_type &
- {
- increase_capacity_if_full();
- _data[_size] = value_type{std::forward<Args>(args)...};
- auto const index = _size++;
- return _data[index];
- }
-
- /**
- * @brief Removes the last element of the container. Calling pop_back on an empty container results in halting the
- * further execution. Iterators and references to the last element are invalidated. The end()
- * iterator is also invalidated.
- */
- auto pop_back() -> void
- {
- throw_if_empty();
- (void)_size--;
- }
-
- /**
- * @brief Returns an iterator to the first element of the vector.
- * If the vector is empty, the returned iterator will be equal to end().
- *
- * @return Iterator to the first element.
- */
- auto begin() noexcept -> pointer
- {
- return _data;
- }
-
- /**
- * @brief Returns an iterator to the first element of the vector.
- * If the vector is empty, the returned iterator will be equal to end().
- *
- * @return Iterator to the first element.
- */
- [[nodiscard]] auto begin() const noexcept -> const_pointer
- {
- return _data;
- }
-
- /**
- * @brief Returns an iterator to the first element of the vector.
- * If the vector is empty, the returned iterator will be equal to end().
- *
- * @return Iterator to the first element.
- */
- [[nodiscard]] auto cbegin() const noexcept -> const_pointer
- {
- return begin();
- }
-
- /**
- * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element
- * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend().
- *
- * @return Reverse iterator to the first element.
- */
- auto rbegin() noexcept -> pointer
- {
- return _data + _size - 1;
- }
-
- /**
- * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element
- * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend().
- *
- * @return Reverse iterator to the first element.
- */
- [[nodiscard]] auto rbegin() const noexcept -> const_pointer
- {
- return _data + _size - 1;
- }
-
- /**
- * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element
- * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend().
- *
- * @return Reverse iterator to the first element.
- */
- [[nodiscard]] auto crbegin() const noexcept -> const_pointer
- {
- return rbegin();
- }
-
- /**
- * @brief Returns an iterator to the element following the last element of the vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Iterator to the element following the last element.
- */
- auto end() noexcept -> pointer
- {
- return _data + _size;
- }
-
- /**
- * @brief Returns an iterator to the element following the last element of the vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Iterator to the element following the last element.
- */
- [[nodiscard]] auto end() const noexcept -> const_pointer
- {
- return _data + _size;
- }
-
- /**
- * @brief Returns an iterator to the element following the last element of the vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Iterator to the element following the last element.
- */
- [[nodiscard]] auto cend() const noexcept -> const_pointer
- {
- return end();
- }
-
- /**
- * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It
- * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Reverse iterator to the element following the last element.
- */
- auto rend() noexcept -> pointer
- {
- return _data + size() - 1;
- }
-
- /**
- * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It
- * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Reverse iterator to the element following the last element.
- */
- [[nodiscard]] auto rend() const noexcept -> const_pointer
- {
- return _data + size() - 1;
- }
-
- /**
- * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It
- * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Reverse iterator to the element following the last element.
- */
- [[nodiscard]] auto crend() const noexcept -> const_pointer
- {
- return rbegin();
- }
-
- /**
- * @brief Returns a pointer to the underlying array serving as element storage. The pointer is such that range
- * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not dereferenceable
- * in that case).
- *
- * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares equal
- * to the address of the first element.
- */
- auto data() -> pointer
- {
- return _data;
- }
-
- /**
- * @brief Returns a pointer to the underlying array serving as element storage. The pointer is such that range
- * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not dereferenceable
- * in that case).
- *
- * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares equal
- * to the address of the first element.
- */
- [[nodiscard]] auto data() const -> const_pointer
- {
- return _data;
- }
-
- /**
- * @brief Returns a reference to the first element in the container. Calling front on an empty container causes
- * undefined behavior.
- *
- * @return Reference to the first element.
- */
- auto front() -> reference
- {
- throw_if_empty();
- return *begin();
- }
-
- /**
- * @brief Returns a reference to the first element in the container. Calling front on an empty container causes
- * undefined behavior.
- *
- * @return Reference to the first element.
- */
- [[nodiscard]] auto front() const -> const_reference
- {
- throw_if_empty();
- return *begin();
- }
-
- /**
- * @brief Returns a reference to the last element in the container. Calling back on an empty container causes
- * undefined behavior.
- *
- * @return Reference to the last element.
- */
- auto back() -> reference
- {
- throw_if_empty();
- return *rbegin();
- }
-
- /**
- * @brief Returns a reference to the last element in the container. Calling back on an empty container causes
- * undefined behavior.
- *
- * @return Reference to the last element.
- */
- [[nodiscard]] auto back() const -> const_reference
- {
- throw_if_empty();
- return *rbegin();
- }
-
- /**
- * @brief Increase the capacity of the vector (the total number of elements that the vector can hold without
- * requiring reallocation) to a value that's greater or equal to new_cap. If new_cap is greater than the current
- * capacity(), new storage is allocated, otherwise the function does nothing.
- *
- * reserve() does not change the size of the vector.
- *
- * If new_cap is greater than capacity(), all iterators (including the end() iterator) and all references to the
- * elements are invalidated. Otherwise, no iterators or references are invalidated.
- *
- * After a call to reserve(), insertions will not trigger reallocation unless the insertion would make the size of
- * the vector greater than the value of capacity().
- *
- * @note Correctly using reserve() can prevent unnecessary reallocations, but inappropriate uses of reserve() (for
- * instance, calling it before every push_back() call) may actually increase the number of reallocations (by causing
- * the capacity to grow linearly rather than exponentially) and result in increased computational complexity and
- * decreased performance. For example, a function that receives an arbitrary vector by reference and appends
- * elements to it should usually not call reserve() on the vector, since it does not know of the vector's usage
- * characteristics.
- *
- * When inserting a range, the range version of insert() is generally preferable as it preserves the correct
- * capacity growth behavior, unlike reserve() followed by a series of push_back()s.
- *
- * reserve() cannot be used to reduce the capacity of the container; to that end shrink_to_fit() is provided.
- *
- * @param new_capacity New capacity of the vector, in number of elements
- */
- auto reserve(size_type new_capacity) -> void
- {
- if (new_capacity <= _capacity)
- {
- return;
- }
-
- _capacity = new_capacity;
- auto temp = new value_type[_capacity]{};
- std::ranges::copy(begin(), end(), temp);
- delete[] _data;
- _data = temp;
- }
-
- /**
- * @brief Requests the removal of unused capacity. Meaning it requests to reduce capacity() to size().
- *
- * If reallocation occurs, all iterators (including the end() iterator) and all references to the elements are
- * invalidated. If no reallocation occurs, no iterators or references are invalidated.
- */
- auto shrink_to_fit() -> void
- {
- if (_size == _capacity)
- {
- return;
- }
-
- _capacity = _size;
- auto temp = new value_type[_capacity]{};
- std::ranges::copy(begin(), end(), temp);
- delete[] _data;
- _data = temp;
- }
-
- /**
- * @brief Wheter there are currently any items this container or not.
- *
- * @return True if there are no elements, false if there are.
- */
- [[nodiscard]] auto empty() const -> bool
- {
- return _size <= 0;
- }
-
- private:
- /**
- * @brief Halts the execution of the application if the data container is currently empty.
- */
- auto throw_if_empty() const -> void
- {
- if (empty())
- {
- os::panic("[Vector] Attempted to access element of currently empty vector");
- }
- }
-
- auto throw_if_out_of_range(size_type index) const -> void
- {
- if (index >= _size)
- {
- os::panic("[Vector] Attempted to read element at invalid index");
- }
- }
-
- /**
- * @brief Increases the internal capacity to 1 if it was previously 0 and to * 2 after that, meaning exponential
- * growth. This is done to decrease the amount of single allocations done and because a power of 2 in memory size is
- * normally perferable for the cache.
- */
- auto increase_capacity_if_full() -> void
- {
- if (_size == _capacity)
- {
- reserve(_capacity == 0U ? 1U : _capacity * 2U);
- }
- }
-
- size_type _size = {}; ///< Amount of elements in the underlying data container
- size_type _capacity = {}; ///< Amount of space for elements in the underlying data container
- value_type * _data = {}; ///< Pointer to the first element in the underlying data container
- };
-
-} // namespace kstd
-
-#endif
diff --git a/libs/kstd/kstd/allocator b/libs/kstd/kstd/allocator
new file mode 100644
index 0000000..0de0e10
--- /dev/null
+++ b/libs/kstd/kstd/allocator
@@ -0,0 +1,64 @@
+#ifndef KSTD_ALLOCATOR_HPP
+#define KSTD_ALLOCATOR_HPP
+
+#include <cstddef>
+#include <new>
+#include <type_traits>
+
+#if __has_builtin(__builtin_operator_new) >= 201'802L
+#define KSTD_OPERATOR_NEW __builtin_operator_new
+#define KSTD_OPERATOR_DELETE __builtin_operator_delete
+#else
+#define KSTD_OPERATOR_NEW ::operator new
+#define KSTD_OPERATOR_DELETE ::operator delete
+#endif
+
+namespace kstd
+{
+
+ template<typename T>
+ struct allocator
+ {
+ using value_type = T;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using propagate_on_container_move_assignment = std::true_type;
+ using is_always_equal = std::true_type;
+
+ constexpr allocator() noexcept = default;
+
+ template<typename U>
+ constexpr allocator(allocator<U> const &) noexcept
+ {}
+
+ constexpr auto allocate(std::size_t n) -> T *
+ {
+ if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
+ {
+ return static_cast<T *>(KSTD_OPERATOR_NEW(n * sizeof(T), std::align_val_t{alignof(T)}));
+ }
+ return static_cast<T *>(KSTD_OPERATOR_NEW(n * sizeof(T)));
+ }
+
+ constexpr void deallocate(T * p, std::size_t n) noexcept
+ {
+ if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
+ {
+ KSTD_OPERATOR_DELETE(p, n * sizeof(T), std::align_val_t{alignof(T)});
+ }
+ KSTD_OPERATOR_DELETE(p, n * sizeof(T));
+ }
+ };
+
+ template<typename T, typename U>
+ constexpr auto operator==(allocator<T> const &, allocator<U> const &) noexcept -> bool
+ {
+ return true;
+ }
+
+} // namespace kstd
+
+#undef KSTD_OPERATOR_NEW
+#undef KSTD_OPERATOR_DELETE
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/asm_ptr b/libs/kstd/kstd/asm_ptr
index c06a8b5..c06a8b5 100644
--- a/libs/kstd/include/kstd/asm_ptr
+++ b/libs/kstd/kstd/asm_ptr
diff --git a/libs/kstd/kstd/bits/concepts.hpp b/libs/kstd/kstd/bits/concepts.hpp
new file mode 100644
index 0000000..74c25cb
--- /dev/null
+++ b/libs/kstd/kstd/bits/concepts.hpp
@@ -0,0 +1,15 @@
+#ifndef KSTD_BITS_CONCEPTS_HPP
+#define KSTD_BITS_CONCEPTS_HPP
+
+#include <concepts>
+#include <ranges>
+namespace kstd::bits
+{
+
+ template<typename RangeType, typename ValueType>
+ concept container_compatible_range =
+ std::ranges::input_range<RangeType> && std::convertible_to<std::ranges::range_reference_t<RangeType>, ValueType>;
+
+}
+
+#endif
diff --git a/libs/kstd/kstd/bits/flat_map.hpp b/libs/kstd/kstd/bits/flat_map.hpp
new file mode 100644
index 0000000..fe46203
--- /dev/null
+++ b/libs/kstd/kstd/bits/flat_map.hpp
@@ -0,0 +1,186 @@
+#ifndef KSTD_BITS_FLAT_MAP_HPP
+#define KSTD_BITS_FLAT_MAP_HPP
+
+#include <concepts>
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+namespace kstd::bits
+{
+
+ template<typename KeyType, typename MappedType>
+ struct flat_map_reference
+ {
+ using key_type = KeyType;
+ using mapped_type = MappedType;
+
+ constexpr flat_map_reference(key_type const & key, mapped_type & mapped)
+ : first{key}
+ , second{mapped}
+ {}
+
+ constexpr auto operator=(flat_map_reference const & other) const -> flat_map_reference const &
+ {
+ second = other.second;
+ return *this;
+ }
+
+ constexpr auto operator=(flat_map_reference && other) const -> flat_map_reference const &
+ {
+ second = std::move(other.second);
+ return *this;
+ }
+
+ template<typename TupleLikeType>
+ requires(std::tuple_size_v<std::remove_cvref_t<TupleLikeType>> == 2)
+ constexpr auto operator=(TupleLikeType && tuple) const -> flat_map_reference const &
+ {
+ second = std::forward<TupleLikeType>(tuple).second;
+ return *this;
+ }
+
+ template<std::size_t Index>
+ requires(Index >= 0 && Index <= 1)
+ [[nodiscard]] constexpr auto get() const noexcept -> decltype(auto)
+ {
+ if constexpr (Index == 0)
+ {
+ return (first);
+ }
+ else
+ {
+ return (second);
+ }
+ }
+
+ key_type const & first; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
+ mapped_type & second; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
+ };
+
+ template<typename Reference>
+ struct flat_map_pointer
+ {
+ Reference reference;
+
+ [[nodiscard]] constexpr auto operator->() noexcept -> Reference *
+ {
+ return std::addressof(reference);
+ }
+
+ [[nodiscard]] constexpr auto operator->() const noexcept -> Reference const *
+ {
+ return std::addressof(reference);
+ }
+ };
+
+ template<typename KeyType, typename MappedType, typename KeyIterator, typename MappedIterator>
+ struct flat_map_iterator
+ {
+ using iterator_category = std::random_access_iterator_tag;
+ using value_type = std::pair<KeyType, MappedType>;
+ using difference_type = std::ptrdiff_t;
+ using reference = flat_map_reference<KeyType, MappedType>;
+ using pointer = flat_map_pointer<reference>;
+
+ constexpr flat_map_iterator() = default;
+
+ constexpr flat_map_iterator(KeyIterator key_iterator, MappedIterator mapped_iterator)
+ : m_key_iterator{key_iterator}
+ , m_mapped_iterator{mapped_iterator}
+ {}
+
+ template<typename OtherMappedType, typename OtherKeyIterator, typename OtherMappedIterator>
+ requires(std::convertible_to<OtherKeyIterator, KeyIterator> &&
+ std::convertible_to<OtherMappedIterator, MappedIterator>)
+ constexpr flat_map_iterator(
+ flat_map_iterator<KeyType, OtherMappedType, OtherKeyIterator, OtherMappedIterator> const & other) noexcept
+ : m_key_iterator{other.m_key_iterator}
+ , m_mapped_iterator{other.m_mapped_iterator}
+ {}
+
+ [[nodiscard]] auto key_iterator() const noexcept -> KeyIterator
+ {
+ return m_key_iterator;
+ }
+
+ [[nodiscard]] constexpr auto operator*() const noexcept -> reference
+ {
+ return {*m_key_iterator, *m_mapped_iterator};
+ }
+
+ [[nodiscard]] constexpr auto operator->() const noexcept -> pointer
+ {
+ return {
+ {*m_key_iterator, *m_mapped_iterator}
+ };
+ }
+
+ constexpr auto operator++() noexcept -> flat_map_iterator &
+ {
+ ++m_key_iterator;
+ ++m_mapped_iterator;
+ return *this;
+ }
+
+ constexpr auto operator++(int) noexcept -> flat_map_iterator
+ {
+ auto copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+ constexpr auto operator--() noexcept -> flat_map_iterator &
+ {
+ --m_key_iterator;
+ --m_mapped_iterator;
+ return *this;
+ }
+
+ constexpr auto operator--(int) noexcept -> flat_map_iterator
+ {
+ auto copy = *this;
+ --(*this);
+ return copy;
+ }
+
+ [[nodiscard]] constexpr auto operator+(difference_type offset) const noexcept -> flat_map_iterator
+ {
+ return {m_key_iterator + offset, m_mapped_iterator + offset};
+ }
+
+ [[nodiscard]] constexpr auto operator-(flat_map_iterator const & other) const noexcept -> difference_type
+ {
+ return m_key_iterator - other.m_key_iterator;
+ }
+
+ [[nodiscard]] constexpr auto operator<=>(flat_map_iterator const & other) const noexcept = default;
+
+ private:
+ KeyIterator m_key_iterator{};
+ MappedIterator m_mapped_iterator{};
+ };
+
+} // namespace kstd::bits
+
+template<typename K, typename M>
+struct std::tuple_size<kstd::bits::flat_map_reference<K, M>> : std::integral_constant<std::size_t, 2>
+{
+};
+
+template<typename K, typename M>
+struct std::tuple_element<0, kstd::bits::flat_map_reference<K, M>>
+{
+ using type = K const &;
+};
+
+template<typename K, typename M>
+struct std::tuple_element<1, kstd::bits::flat_map_reference<K, M>>
+{
+ using type = M &;
+};
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/arg.hpp b/libs/kstd/kstd/bits/format/arg.hpp
new file mode 100644
index 0000000..e65b26f
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/arg.hpp
@@ -0,0 +1,75 @@
+#ifndef KSTD_BITS_FORMAT_ARG_HPP
+#define KSTD_BITS_FORMAT_ARG_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/fwd.hpp>
+
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+ enum struct arg_type : std::uint8_t
+ {
+ none,
+ boolean,
+ character,
+ integer,
+ unsigned_integer,
+ string_view,
+ c_string,
+ pointer,
+ user_defined,
+ };
+ } // namespace bits::format
+
+ struct format_arg
+ {
+ bits::format::arg_type type{};
+ union
+ {
+ bool boolean;
+ char character;
+ std::int64_t integer;
+ std::uint64_t unsigned_integer;
+ std::string_view string_view;
+ char const * c_string;
+ void const * pointer;
+ struct
+ {
+ void const * pointer;
+ auto (*format)(void const * value, format_parse_context & parse_context, format_context & context) -> void;
+ } user_defined;
+ } value{};
+ };
+
+ namespace bits::format
+ {
+ constexpr auto extrat_dynamic_width(format_arg const & arg) -> std::size_t
+ {
+ if (arg.type == arg_type::unsigned_integer)
+ {
+ return static_cast<std::size_t>(arg.value.unsigned_integer);
+ }
+ else if (arg.type == arg_type::integer)
+ {
+ if (arg.value.integer < 0)
+ {
+ error("Dynamic width cannont be negative.");
+ }
+ return static_cast<std::size_t>(arg.value.integer);
+ }
+ error("Dynamic width argument is not an integral value.");
+ return 0;
+ }
+ } // namespace bits::format
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/args.hpp b/libs/kstd/kstd/bits/format/args.hpp
new file mode 100644
index 0000000..e8e3114
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/args.hpp
@@ -0,0 +1,160 @@
+#ifndef KSTD_BITS_FORMAT_ARGS_HPP
+#define KSTD_BITS_FORMAT_ARGS_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/arg.hpp>
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/fwd.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <array>
+#include <concepts>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string_view>
+#include <type_traits>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+
+ template<typename ValueType>
+ auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context)
+ -> void
+ {
+ auto typed_value_pointer = static_cast<ValueType const *>(value_pointer);
+ auto fmt = formatter<std::remove_cvref_t<ValueType>>{};
+ auto const it = fmt.parse(parse_context);
+ parse_context.advance_to(it);
+ fmt.format(*typed_value_pointer, context);
+ }
+
+ template<typename ValueType>
+ constexpr auto determine_arg_type() -> arg_type
+ {
+ using decay_type = std::remove_cvref_t<ValueType>;
+ if constexpr (std::same_as<decay_type, bool>)
+ {
+ return arg_type::boolean;
+ }
+ else if constexpr (std::same_as<decay_type, char>)
+ {
+ return arg_type::character;
+ }
+ else if constexpr (std::integral<decay_type> && std::is_signed_v<decay_type>)
+ {
+ return arg_type::integer;
+ }
+ else if constexpr (std::integral<decay_type> && std::is_unsigned_v<decay_type>)
+ {
+ return arg_type::unsigned_integer;
+ }
+ else if constexpr (std::same_as<decay_type, std::string_view>)
+ {
+ return arg_type::string_view;
+ }
+ else if constexpr (std::same_as<std::decay_t<decay_type>, char *> ||
+ std::same_as<std::decay_t<decay_type>, char const *>)
+ {
+ return arg_type::c_string;
+ }
+ else if constexpr (std::is_pointer_v<decay_type> || std::same_as<decay_type, std::nullptr_t>)
+ {
+ if constexpr (std::same_as<decay_type, char *> || std::same_as<decay_type, char const *>)
+ {
+ return arg_type::user_defined;
+ }
+ else
+ {
+ return arg_type::pointer;
+ }
+ }
+ else
+ {
+ return arg_type::user_defined;
+ }
+ }
+
+ template<typename ValueType>
+ constexpr auto make_single_arg(ValueType const & value) -> format_arg
+ {
+ auto result = format_arg{};
+ constexpr auto type = determine_arg_type<ValueType>();
+ result.type = type;
+
+ if constexpr (type == arg_type::boolean)
+ {
+ result.value.boolean = value;
+ }
+ else if constexpr (type == arg_type::character)
+ {
+ result.value.character = value;
+ }
+ else if constexpr (type == arg_type::integer)
+ {
+ result.value.integer = static_cast<std::int64_t>(value);
+ }
+ else if constexpr (type == arg_type::unsigned_integer)
+ {
+ result.value.unsigned_integer = static_cast<std::uint64_t>(value);
+ }
+ else if constexpr (type == arg_type::string_view)
+ {
+ result.value.string_view = value;
+ }
+ else if constexpr (type == arg_type::c_string)
+ {
+ result.value.c_string = value;
+ }
+ else if constexpr (type == arg_type::pointer)
+ {
+ if constexpr (std::same_as<std::remove_cvref_t<ValueType>, std::nullptr_t>)
+ {
+ result.value.pointer = nullptr;
+ }
+ else
+ {
+ result.value.pointer = static_cast<void const *>(value);
+ }
+ }
+ else
+ {
+ result.value.user_defined.pointer = &value;
+ result.value.user_defined.format = format_trampoline<ValueType>;
+ }
+ return result;
+ }
+
+ } // namespace bits::format
+
+ using format_args = std::span<format_arg const>;
+
+ template<std::size_t Count>
+ struct format_arg_store
+ {
+ std::array<format_arg, Count> args{};
+ };
+
+ template<typename... Arguments>
+ [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store<sizeof...(Arguments)>
+ {
+ using namespace bits::format;
+
+ if constexpr (sizeof...(Arguments) == 0)
+ {
+ return format_arg_store<0>{};
+ }
+ else
+ {
+ return format_arg_store<sizeof...(Arguments)>{
+ std::array<format_arg, sizeof...(Arguments)>{make_single_arg(args)...}};
+ }
+ }
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/context.hpp b/libs/kstd/kstd/bits/format/context.hpp
new file mode 100644
index 0000000..c166ba9
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/context.hpp
@@ -0,0 +1,65 @@
+#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP
+#define KSTD_BITS_FORMAT_CONTEXT_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/arg.hpp>
+#include <kstd/bits/format/output_buffer.hpp>
+#include <kstd/os/error.hpp>
+
+#include <concepts>
+#include <cstddef>
+#include <span>
+#include <string_view>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+ template<typename T>
+ constexpr auto inline is_width_v = std::integral<T> && //
+ !std::same_as<bool, T> && //
+ !std::same_as<char, T> && //
+ !std::same_as<wchar_t, T> && //
+ !std::same_as<char8_t, T> && //
+ !std::same_as<char16_t, T> && //
+ !std::same_as<char32_t, T>;
+ }
+
+ struct format_context
+ {
+ using format_args = std::span<format_arg const>;
+ format_args args{};
+
+ format_context(bits::format::output_buffer & buffer, format_args args)
+ : args{args}
+ , m_buffer{&buffer}
+ {}
+
+ [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const &
+ {
+ if (id >= args.size())
+ {
+ kstd::os::panic("[kstd:format] argument index out of range!");
+ }
+ return args[id];
+ }
+
+ constexpr auto push(std::string_view string) -> void
+ {
+ m_buffer->push(string);
+ }
+
+ constexpr auto push(char character) -> void
+ {
+ m_buffer->push(character);
+ }
+
+ private:
+ bits::format::output_buffer * m_buffer;
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/error.hpp b/libs/kstd/kstd/bits/format/error.hpp
new file mode 100644
index 0000000..c0cb53d
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/error.hpp
@@ -0,0 +1,24 @@
+#ifndef KSTD_BITS_FORMAT_ERROR_HPP
+#define KSTD_BITS_FORMAT_ERROR_HPP
+
+#include <kstd/os/error.hpp>
+
+namespace kstd::bits::format
+{
+
+ constexpr auto error(char const * message) -> void
+ {
+ if consteval
+ {
+ extern void compile_time_format_error_triggered(char const *);
+ compile_time_format_error_triggered(message);
+ }
+ else
+ {
+ kstd::os::panic("Error while formatting a string.");
+ }
+ }
+
+} // namespace kstd::bits::format
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter.hpp b/libs/kstd/kstd/bits/format/formatter.hpp
new file mode 100644
index 0000000..eb28829
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter.hpp
@@ -0,0 +1,92 @@
+#ifndef KSTD_BITS_FORMATTER_HPP
+#define KSTD_BITS_FORMATTER_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <algorithm>
+#include <iterator>
+#include <string_view>
+
+namespace kstd
+{
+
+ template<typename>
+ struct formatter
+ {
+ formatter() = delete;
+ formatter(formatter const &) = delete;
+ auto operator=(formatter const &) -> formatter & = delete;
+ };
+
+ template<typename ValueType>
+ struct range_formatter
+ {
+ constexpr auto set_separator(std::string_view sep) -> void
+ {
+ m_separator = sep;
+ }
+
+ constexpr auto set_brackets(std::string_view opening, std::string_view closing)
+ {
+ m_prefix = opening;
+ m_suffix = closing;
+ }
+
+ constexpr auto parse(format_parse_context & context)
+ {
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (it != end && *it == 'n')
+ {
+ set_brackets("", "");
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == ':')
+ {
+ std::advance(it, 1);
+ context.advance_to(it);
+ it = m_inner_formatter.parse(context);
+ }
+
+ if (it != end && *it != '}')
+ {
+ bits::format::error("Invalid formate specifier for range");
+ }
+
+ return it;
+ }
+
+ template<typename Range>
+ auto format(Range const & range, format_context & context)
+ {
+ context.push(m_prefix);
+
+ auto is_first = true;
+ std::ranges::for_each(range, [&](auto const & element) {
+ if (!is_first)
+ {
+ context.push(m_separator);
+ }
+ m_inner_formatter.format(element, context);
+ is_first = false;
+ });
+
+ context.push(m_suffix);
+ }
+
+ private:
+ kstd::formatter<ValueType> m_inner_formatter{};
+ std::string_view m_separator{", "};
+ std::string_view m_prefix{"["};
+ std::string_view m_suffix{"]"};
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/bool.hpp b/libs/kstd/kstd/bits/format/formatter/bool.hpp
new file mode 100644
index 0000000..cc8d190
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/bool.hpp
@@ -0,0 +1,83 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+#include <kstd/bits/format/specifiers.hpp>
+
+#include <iterator>
+#include <string_view>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<bool>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (it != end && *it != '}')
+ {
+ if (*it == 's')
+ {
+ specifiers.type = *it;
+ std::advance(it, 1);
+ }
+ else
+ {
+ bits::format::error("Invalid type specifier for bool.");
+ }
+ }
+
+ if (it != end && *it != '}')
+ {
+ bits::format::error("Missing terminating '}' in format string.");
+ }
+
+ return it;
+ }
+
+ auto format(bool value, format_context & context) const -> void
+ {
+ auto const text = value ? std::string_view{"true"} : std::string_view{"false"};
+ auto final_width = 0uz;
+
+ if (specifiers.mode == bits::format::width_mode::static_value)
+ {
+ final_width = specifiers.width_value;
+ }
+ else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id)
+ {
+ auto const & arg = context.arg(specifiers.width_value);
+ final_width = bits::format::extrat_dynamic_width(arg);
+ }
+
+ auto padding = bits::format::calculate_format_padding(final_width, text.size(), specifiers.align,
+ bits::format::alignment::left);
+
+ for (auto i = 0uz; i < padding.left; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+
+ context.push(text);
+
+ for (auto i = 0uz; i < padding.right; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/byte.hpp b/libs/kstd/kstd/bits/format/formatter/byte.hpp
new file mode 100644
index 0000000..cc8aece
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/byte.hpp
@@ -0,0 +1,23 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/formatter/integral.hpp>
+
+#include <cstddef>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<std::byte> : formatter<int>
+ {
+ auto format(std::byte value, format_context & context) const -> void
+ {
+ formatter<int>::format(static_cast<int>(value), context);
+ }
+ };
+
+} // namespace kstd
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/char.hpp b/libs/kstd/kstd/bits/format/formatter/char.hpp
new file mode 100644
index 0000000..92489a1
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/char.hpp
@@ -0,0 +1,94 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/formatter/integral.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+#include <kstd/bits/format/specifiers.hpp>
+
+#include <iterator>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<char> : formatter<unsigned char>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (specifiers.alternative_form || specifiers.zero_pad || specifiers.sign != bits::format::sign_mode::none)
+ {
+ bits::format::error("Invalid format specifiers for 'char'");
+ }
+
+ if (it != end && *it != '}')
+ {
+ if (*it == 'c' || *it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X')
+ {
+ specifiers.type = *it;
+ std::advance(it, 1);
+ }
+ else
+ {
+ bits::format::error("Invalid type specifier for char.");
+ }
+ }
+
+ if (it != end && *it != '}')
+ {
+ bits::format::error("Missing terminating '}' in format string.");
+ }
+
+ return it;
+ }
+
+ auto format(char value, format_context & context) const -> void
+ {
+ if (specifiers.type == '\0' || specifiers.type == 'c')
+ {
+ auto final_width = 0uz;
+
+ if (specifiers.mode == bits::format::width_mode::static_value)
+ {
+ final_width = specifiers.width_value;
+ }
+ else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id)
+ {
+ auto const & arg = context.arg(specifiers.width_value);
+ final_width = bits::format::extrat_dynamic_width(arg);
+ }
+
+ auto padding =
+ bits::format::calculate_format_padding(final_width, 1, specifiers.align, bits::format::alignment::left);
+
+ for (auto i = 0uz; i < padding.left; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+
+ context.push(value);
+
+ for (auto i = 0uz; i < padding.right; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+ }
+ else
+ {
+ formatter<unsigned char>::format(static_cast<unsigned char>(value), context);
+ }
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/kstd/bits/format/formatter/cstring.hpp
new file mode 100644
index 0000000..553c8ca
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/cstring.hpp
@@ -0,0 +1,29 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/formatter/string_view.hpp>
+
+#include <string_view>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<char const *> : formatter<std::string_view>
+ {
+ auto format(char const * string, format_context & context) const -> void
+ {
+ formatter<std::string_view>::format(string ? std::string_view{string} : "(null)", context);
+ }
+ };
+
+ template<>
+ struct formatter<char *> : formatter<char const *>
+ {
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/integral.hpp b/libs/kstd/kstd/bits/format/formatter/integral.hpp
new file mode 100644
index 0000000..d17dc95
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/integral.hpp
@@ -0,0 +1,204 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+#include <kstd/bits/format/specifiers.hpp>
+
+#include <array>
+#include <concepts>
+#include <cstddef>
+#include <iterator>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+namespace kstd
+{
+
+ template<std::integral T>
+ struct formatter<T>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto static maximum_digits = 80;
+
+ enum struct base
+ {
+ bin = 2,
+ oct = 8,
+ dec = 10,
+ hex = 16,
+ };
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (it != end && *it != '}')
+ {
+ if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p')
+ {
+ specifiers.type = *it;
+ std::advance(it, 1);
+ }
+ else
+ {
+ bits::format::error("Invalid type specifier for integral type.");
+ }
+ }
+
+ if (it != end && *it != '}')
+ {
+ bits::format::error("Missing terminating '}' in format string.");
+ }
+
+ return it;
+ }
+
+ auto format(T value, format_context & context) const -> void
+ {
+ auto final_width = 0uz;
+ if (specifiers.mode == bits::format::width_mode::static_value)
+ {
+ final_width = specifiers.width_value;
+ }
+ else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id)
+ {
+ auto const & arg = context.arg(specifiers.width_value);
+ final_width = bits::format::extrat_dynamic_width(arg);
+ }
+
+ using unsigned_T = std::make_unsigned_t<T>;
+ auto absolute_value = static_cast<unsigned_T>(value);
+ auto is_negative = false;
+
+ if constexpr (std::is_signed_v<T>)
+ {
+ if (value < 0)
+ {
+ is_negative = true;
+ absolute_value = 0 - static_cast<unsigned_T>(value);
+ }
+ }
+
+ auto const base = [type = specifiers.type] -> auto {
+ switch (type)
+ {
+ case 'x':
+ case 'X':
+ case 'p':
+ return base::hex;
+ case 'b':
+ case 'B':
+ return base::bin;
+ case 'o':
+ return base::oct;
+ default:
+ return base::dec;
+ }
+ }();
+
+ auto buffer = std::array<char, maximum_digits>{};
+ auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef";
+ auto current = buffer.rbegin();
+
+ if (absolute_value == 0)
+ {
+ *current = '0';
+ std::advance(current, 1);
+ }
+ else
+ {
+ while (absolute_value != 0)
+ {
+ *current = digits[absolute_value % std::to_underlying(base)];
+ std::advance(current, 1);
+ absolute_value /= std::to_underlying(base);
+ }
+ }
+
+ auto content_length = static_cast<std::size_t>(std::distance(buffer.rbegin(), current));
+ auto prefix = std::array<char, 2>{'0', '\0'};
+ auto prefix_length = 0uz;
+ if (specifiers.alternative_form)
+ {
+ switch (base)
+ {
+ case base::bin:
+ prefix[1] = specifiers.type == 'B' ? 'B' : 'b';
+ prefix_length = 2;
+ break;
+ case base::oct:
+ prefix_length = 1;
+ break;
+ case base::hex:
+ prefix[1] = specifiers.type == 'X' ? 'X' : 'x';
+ prefix_length = 2;
+ break;
+ default:
+ break;
+ }
+ }
+
+ auto sign_character = '\0';
+ if (is_negative)
+ {
+ sign_character = '-';
+ }
+ else if (specifiers.sign == bits::format::sign_mode::plus)
+ {
+ sign_character = '+';
+ }
+ else if (specifiers.sign == bits::format::sign_mode::space)
+ {
+ sign_character = ' ';
+ }
+
+ auto const total_length = content_length + prefix_length + (sign_character != '\0');
+ auto const padding = bits::format::calculate_format_padding(final_width, total_length, specifiers.align,
+ bits::format::alignment::right);
+ auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::format::alignment::none);
+
+ if (!effective_zero_pad)
+ {
+ for (auto i = 0uz; i < padding.left; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+ }
+
+ if (sign_character != '\0')
+ {
+ context.push(sign_character);
+ }
+ if (prefix_length > 0)
+ {
+ context.push(std::string_view{prefix.data(), prefix_length});
+ }
+
+ if (effective_zero_pad)
+ {
+ for (auto i = 0uz; i < padding.left; ++i)
+ {
+ context.push('0');
+ }
+ }
+
+ context.push(std::string_view{current.base(), content_length});
+
+ for (auto i = 0uz; i < padding.right; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/ordering.hpp b/libs/kstd/kstd/bits/format/formatter/ordering.hpp
new file mode 100644
index 0000000..7832226
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/ordering.hpp
@@ -0,0 +1,111 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+#include <kstd/bits/format/specifiers.hpp>
+
+#include <compare>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<std::strong_ordering>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+ return context.begin();
+ }
+
+ auto format(std::strong_ordering value, format_context & context) const -> void
+ {
+ if (value == std::strong_ordering::equal)
+ {
+ return context.push(specifiers.alternative_form ? "==" : "equal");
+ }
+ else if (value == std::strong_ordering::equivalent)
+ {
+ return context.push(specifiers.alternative_form ? "==" : "equivalent");
+ }
+ else if (value == std::strong_ordering::greater)
+ {
+ return context.push(specifiers.alternative_form ? ">" : "greater");
+ }
+ else if (value == std::strong_ordering::less)
+ {
+ return context.push(specifiers.alternative_form ? "<" : "less");
+ }
+ kstd::os::panic("[kstd:format] Invalid strong ordering value!");
+ }
+ };
+
+ template<>
+ struct formatter<std::weak_ordering>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+ return context.begin();
+ }
+
+ auto format(std::weak_ordering value, format_context & context) const -> void
+ {
+ if (value == std::weak_ordering::equivalent)
+ {
+ return context.push(specifiers.alternative_form ? "==" : "equivalent");
+ }
+ else if (value == std::weak_ordering::greater)
+ {
+ return context.push(specifiers.alternative_form ? ">" : "greater");
+ }
+ else if (value == std::weak_ordering::less)
+ {
+ return context.push(specifiers.alternative_form ? "<" : "less");
+ }
+ kstd::os::panic("[kstd:format] Invalid weak ordering value!");
+ }
+ };
+
+ template<>
+ struct formatter<std::partial_ordering>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+ return context.begin();
+ }
+
+ auto format(std::partial_ordering value, format_context & context) const -> void
+ {
+ if (value == std::partial_ordering::equivalent)
+ {
+ return context.push(specifiers.alternative_form ? "==" : "equivalent");
+ }
+ else if (value == std::partial_ordering::greater)
+ {
+ return context.push(specifiers.alternative_form ? ">" : "greater");
+ }
+ else if (value == std::partial_ordering::less)
+ {
+ return context.push(specifiers.alternative_form ? "<" : "less");
+ }
+ else if (value == std::partial_ordering::unordered)
+ {
+ return context.push(specifiers.alternative_form ? "<=>" : "unordered");
+ }
+ kstd::os::panic("[kstd:format] Invalid partial ordering value!");
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/pointer.hpp b/libs/kstd/kstd/bits/format/formatter/pointer.hpp
new file mode 100644
index 0000000..15f9a5b
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/pointer.hpp
@@ -0,0 +1,42 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/formatter/integral.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+#include <kstd/bits/format/specifiers.hpp>
+
+#include <bit>
+#include <cstdint>
+
+namespace kstd
+{
+
+ template<typename T>
+ struct formatter<T const *> : formatter<std::uintptr_t>
+ {
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ auto result = formatter<std::uintptr_t>::parse(context);
+ if (!this->specifiers.type)
+ {
+ this->specifiers.type = 'p';
+ this->specifiers.alternative_form = true;
+ }
+ return result;
+ }
+
+ auto format(T const * pointer, format_context & context) const -> void
+ {
+ formatter<std::uintptr_t>::format(std::bit_cast<std::uintptr_t>(pointer), context);
+ }
+ };
+
+ template<typename T>
+ struct formatter<T *> : formatter<T const *>
+ {
+ };
+
+} // namespace kstd
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/range.hpp b/libs/kstd/kstd/bits/format/formatter/range.hpp
new file mode 100644
index 0000000..05af06f
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/range.hpp
@@ -0,0 +1,32 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP
+
+#include <kstd/bits/format/formatter.hpp>
+
+#include <concepts>
+#include <ranges>
+#include <string_view>
+#include <type_traits>
+
+namespace kstd
+{
+ namespace bits::format
+ {
+ template<typename T>
+ concept iterable = requires(T const & t) {
+ t.begin();
+ t.end();
+ };
+
+ template<typename T>
+ concept formattable_range = iterable<T> && !std::same_as<std::remove_cvref_t<T>, std::string_view> &&
+ !std::same_as<std::decay_t<T>, char *> && !std::same_as<std::decay_t<T>, char const *>;
+ } // namespace bits::format
+
+ template<bits::format::formattable_range Range>
+ struct formatter<Range> : range_formatter<std::ranges::range_value_t<Range>>
+ {
+ };
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/string_view.hpp b/libs/kstd/kstd/bits/format/formatter/string_view.hpp
new file mode 100644
index 0000000..7d74579
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/string_view.hpp
@@ -0,0 +1,41 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <string_view>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<std::string_view>
+ {
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ auto it = context.begin();
+
+ if (it != context.end() && *it == 's')
+ {
+ ++it;
+ }
+
+ if (it != context.end() && *it != '}')
+ {
+ bits::format::error("Invalid specifier for string_view.");
+ }
+ return it;
+ }
+
+ auto format(std::string_view const & string, format_context & context) const -> void
+ {
+ context.push(string);
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/fwd.hpp b/libs/kstd/kstd/bits/format/fwd.hpp
new file mode 100644
index 0000000..6caedae
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/fwd.hpp
@@ -0,0 +1,23 @@
+#ifndef KSTD_BITS_FORMAT_FWD_HPP
+#define KSTD_BITS_FORMAT_FWD_HPP
+
+// IWYU pragma: private
+
+#include <cstddef>
+
+namespace kstd
+{
+
+ struct format_parse_context;
+ struct format_context;
+ struct format_arg;
+
+ template<typename>
+ struct formatter;
+
+ template<std::size_t>
+ struct format_arg_store;
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/output_buffer.hpp b/libs/kstd/kstd/bits/format/output_buffer.hpp
new file mode 100644
index 0000000..fd7a2b4
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/output_buffer.hpp
@@ -0,0 +1,32 @@
+#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP
+#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <string_view>
+
+namespace kstd::bits::format
+{
+
+ //! An abstract interface for formatted output buffers.
+ //!
+ //! This interface is intended to be use for functions dealing with string formatting, like the print and the format
+ //! family.
+ struct output_buffer
+ {
+ virtual ~output_buffer() = default;
+
+ //! Push a text segment into the buffer.
+ //!
+ //! @param text The text segment to push.
+ virtual auto push(std::string_view text) -> void = 0;
+
+ //! Push a single character into the buffer.
+ //!
+ //! @param character The character to push into the buffer.
+ virtual auto push(char character) -> void = 0;
+ };
+
+} // namespace kstd::bits::format
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/parse_context.hpp b/libs/kstd/kstd/bits/format/parse_context.hpp
new file mode 100644
index 0000000..cab8d72
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/parse_context.hpp
@@ -0,0 +1,109 @@
+#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP
+#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/error.hpp>
+
+#include <cstddef>
+#include <string_view>
+
+namespace kstd
+{
+
+ struct format_parse_context
+ {
+ using iterator = std::string_view::const_iterator;
+
+ constexpr format_parse_context(std::string_view format, std::size_t argument_count,
+ bool const * is_integral = nullptr)
+ : m_current{format.begin()}
+ , m_end{format.end()}
+ , m_argument_count{argument_count}
+ , m_is_integral{is_integral}
+ {}
+
+ [[nodiscard]] constexpr auto begin() const -> iterator
+ {
+ return m_current;
+ }
+
+ [[nodiscard]] constexpr auto end() const -> iterator
+ {
+ return m_end;
+ }
+
+ constexpr auto advance_to(iterator position) -> void
+ {
+ m_current = position;
+ }
+
+ constexpr auto next_arg_id() -> std::size_t
+ {
+ if (m_mode == index_mode::manual)
+ {
+ bits::format::error("Cannot mix automatic and manual indexing.");
+ }
+
+ m_mode = index_mode::automatic;
+
+ if (m_next_argument_id >= m_argument_count)
+ {
+ bits::format::error("Argument index out of bounds.");
+ }
+ return m_next_argument_id++;
+ }
+
+ constexpr auto check_arg_id(std::size_t index) -> void
+ {
+ if (m_mode == index_mode::automatic)
+ {
+ bits::format::error("Cannot mix automatic and manual indexing.");
+ }
+
+ m_mode = index_mode::manual;
+
+ if (index >= m_argument_count)
+ {
+ bits::format::error("Argument index out of bounds.");
+ }
+ }
+
+ constexpr auto check_dynamic_width_id(std::size_t id) -> void
+ {
+ check_arg_id(id);
+ if (m_is_integral && !m_is_integral[id])
+ {
+ bits::format::error("Dynamic width argument must be an integral object.");
+ }
+ }
+
+ constexpr auto next_dynamic_width_id() -> std::size_t
+ {
+ auto const id = next_arg_id();
+ if (m_is_integral && !m_is_integral[id])
+ {
+ bits::format::error("Dynamic width argument must be an integral object.");
+ }
+ return id;
+ }
+
+ private:
+ enum class index_mode
+ {
+ unknown,
+ automatic,
+ manual,
+ };
+
+ iterator m_current{};
+ iterator m_end{};
+ index_mode m_mode{};
+ std::size_t m_next_argument_id{};
+ std::size_t m_argument_count{};
+ bool const * m_is_integral{};
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/specifiers.hpp b/libs/kstd/kstd/bits/format/specifiers.hpp
new file mode 100644
index 0000000..211c95d
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/specifiers.hpp
@@ -0,0 +1,205 @@
+#ifndef KSTD_BITS_FORMAT_SPECS_HPP
+#define KSTD_BITS_FORMAT_SPECS_HPP
+
+// IWYU pragma: private
+
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <cstddef>
+#include <cstdint>
+#include <iterator>
+
+namespace kstd::bits::format
+{
+ enum struct alignment : std::uint8_t
+ {
+ none,
+ left,
+ right,
+ center,
+ };
+
+ enum struct sign_mode : std::uint8_t
+ {
+ none,
+ plus,
+ minus,
+ space,
+ };
+
+ enum struct width_mode : std::uint8_t
+ {
+ none,
+ static_value,
+ dynamic_argument_id
+ };
+
+ struct specifiers
+ {
+ char fill{' '};
+ alignment align{};
+ sign_mode sign{};
+ bool alternative_form{};
+ bool zero_pad{};
+
+ width_mode mode{};
+ std::size_t width_value{};
+ char type{};
+ };
+
+ struct padding
+ {
+ std::size_t left{};
+ std::size_t right{};
+ };
+
+ constexpr auto parse_format_specifiers(format_parse_context & context) -> specifiers
+ {
+ auto specs = specifiers{};
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (it != end && *it == '}')
+ {
+ return specs;
+ }
+
+ if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^'))
+ {
+ specs.fill = *it;
+ switch (*std::next(it))
+ {
+ case '<':
+ specs.align = alignment::left;
+ break;
+ case '>':
+ specs.align = alignment::right;
+ break;
+ case '^':
+ default:
+ specs.align = alignment::center;
+ break;
+ }
+ std::advance(it, 2);
+ }
+ else if (*it == '<' || *it == '>' || *it == '^')
+ {
+ switch (*it)
+ {
+ case '<':
+ specs.align = alignment::left;
+ break;
+ case '>':
+ specs.align = alignment::right;
+ break;
+ case '^':
+ default:
+ specs.align = alignment::center;
+ break;
+ }
+ std::advance(it, 1);
+ }
+
+ if (it != end && (*it == '+' || *it == '-' || *it == ' '))
+ {
+ switch (*it)
+ {
+ case '+':
+ specs.sign = sign_mode::plus;
+ break;
+ case '-':
+ specs.sign = sign_mode::minus;
+ break;
+ case ' ':
+ default:
+ specs.sign = sign_mode::space;
+ break;
+ }
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == '#')
+ {
+ specs.alternative_form = true;
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == '0')
+ {
+ specs.zero_pad = true;
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == '{')
+ {
+ specs.mode = width_mode::dynamic_argument_id;
+ std::advance(it, 1);
+ auto argument_id = 0uz;
+
+ if (it != end && *it >= '0' && *it <= '9')
+ {
+ while (it != end && *it >= '0' && *it <= '9')
+ {
+ argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0');
+ std::advance(it, 1);
+ }
+ context.check_dynamic_width_id(argument_id);
+ }
+ else
+ {
+ argument_id = context.next_dynamic_width_id();
+ }
+
+ if (it == end || *it != '}')
+ {
+ error("Expected '}' for dynamic width.");
+ }
+ std::advance(it, 1);
+ specs.width_value = argument_id;
+ }
+ else if (it != end && *it >= '0' && *it <= '9')
+ {
+ specs.mode = width_mode::static_value;
+ while (it != end && *it >= '0' && *it <= '9')
+ {
+ specs.width_value = specs.width_value * 10 + static_cast<std::size_t>(*it - '0');
+ std::advance(it, 1);
+ }
+ }
+
+ context.advance_to(it);
+ return specs;
+ }
+
+ constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length,
+ alignment requested_alignment, alignment default_alignment) -> padding
+ {
+ if (target_width <= content_length)
+ {
+ return {};
+ }
+
+ auto total_padding = target_width - content_length;
+ auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment;
+
+ switch (effective_alignment)
+ {
+ case alignment::center:
+ {
+ auto left = total_padding / 2;
+ auto right = total_padding - left;
+ return {left, right};
+ }
+ case alignment::left:
+ return {0, total_padding};
+ case alignment::right:
+ default:
+ return {total_padding, 0};
+ break;
+ }
+ }
+
+} // namespace kstd::bits::format
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/string.hpp b/libs/kstd/kstd/bits/format/string.hpp
new file mode 100644
index 0000000..e7e4088
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/string.hpp
@@ -0,0 +1,148 @@
+#ifndef KSTD_BITS_FORMAT_STRING_HPP
+#define KSTD_BITS_FORMAT_STRING_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <array>
+#include <cstddef>
+#include <string_view>
+#include <type_traits>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+ template<typename... Args>
+ constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void
+ {
+ auto found = false;
+ auto current_index = 0uz;
+
+ (..., [&] {
+ if (current_index == target_index && !found)
+ {
+ using decay_type = std::remove_cvref_t<Args>;
+ static_assert(std::is_default_constructible_v<formatter<decay_type>>, "Missing formatter specialization.");
+ auto fmt = formatter<decay_type>{};
+
+ auto it = fmt.parse(context);
+ context.advance_to(it);
+ found = true;
+ }
+ ++current_index;
+ }());
+
+ if (!found)
+ {
+ error("Argument index out of bounds.");
+ }
+ }
+
+ template<typename... Args>
+ constexpr auto validate_dynamic_width(std::size_t target_index) -> void
+ {
+ auto is_valid_integer = false;
+ auto current_index = 0uz;
+
+ (..., [&] {
+ if (current_index == target_index)
+ {
+ using decay_type = std::remove_cvref_t<Args>;
+ if constexpr (std::is_integral_v<decay_type>)
+ {
+ is_valid_integer = true;
+ }
+ }
+ ++current_index;
+ }());
+
+ if (!is_valid_integer)
+ {
+ error("Dynamic width argument must be an integral object.");
+ }
+ }
+ } // namespace bits::format
+
+ template<typename... Args>
+ struct format_string
+ {
+ template<std::size_t Size>
+ consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT
+ : str_view{str}
+ {
+ using namespace bits::format;
+
+ auto const is_width_compatible =
+ std::array<bool, (sizeof...(Args) > 0 ? sizeof...(Args) : 1)>{is_width_v<std::remove_cvref_t<Args>>...};
+ auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()};
+ auto it = context.begin();
+
+ while (it != context.end())
+ {
+ if (*it == '{')
+ {
+ ++it;
+ if (it != context.end() && *it == '{')
+ {
+ ++it;
+ context.advance_to(it);
+ continue;
+ }
+
+ context.advance_to(it);
+ auto argument_id = 0uz;
+
+ if (it != context.end() && *it >= '0' && *it <= '9')
+ {
+ while (it != context.end() && *it >= '0' && *it <= '9')
+ {
+ argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0');
+ ++it;
+ }
+ context.check_arg_id(argument_id);
+ context.advance_to(it);
+ }
+ else
+ {
+ argument_id = context.next_arg_id();
+ }
+
+ if (it != context.end() && *it == ':')
+ {
+ ++it;
+ context.advance_to(it);
+ }
+
+ validate_argument<Args...>(argument_id, context);
+
+ it = context.begin();
+ if (it == context.end() || *it != '}')
+ {
+ bits::format::error("Missing closing '}' in format string.");
+ }
+ }
+ else if (*it == '}')
+ {
+ ++it;
+ if (it != context.end() && *it == '}')
+ {
+ bits::format::error("Unescaped '}' in format string.");
+ }
+ }
+ ++it;
+ context.advance_to(it);
+ }
+ }
+
+ std::string_view str_view;
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/vformat.hpp b/libs/kstd/kstd/bits/format/vformat.hpp
new file mode 100644
index 0000000..994fae5
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/vformat.hpp
@@ -0,0 +1,99 @@
+#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP
+#define KSTD_BITS_FORMAT_VFORMAT_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/args.hpp>
+#include <kstd/bits/format/output_buffer.hpp>
+#include <kstd/bits/format/string.hpp>
+#include <kstd/string>
+
+#include <algorithm>
+#include <iterator>
+#include <string_view>
+#include <type_traits>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+ //! Format a string with the given arguments into the given buffer.
+ //!
+ //! External implementations of `vprint` may call this function.
+ //!
+ //! @param buffer The buffer to format into.
+ //! @param format The format string to use.
+ //! @param args The arguments for the format string.
+ auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void;
+
+ struct string_writer : output_buffer
+ {
+ auto push(std::string_view text) -> void override;
+ auto push(char character) -> void override;
+
+ auto release() -> string &&;
+
+ private:
+ string m_result{};
+ };
+
+ template<std::output_iterator<char> Output>
+ struct iterator_writer : output_buffer
+ {
+ explicit iterator_writer(Output iterator)
+ : m_output{iterator}
+ {}
+
+ [[nodiscard]] auto iterator() const -> Output
+ {
+ return m_output;
+ }
+
+ auto push(std::string_view text) -> void override
+ {
+ m_output = std::ranges::copy(text, m_output).out;
+ }
+
+ auto push(char character) -> void override
+ {
+ *m_output++ = character;
+ }
+
+ private:
+ Output m_output{};
+ };
+
+ } // namespace bits::format
+
+ //! Format a given string with the provided arguments.
+ //!
+ //! @param format The format string.
+ //! @param args The arguments for the format string.
+ //! @return A new string containing the result of the format operation.
+ template<typename... ArgumentTypes>
+ auto format(format_string<std::type_identity_t<ArgumentTypes>...> format, ArgumentTypes &&... args) -> string
+ {
+ auto buffer = bits::format::string_writer{};
+ bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward<ArgumentTypes>(args)...).args);
+ return buffer.release();
+ }
+
+ //! Format a given string with the provided arguments.
+ //!
+ //! @param iterator The iterator to write to.
+ //! @param format The format string.
+ //! @param args The arguments for the format string.
+ //! @return An iterator past the last element written.
+ template<std::output_iterator<char> Output, typename... ArgumentTypes>
+ auto format_to(Output iterator, format_string<std::type_identity_t<ArgumentTypes>...> format,
+ ArgumentTypes &&... args) -> Output
+ {
+ auto buffer = bits::format::iterator_writer{iterator};
+ bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward<ArgumentTypes>(args)...).args);
+ return buffer.iterator();
+ }
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/observer_ptr.hpp b/libs/kstd/kstd/bits/observer_ptr.hpp
new file mode 100644
index 0000000..2593d7a
--- /dev/null
+++ b/libs/kstd/kstd/bits/observer_ptr.hpp
@@ -0,0 +1,163 @@
+#ifndef KSTD_OBSERVER_PTR_HPP
+#define KSTD_OBSERVER_PTR_HPP
+
+// IWYU pragma: private, include <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/kstd/bits/observer_ptr.test.cpp b/libs/kstd/kstd/bits/observer_ptr.test.cpp
new file mode 100644
index 0000000..1ba9c63
--- /dev/null
+++ b/libs/kstd/kstd/bits/observer_ptr.test.cpp
@@ -0,0 +1,360 @@
+#include <kstd/memory>
+#include <kstd/test_support/os_panic.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <array>
+#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")
+ {
+ auto arr = std::array{1, 2};
+ auto ptr1 = kstd::observer_ptr{&arr[0]};
+ auto ptr2 = kstd::observer_ptr{&arr[1]};
+
+ WHEN("comparing the same observer pointer")
+ {
+ THEN("they are equal")
+ {
+ REQUIRE(ptr1 == ptr1);
+ REQUIRE((ptr1 <=> ptr1) == std::strong_ordering::equal);
+ }
+ }
+
+ WHEN("comparing different observer pointers")
+ {
+ THEN("they are ordered correctly")
+ {
+ REQUIRE(ptr1 != ptr2);
+ REQUIRE(ptr1 < ptr2);
+ REQUIRE(ptr1 <= ptr2);
+ REQUIRE(ptr2 > ptr1);
+ REQUIRE(ptr2 >= ptr1);
+ REQUIRE((ptr1 <=> ptr2) == std::strong_ordering::less);
+ REQUIRE((ptr2 <=> ptr1) == std::strong_ordering::greater);
+ }
+ }
+ }
+
+ GIVEN("A null observer pointer")
+ {
+ auto ptr = kstd::observer_ptr<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/include/kstd/bits/print_sink.hpp b/libs/kstd/kstd/bits/print_sink.hpp
index 0e0955c..af765e0 100644
--- a/libs/kstd/include/kstd/bits/print_sink.hpp
+++ b/libs/kstd/kstd/bits/print_sink.hpp
@@ -3,8 +3,6 @@
// IWYU pragma: private, include <kstd/print>
-#include <kstd/format>
-
namespace kstd
{
diff --git a/libs/kstd/kstd/bits/shared_ptr.hpp b/libs/kstd/kstd/bits/shared_ptr.hpp
new file mode 100644
index 0000000..8930095
--- /dev/null
+++ b/libs/kstd/kstd/bits/shared_ptr.hpp
@@ -0,0 +1,648 @@
+#ifndef KSTD_BITS_SHARED_PTR_HPP
+#define KSTD_BITS_SHARED_PTR_HPP
+
+#include <atomic>
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+// IWYU pragma: private, include <kstd/memory>
+
+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<std::size_t> shared_count;
+ std::atomic<std::size_t> weak_count;
+
+ explicit shared_control_block(std::size_t shared = 1, std::size_t weak = 0)
+ : shared_count(shared)
+ , weak_count(weak)
+ {}
+ };
+
+ template<typename T>
+ 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<typename T>
+ struct weak_ptr
+ {
+ template<typename U>
+ friend struct shared_ptr;
+
+ /**
+ * @brief Constructs a null weak_ptr.
+ */
+ weak_ptr() noexcept
+ : pointer(nullptr)
+ , control(nullptr)
+ {}
+
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ weak_ptr(shared_ptr<U> 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<T>
+ {
+ return shared_ptr<T>(*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<typename T>
+ struct enable_shared_from_this
+ {
+ template<typename U>
+ friend struct shared_ptr;
+
+ friend T;
+
+ public:
+ /**
+ * @brief Returns a shared_ptr that shares ownership of *this.
+ */
+ auto shared_from_this() -> shared_ptr<T>
+ {
+ return shared_ptr<T>(weak_this);
+ }
+
+ /**
+ * @brief Returns a shared_ptr that shares ownership of *this.
+ */
+ auto shared_from_this() const -> shared_ptr<T const>
+ {
+ return shared_ptr<T const>(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<T> const & ptr) const
+ {
+ weak_this = ptr;
+ }
+
+ mutable weak_ptr<T> weak_this{}; ///< Weak pointer to the object, used for shared_from_this functionality.
+ };
+
+ /**
+ * @brief Shared_pointer is a smart pointer that retains shared ownership of an object through a pointer. Several
+ * shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of
+ * the following happens: the last remaining shared_ptr owning the object is destroyed; the last remaining
+ * shared_ptr owning the object is assigned another pointer via operator= or reset(). A
+ * shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used
+ * to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(),
+ * the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count
+ * reaches zero.
+ *
+ * @tparam T The type of the managed object.
+ */
+ template<typename T>
+ struct shared_ptr
+ {
+ template<typename U>
+ friend struct shared_ptr;
+
+ template<typename U>
+ friend struct weak_ptr;
+
+ /**
+ * @brief Construct an empty shared_ptr.
+ */
+ shared_ptr() noexcept
+ : pointer(nullptr)
+ , control(nullptr)
+ {}
+
+ /**
+ * @brief Construct an empty shared_ptr from nullptr.
+ */
+ shared_ptr(std::nullptr_t) noexcept
+ : pointer(nullptr)
+ , control(nullptr)
+ {}
+
+ /**
+ * @brief Constructor.
+ *
+ * @param pointer A pointer to an object to manage (default is nullptr).
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ explicit shared_ptr(U * pointer = nullptr)
+ : pointer(pointer)
+ , control(pointer != nullptr ? new shared_control_block() : nullptr)
+ {
+ assign_enable_shared_from_this(pointer);
+ }
+
+ /**
+ * @brief Constructor a shared_ptr from a weak_ptr. If other is not expired, constructs a shared_ptr which shares
+ * ownership of the object managed by other. Otherwise, constructs an empty shared_ptr.
+ *
+ * @param other The weak_ptr to construct from.
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ explicit shared_ptr(weak_ptr<U> const & other)
+ : pointer(nullptr)
+ , control(nullptr)
+ {
+ if (other.control != nullptr && other.control->shared_count != 0)
+ {
+ pointer = other.pointer;
+ control = other.control;
+ ++(control->shared_count);
+ }
+ }
+
+ /**
+ * @brief Copy constructor.
+ *
+ * @param other The shared_ptr to copy from.
+ */
+ shared_ptr(shared_ptr const & other)
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ if (control != nullptr)
+ {
+ ++(control->shared_count);
+ }
+ }
+
+ /**
+ * @brief Converting copy constructor for compatible shared_ptr types.
+ *
+ * @tparam U Source pointer element type.
+ * @param other The shared_ptr to copy from.
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ shared_ptr(shared_ptr<U> const & other)
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ if (control != nullptr)
+ {
+ ++(control->shared_count);
+ }
+ }
+
+ /**
+ * @brief Move constructor.
+ *
+ * @param other The shared_ptr to move from.
+ */
+ shared_ptr(shared_ptr && other) noexcept
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ other.pointer = nullptr;
+ other.control = nullptr;
+ }
+
+ /**
+ * @brief Converting move constructor for compatible shared_ptr types.
+ *
+ * @tparam U Source pointer element type.
+ * @param other The shared_ptr to move from.
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ shared_ptr(shared_ptr<U> && other) noexcept
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ other.pointer = nullptr;
+ other.control = nullptr;
+ }
+
+ /**
+ * @brief Copy assignment operator. Replaces the managed object with the one managed by r. Shares ownership of the
+ * object managed by r. If r manages no object, *this manages no object too. Equivalent to
+ * shared_ptr<T>(r).swap(*this).
+ *
+ * @param other Another smart pointer to share the ownership with.
+ * @return Reference to this shared pointer.
+ */
+ auto operator=(shared_ptr const & other) -> shared_ptr &
+ {
+ if (this != &other)
+ {
+ cleanup();
+ pointer = other.pointer;
+ control = other.control;
+
+ if (control != nullptr)
+ {
+ ++(control->shared_count);
+ }
+ }
+
+ return *this;
+ }
+
+ /**
+ * @brief Converting copy assignment for compatible shared_ptr types.
+ *
+ * @tparam U Source pointer element type.
+ * @param other Another smart pointer to share ownership with.
+ * @return Reference to this shared pointer.
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ auto operator=(shared_ptr<U> const & other) -> shared_ptr &
+ {
+ cleanup();
+ pointer = other.pointer;
+ control = other.control;
+
+ if (control != nullptr)
+ {
+ ++(control->shared_count);
+ }
+
+ return *this;
+ }
+
+ /**
+ * @brief Move assignment operator. Move-assigns a shared_ptr from r. After the assignment, *this contains a copy of
+ * the previous state of r, and r is empty. Equivalent to shared_ptr<T>(std::move(r)).swap(*this).
+ *
+ * @param other Another smart pointer to acquire the ownership from.
+ * @return Reference to this shared pointer.
+ */
+ auto operator=(shared_ptr && other) noexcept -> shared_ptr &
+ {
+ if (this != &other)
+ {
+ cleanup();
+ pointer = other.pointer;
+ control = other.control;
+ other.pointer = nullptr;
+ other.control = nullptr;
+ }
+
+ return *this;
+ }
+
+ /**
+ * @brief Converting move assignment for compatible shared_ptr types.
+ *
+ * @tparam U Source pointer element type.
+ * @param other Another smart pointer to acquire ownership from.
+ * @return Reference to this shared pointer.
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ auto operator=(shared_ptr<U> && other) noexcept -> shared_ptr &
+ {
+ cleanup();
+ pointer = other.pointer;
+ control = other.control;
+ other.pointer = nullptr;
+ other.control = nullptr;
+
+ return *this;
+ }
+
+ /**
+ * @brief Reset this shared_ptr to empty via nullptr assignment.
+ */
+ auto operator=(std::nullptr_t) noexcept -> shared_ptr &
+ {
+ cleanup();
+ pointer = nullptr;
+ control = nullptr;
+ return *this;
+ }
+
+ /**
+ * @brief Destructor. Cleans up resources if necessary.
+ */
+ ~shared_ptr()
+ {
+ cleanup();
+ }
+
+ /**
+ * @brief Replaces the managed object.
+ *
+ * @param ptr Pointer to a new object to manage (default = nullptr).
+ */
+ void reset(T * ptr = nullptr)
+ {
+ cleanup();
+ pointer = ptr;
+ control = ptr != nullptr ? new shared_control_block() : nullptr;
+ assign_enable_shared_from_this(ptr);
+ }
+
+ /**
+ * @brief Exchanges the stored pointer values and the ownerships of *this and r. Reference counts, if any, are not
+ * adjusted.
+ *
+ * @param other The shared_ptr to swap with.
+ */
+ void swap(shared_ptr & other)
+ {
+ std::swap(pointer, other.pointer);
+ std::swap(control, other.control);
+ }
+
+ /**
+ * @brief Dereference operator. If get() is a null pointer, the behavior is undefined.
+ *
+ * @return Returns the object owned by *this, equivalent to *get().
+ */
+ [[nodiscard]] auto operator*() const -> T &
+ {
+ return *pointer;
+ }
+
+ /**
+ * @brief Member access operator.
+ *
+ * @return Returns a pointer to the object owned by *this, i.e. get().
+ */
+ [[nodiscard]] auto operator->() const -> T *
+ {
+ return pointer;
+ }
+
+ /**
+ * @brief Returns a pointer to the managed object or nullptr if no object is owned.
+ *
+ * @return Pointer to the managed object or nullptr if no object is owned.
+ */
+ [[nodiscard]] auto get() const -> T *
+ {
+ return pointer;
+ }
+
+ /**
+ * @brief Returns the number of different shared_ptr instances (*this included) managing the current object. If
+ * there is no managed object, ​0​ is returned.
+ *
+ * @note Common use cases include comparison with ​0​. If use_count returns zero, the shared pointer is empty
+ * and manages no objects (whether or not its stored pointer is nullptr). Comparison with 1. If use_count returns 1,
+ * there are no other owners.
+ *
+ * @return The number of Shared_pointer instances managing the current object or ​0​ if there is no managed
+ * object.
+ */
+ [[nodiscard]] auto use_count() const -> std::size_t
+ {
+ if (control != nullptr)
+ {
+ return control->shared_count;
+ }
+
+ return 0;
+ }
+
+ /**
+ * @brief Checks whether *this owns an object, i.e. whether get() != nullptr.
+ *
+ * @return true if *this owns an object, false otherwise.
+ */
+ [[nodiscard]] explicit operator bool() const
+ {
+ return pointer != nullptr;
+ }
+
+ /**
+ * @brief Compare shared_ptr with nullptr.
+ */
+ [[nodiscard]] auto operator==(std::nullptr_t) const -> bool
+ {
+ return pointer == nullptr;
+ }
+
+ /**
+ * @brief Compare nullptr with shared_ptr.
+ */
+ [[nodiscard]] friend auto operator==(std::nullptr_t, shared_ptr const & ptr) -> bool
+ {
+ return ptr.pointer == nullptr;
+ }
+
+ private:
+ /**
+ * @brief If the candidate type inherits from enable_shared_from_this, assigns the internal weak pointer to this
+ * shared_ptr. This weak_ptr is used to implement shared_from_this functionality for the candidate type. If the
+ * candidate type does not inherit from enable_shared_from_this, this function does nothing.
+ *
+ * @tparam U The candidate type to check for enable_shared_from_this inheritance.
+ * @param candidate The candidate object to assign the internal weak pointer for.
+ */
+ template<typename U>
+ auto assign_enable_shared_from_this(U * candidate) -> void
+ {
+ if constexpr (requires(U * p, shared_ptr<T> const & sp) { p->internal_assign_ptr(sp); })
+ {
+ if (candidate != nullptr)
+ {
+ candidate->internal_assign_ptr(*this);
+ }
+ }
+ }
+
+ /**
+ * @brief Releases ownership and deletes the object if this was the last reference to the owned managed object.
+ */
+ auto cleanup() -> void
+ {
+ if (control != nullptr)
+ {
+ if (--(control->shared_count) == 0)
+ {
+ delete pointer;
+ pointer = nullptr;
+
+ if (control->weak_count == 0)
+ {
+ delete control;
+ }
+ }
+ }
+ }
+
+ T * pointer; ///< The managed object.
+ shared_control_block * control; ///< Shared control block.
+ };
+
+ /**
+ * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls
+ * lhs.swap(rhs).
+ *
+ * @tparam T Type of the managed object.
+ * @param lhs, rhs Smart pointers whose contents to swap.
+ */
+ template<typename T>
+ auto swap(shared_ptr<T> & lhs, shared_ptr<T> & rhs) -> void
+ {
+ lhs.swap(rhs);
+ }
+
+ /**
+ * @brief Constructs an object of type T and wraps it in a shared_ptr. Constructs a non-array type T. The
+ * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is
+ * not an array type. The function is equivalent to: shared_ptr<T>(new T(std::forward<Args>(args)...)).
+ *
+ * @tparam T Type of the managed object.
+ * @tparam Args Argument types for T's constructor.
+ * @param args List of arguments with which an instance of T will be constructed.
+ * @returns Shared_pointer of an instance of type T.
+ */
+ template<typename T, typename... Args>
+ auto make_shared(Args &&... args) -> shared_ptr<T>
+ {
+ return shared_ptr<T>(new T(std::forward<Args>(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<typename T, typename U>
+ [[nodiscard]] auto inline operator==(shared_ptr<T> const & lhs, shared_ptr<U> 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<typename T, typename U>
+ [[nodiscard]] auto inline operator<=>(shared_ptr<T> const & lhs, shared_ptr<U> const & rhs)
+ {
+ return lhs.get() <=> rhs.get();
+ }
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/unique_ptr.hpp b/libs/kstd/kstd/bits/unique_ptr.hpp
index e0870b1..3d803b4 100644
--- a/libs/kstd/include/kstd/bits/unique_ptr.hpp
+++ b/libs/kstd/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/kstd/cstring b/libs/kstd/kstd/cstring
new file mode 100644
index 0000000..bd8b28d
--- /dev/null
+++ b/libs/kstd/kstd/cstring
@@ -0,0 +1,21 @@
+#ifndef KSTD_CSTRING
+#define KSTD_CSTRING
+
+#include <cstddef>
+
+namespace kstd::libc
+{
+
+ extern "C"
+ {
+ auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void *;
+ auto memset(void * dest, int value, std::size_t size) noexcept -> void *;
+ auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void *;
+ auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int;
+
+ auto strlen(char const * string) noexcept -> std::size_t;
+ }
+
+} // namespace kstd::libc
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/ext/bitfield_enum b/libs/kstd/kstd/ext/bitfield_enum
index 80fe9d2..80fe9d2 100644
--- a/libs/kstd/include/kstd/ext/bitfield_enum
+++ b/libs/kstd/kstd/ext/bitfield_enum
diff --git a/libs/kstd/kstd/flat_map b/libs/kstd/kstd/flat_map
new file mode 100644
index 0000000..e51eb91
--- /dev/null
+++ b/libs/kstd/kstd/flat_map
@@ -0,0 +1,406 @@
+#ifndef KSTD_FLAT_MAP_HPP
+#define KSTD_FLAT_MAP_HPP
+
+#include <kstd/bits/flat_map.hpp>
+#include <kstd/os/error.hpp>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <concepts>
+#include <cstddef>
+#include <functional>
+#include <iterator>
+#include <utility>
+
+namespace kstd
+{
+
+ template<typename KeyType, typename MappedType, typename KeyCompare = std::less<KeyType>,
+ typename KeyContainerType = kstd::vector<KeyType>, typename MappedContainerType = kstd::vector<MappedType>>
+ struct flat_map
+ {
+ //! The type of container used to store the map keys.
+ using key_container_type = KeyContainerType;
+
+ //! The type of container used to store the map values.
+ using mapped_container_type = MappedContainerType;
+
+ //! The type of the map keys.
+ using key_type = KeyType;
+
+ //! The type of the mappe values.
+ using mapped_type = MappedType;
+
+ //! The type of a single key-value value in the map.
+ using value_type = std::pair<key_type, mapped_type>;
+
+ //! The comparator used to sort the keys.
+ using key_compare = KeyCompare;
+
+ using reference = std::pair<key_type const &, mapped_type &>;
+ using const_reference = std::pair<key_type const &, mapped_type const &>;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using iterator = bits::flat_map_iterator<KeyType, MappedType, typename key_container_type::iterator,
+ typename mapped_container_type::iterator>;
+ using const_iterator =
+ bits::flat_map_iterator<KeyType, MappedType const, typename key_container_type::const_iterator,
+ typename mapped_container_type::const_iterator>;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+ using containers = struct
+ {
+ key_container_type keys;
+ mapped_container_type values;
+ };
+
+ //! Compare two object of type value_type.
+ struct value_compare
+ {
+ constexpr auto operator()(const_reference lhs, const_reference rhs) const -> bool
+ {
+ return lhs.first < rhs.first;
+ }
+ };
+
+ //! Construct an empty flat map.
+ constexpr flat_map()
+ : flat_map{key_compare{}}
+ {}
+
+ //! Construct an empty flat map using the given custom comparator.
+ //!
+ //! @param comparator The comparator to use for comparing keys.
+ constexpr explicit flat_map(key_compare const & comparator)
+ : m_containers{}
+ , m_comparator{comparator}
+ {}
+
+ //! Replace the contents of this flat map with the one of a different one.
+ //!
+ //! @param other the flat map to copy from.
+ //! @return A reference to this flat map.
+ auto operator=(flat_map const & other) -> flat_map &
+ {
+ if (this != &other)
+ {
+ std::tie(m_containers, m_comparator) = std::tie(other.m_containers, other.m_comparator);
+ }
+ return *this;
+ }
+
+ //! Get a reference to the mapped value associated with the given key.
+ //!
+ //! @warning This function will panic if the key is not found.
+ //! @param key The key to look up.
+ //! @return A reference to the mapped value.
+ [[nodiscard]] constexpr auto at(key_type const & key) -> mapped_type &
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key))
+ {
+ auto offset = std::distance(m_containers.keys.begin(), found);
+ return *(m_containers.values.begin() + offset);
+ }
+ os::panic("[kstd::flat_map] Key not found");
+ }
+
+ //! Get a reference to the mapped value associated with the given key.
+ //!
+ //! @warning This function will panic if the key is not found.
+ //! @param key The key to look up.
+ //! @return A const reference to the mapped value.
+ [[nodiscard]] constexpr auto at(key_type const & key) const -> mapped_type const &
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key))
+ {
+ auto offset = std::distance(m_containers.keys.cbegin(), found);
+ return *(m_containers.values.cbegin() + offset);
+ }
+ os::panic("[kstd::flat_map] Key not found");
+ }
+
+ //! Get a reference to the mapped value associated with the given key.
+ //!
+ //! @note This overload only participates in overload resolution if the key compare type is transparent.
+ //! @warning This function will panic if the key is not found.
+ //! @param x The key to look up.
+ //! @return A reference to the mapped value.
+ template<typename K>
+ requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; }
+ [[nodiscard]] constexpr auto at(K const & x) -> mapped_type &
+ {
+ auto found = find(x);
+ if (found != end())
+ {
+ auto offset = std::distance(m_containers.keys.begin(), found.key_iterator());
+ return *(m_containers.values.begin() + offset);
+ }
+ os::panic("[kstd::flat_map] Key not found");
+ }
+
+ //! Get a reference to the mapped value associated with the given key.
+ //! @note This overload only participates in overload resolution if the key compare type is transparent.
+ //! @warning This function will panic if the key is not found.
+ //! @param x The key to look up.
+ //! @return A const reference to the mapped value.
+ template<typename K>
+ requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; }
+ [[nodiscard]] auto at(K const & x) const -> mapped_type const &
+ {
+ auto found = find(x);
+ if (found != end())
+ {
+ auto offset = std::distance(m_containers.keys.cbegin(), found.key_iterator());
+ return *(m_containers.values.cbegin() + offset);
+ }
+ os::panic("[kstd::flat_map] Key not found");
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto begin() noexcept -> iterator
+ {
+ return iterator{m_containers.keys.begin(), m_containers.values.begin()};
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto begin() const noexcept -> const_iterator
+ {
+ return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()};
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto cbegin() const noexcept -> const_iterator
+ {
+ return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto end() noexcept -> iterator
+ {
+ return iterator{m_containers.keys.end(), m_containers.values.end()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto end() const noexcept -> const_iterator
+ {
+ return const_iterator{m_containers.keys.cend(), m_containers.values.cend()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto cend() const noexcept -> const_iterator
+ {
+ return const_iterator{m_containers.keys.cend(), m_containers.values.cend()};
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto rbegin() noexcept -> reverse_iterator
+ {
+ return reverse_iterator{end()};
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator
+ {
+ return const_reverse_iterator{cend()};
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator
+ {
+ return const_reverse_iterator{cend()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto rend() noexcept -> reverse_iterator
+ {
+ return reverse_iterator{begin()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator
+ {
+ return const_reverse_iterator{cbegin()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator
+ {
+ return const_reverse_iterator{cbegin()};
+ }
+
+ //! Check whether this flat map is empty or not
+ [[nodiscard]] auto empty() const noexcept -> bool
+ {
+ return m_containers.keys.empty();
+ }
+
+ //! Get the number of elements in this flat map.
+ [[nodiscard]] auto size() const noexcept -> size_type
+ {
+ return m_containers.keys.size();
+ }
+
+ //! Get the maximum number of elements possible in this flat map
+ [[nodiscard]] auto max_size() const noexcept -> size_type
+ {
+ return std::min(m_containers.keys.max_size(), m_containers.values.max_size());
+ }
+
+ //! Try to insert a new key-value pair into the map.
+ //!
+ //! @param args Arguments to use for constructing the key-value pair.
+ //! @return A pair of an iterator to the inserted element and a boolean indicating whether the insertion took place.
+ template<typename... Args>
+ auto emplace(Args &&... args) -> std::pair<iterator, bool>
+ requires std::constructible_from<value_type, Args...>
+ {
+ auto value = value_type{std::forward<Args>(args)...};
+ auto found = std::ranges::lower_bound(m_containers.keys, value.first, m_comparator);
+
+ if (found != m_containers.keys.cend() && !m_comparator(value.first, *found) && !m_comparator(*found, value.first))
+ {
+ auto offset = std::distance(m_containers.keys.begin(), found);
+ return {
+ iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset},
+ false
+ };
+ }
+
+ auto offset = std::distance(m_containers.keys.begin(), found);
+ auto key_iterator = m_containers.keys.begin() + offset;
+ auto mapped_iterator = m_containers.values.begin() + offset;
+
+ auto inserted_key = m_containers.keys.insert(key_iterator, std::move(value.first));
+ auto inserted_mapped = m_containers.values.insert(mapped_iterator, std::move(value.second));
+
+ return {
+ iterator{inserted_key, inserted_mapped},
+ true
+ };
+ }
+
+ //! Try to insert a element for the given key into this map.
+ //!
+ //! This function does nothing if the key is already present.
+ //!
+ //! @param key The key to insert a value for.
+ //! @param args The arguments to use to construct the mapped value.
+ template<typename... Args>
+ auto try_emplace(key_type const & key, Args &&... args) -> std::pair<iterator, bool>
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(*found, key) && !m_comparator(key, *found))
+ {
+ return {found, false};
+ }
+
+ auto offset = std::distance(m_containers.keys.cbegin(), found);
+ auto intersertion_point = m_containers.value.begin() + offset;
+
+ auto inserted_key = m_containers.keys.emplace(key);
+ auto inserted_mapped = m_containers.values.emplace(std::forward<Args>(args)...);
+
+ return {
+ iterator{inserted_key, inserted_mapped},
+ true
+ };
+ }
+
+ //! Find an element with an equivalent key.
+ //!
+ //! @param key The key to look up.
+ //! @return An iterator to the element with the equivalent key, or end() if no such element is found.
+ [[nodiscard]] auto find(key_type const & key) noexcept -> iterator
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key))
+ {
+ auto offset = std::distance(m_containers.keys.begin(), found);
+ return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset};
+ }
+ return end();
+ }
+
+ //! Find an element with an equivalent key.
+ //!
+ //! @param key The key to look up.
+ //! @return An iterator to the element with the equivalent key, or end() if no such element is found.
+ [[nodiscard]] auto find(key_type const & key) const noexcept -> const_iterator
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key))
+ {
+ auto offset = std::distance(m_containers.keys.cbegin(), found);
+ return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset};
+ }
+ return cend();
+ }
+
+ //! Find an element with an equivalent key.
+ //! @note This overload only participates in overload resolution if the key compare type is transparent.
+ //! @param x The key to look up.
+ //! @return An iterator to the element with the equivalent key, or end() if no such element is found.
+ template<typename K>
+ requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; }
+ [[nodiscard]] auto find(K const & x) noexcept -> iterator
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x))
+ {
+ auto offset = std::distance(m_containers.keys.begin(), found);
+ return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset};
+ }
+ return end();
+ }
+
+ //! Find an element with an equivalent key.
+ //! @note This overload only participates in overload resolution if the key compare type is transparent.
+ //! @param x The key to look up.
+ //! @return An iterator to the element with the equivalent key, or end() if no such element is found.
+ template<typename K>
+ requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; }
+ [[nodiscard]] auto find(K const & x) const noexcept -> const_iterator
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x))
+ {
+ auto offset = std::distance(m_containers.keys.cbegin(), found);
+ return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset};
+ }
+ return cend();
+ }
+
+ //! Check if the map contains the given key.
+ //!
+ //! @param key The key to check.
+ //! @return true iff. the key is found, false otherwise.
+ [[nodiscard]] constexpr auto contains(key_type const & key) const noexcept -> bool
+ {
+ return find(key) != cend();
+ }
+
+ //! Get a reference to the keys container.
+ //!
+ //! @return a reference to the keys container.
+ [[nodiscard]] constexpr auto keys() const noexcept -> key_container_type const &
+ {
+ return m_containers.keys;
+ }
+
+ //! Get a reference to the values container.
+ //!
+ //! @return a reference to the values container.
+ [[nodiscard]] constexpr auto values() const noexcept -> mapped_container_type const &
+ {
+ return m_containers.values;
+ }
+
+ private:
+ containers m_containers;
+ key_compare m_comparator;
+ };
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/flat_map.test.cpp b/libs/kstd/kstd/flat_map.test.cpp
new file mode 100644
index 0000000..9df03bb
--- /dev/null
+++ b/libs/kstd/kstd/flat_map.test.cpp
@@ -0,0 +1,351 @@
+#include <kstd/flat_map>
+
+#include <kstd/test_support/os_panic.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+SCENARIO("Flat Map initialization and construction", "[flat_map]")
+{
+ GIVEN("An empty context")
+ {
+ WHEN("constructing by default")
+ {
+ auto map = kstd::flat_map<int, int>{};
+
+ THEN("the Flat Map does not contain elements")
+ {
+ REQUIRE_FALSE(map.contains(1));
+ }
+
+ THEN("the keys container is empty")
+ {
+ REQUIRE(map.keys().empty());
+ }
+
+ THEN("the values container is empty")
+ {
+ REQUIRE(map.values().empty());
+ }
+ }
+ }
+}
+
+SCENARIO("Flat Map modifiers", "[flat_map]")
+{
+ GIVEN("An empty Flat Map")
+ {
+ auto map = kstd::flat_map<int, int>{};
+
+ WHEN("emplacing a new element")
+ {
+ auto [it, inserted] = map.emplace(1, 100);
+
+ THEN("the map contains the new element")
+ {
+ REQUIRE(inserted);
+ REQUIRE(map.contains(1));
+ }
+ }
+
+ WHEN("emplacing an existing element")
+ {
+ map.emplace(1, 100);
+ auto [it, inserted] = map.emplace(1, 200);
+
+ THEN("the map does not insert the duplicate")
+ {
+ REQUIRE_FALSE(inserted);
+ REQUIRE(map.contains(1));
+ }
+ }
+
+ AND_GIVEN("a populated Flat Map")
+ {
+ auto other = kstd::flat_map<int, int>{};
+ other.emplace(1, 10);
+ other.emplace(2, 20);
+ other.emplace(3, 30);
+
+ WHEN("assigning the populated Flat Map to the empty one")
+ {
+ map = other;
+
+ THEN("the elements are copied")
+ {
+ REQUIRE(map.at(1) == 10);
+ REQUIRE(map.at(2) == 20);
+ REQUIRE(map.at(3) == 30);
+ }
+
+ THEN("the elements are still in the populated Flat Map")
+ {
+ REQUIRE(other.at(1) == 10);
+ REQUIRE(other.at(2) == 20);
+ REQUIRE(other.at(3) == 30);
+ }
+ }
+ }
+ }
+}
+
+SCENARIO("Flat Map element access", "[flat_map]")
+{
+ GIVEN("A populated Flat Map")
+ {
+ auto map = kstd::flat_map<int, int>{};
+ map.emplace(1, 10);
+ map.emplace(2, 20);
+ map.emplace(3, 30);
+
+ WHEN("accessing an existing element with at()")
+ {
+ auto & val = map.at(2);
+
+ THEN("it returns a reference to the mapped value")
+ {
+ REQUIRE(val == 20);
+ }
+
+ THEN("the mapped value can be modified")
+ {
+ val = 200;
+ REQUIRE(map.at(2) == 200);
+ }
+ }
+
+ WHEN("accessing a non-existent element with at()")
+ {
+ THEN("it panics")
+ {
+ REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic);
+ }
+ }
+ }
+
+ GIVEN("A const populated Flat Map")
+ {
+ auto map_builder = kstd::flat_map<int, int>{};
+ map_builder.emplace(1, 10);
+ map_builder.emplace(2, 20);
+ auto const map = map_builder;
+
+ WHEN("accessing an existing element with const at()")
+ {
+ auto const & val = map.at(2);
+
+ THEN("it returns a const reference to the mapped value")
+ {
+ REQUIRE(val == 20);
+ }
+ }
+
+ WHEN("accessing a non-existent element with const at()")
+ {
+ THEN("it panics")
+ {
+ REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic);
+ }
+ }
+ }
+}
+
+SCENARIO("Flat Map iterators", "[flat_map]")
+{
+ GIVEN("A populated Flat Map")
+ {
+ auto map = kstd::flat_map<int, int>{};
+ map.emplace(1, 10);
+ map.emplace(2, 20);
+ map.emplace(3, 30);
+
+ WHEN("using forward iterators")
+ {
+ THEN("they navigate the elements in the correct forward order")
+ {
+ auto it = map.begin();
+ REQUIRE(it != map.end());
+ REQUIRE((*it).first == 1);
+
+ ++it;
+ REQUIRE(it != map.end());
+ REQUIRE((*it).first == 2);
+
+ ++it;
+ REQUIRE(it != map.end());
+ REQUIRE((*it).first == 3);
+
+ ++it;
+ REQUIRE(it == map.end());
+ }
+
+ THEN("const forward iterators provide correct access")
+ {
+ auto it = map.cbegin();
+ REQUIRE(it != map.cend());
+ REQUIRE((*it).first == 1);
+
+ ++it;
+ REQUIRE(it != map.cend());
+ REQUIRE((*it).first == 2);
+
+ ++it;
+ REQUIRE(it != map.cend());
+ REQUIRE((*it).first == 3);
+
+ ++it;
+ REQUIRE(it == map.cend());
+ }
+
+ THEN("assignment through the proxy modifies the mapped value")
+ {
+ auto it = map.begin();
+
+ *it = std::pair{1, 100};
+
+ REQUIRE(it->second == 100);
+ REQUIRE(map.at(1) == 100);
+ }
+
+ THEN("structured bindings evaluate correctly")
+ {
+ auto it = map.cbegin();
+
+ auto [key, value] = *it;
+
+ REQUIRE(key == 1);
+ REQUIRE(value == 10);
+
+ STATIC_REQUIRE(std::is_same_v<decltype(key), int const &>);
+ STATIC_REQUIRE(std::is_same_v<decltype(value), int const &>);
+ }
+ }
+
+ WHEN("using reverse iterators")
+ {
+ THEN("they navigate the elements in the correct reverse order")
+ {
+ auto it = map.rbegin();
+ REQUIRE(it != map.rend());
+ REQUIRE((*it).first == 3);
+
+ ++it;
+ REQUIRE(it != map.rend());
+ REQUIRE((*it).first == 2);
+
+ ++it;
+ REQUIRE(it != map.rend());
+ REQUIRE((*it).first == 1);
+
+ ++it;
+ REQUIRE(it == map.rend());
+ }
+
+ THEN("const reverse iterators provide correct access")
+ {
+ auto it = map.crbegin();
+ REQUIRE(it != map.crend());
+ REQUIRE((*it).first == 3);
+
+ ++it;
+ REQUIRE(it != map.crend());
+ REQUIRE((*it).first == 2);
+
+ ++it;
+ REQUIRE(it != map.crend());
+ REQUIRE((*it).first == 1);
+
+ ++it;
+ REQUIRE(it == map.crend());
+ }
+ }
+ }
+
+ GIVEN("an empty Flat Map")
+ {
+ auto map = kstd::flat_map<int, int>{};
+
+ WHEN("getting iterators")
+ {
+ THEN("begin() equals end() and cbegin() equals cend()")
+ {
+ REQUIRE(map.begin() == map.end());
+ REQUIRE(map.cbegin() == map.cend());
+ }
+
+ THEN("rbegin() equals rend() and crbegin() equals crend()")
+ {
+ REQUIRE(map.rbegin() == map.rend());
+ REQUIRE(map.crbegin() == map.crend());
+ }
+ }
+ }
+}
+
+SCENARIO("Flat Map heterogeneous element access", "[flat_map]")
+{
+ GIVEN("A populated Flat Map with a transparent comparator")
+ {
+ auto map = kstd::flat_map<int, int, std::less<void>>{};
+ map.emplace(1, 10);
+ map.emplace(2, 20);
+ map.emplace(3, 30);
+
+ WHEN("accessing an existing element with a different key type via at()")
+ {
+ long const key = 2L;
+ auto & val = map.at(key);
+
+ THEN("it returns a reference to the mapped value")
+ {
+ REQUIRE(val == 20);
+ }
+
+ THEN("the mapped value can be modified")
+ {
+ val = 200;
+ REQUIRE(map.at(2L) == 200);
+ }
+ }
+
+ WHEN("accessing a non-existent element with a different key type via at()")
+ {
+ long const key = 4L;
+ THEN("it panics")
+ {
+ REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic);
+ }
+ }
+ }
+
+ GIVEN("A const populated Flat Map with a transparent comparator")
+ {
+ auto map_builder = kstd::flat_map<int, int, std::less<void>>{};
+ map_builder.emplace(1, 10);
+ map_builder.emplace(2, 20);
+ auto const map = map_builder;
+
+ WHEN("accessing an existing element with a different key type via const at()")
+ {
+ long const key = 2L;
+ auto const & val = map.at(key);
+
+ THEN("it returns a const reference to the mapped value")
+ {
+ REQUIRE(val == 20);
+ }
+ }
+
+ WHEN("accessing a non-existent element with a different key type via const at()")
+ {
+ long const key = 4L;
+ THEN("it panics")
+ {
+ REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic);
+ }
+ }
+ }
+}
diff --git a/libs/kstd/kstd/format b/libs/kstd/kstd/format
new file mode 100644
index 0000000..e04b79a
--- /dev/null
+++ b/libs/kstd/kstd/format
@@ -0,0 +1,22 @@
+#ifndef KSTD_FORMAT_HPP
+#define KSTD_FORMAT_HPP
+
+#include <kstd/bits/format/arg.hpp> // IWYU pragma: export
+#include <kstd/bits/format/args.hpp> // IWYU pragma: export
+#include <kstd/bits/format/context.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/bool.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/byte.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/char.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/cstring.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/integral.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/ordering.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/pointer.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/range.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/string_view.hpp> // IWYU pragma: export
+#include <kstd/bits/format/output_buffer.hpp> // IWYU pragma: export
+#include <kstd/bits/format/parse_context.hpp> // IWYU pragma: export
+#include <kstd/bits/format/string.hpp> // IWYU pragma: export
+#include <kstd/bits/format/vformat.hpp> // IWYU pragma: export
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/format.test.cpp b/libs/kstd/kstd/format.test.cpp
new file mode 100644
index 0000000..624779a
--- /dev/null
+++ b/libs/kstd/kstd/format.test.cpp
@@ -0,0 +1,114 @@
+#include <kstd/format>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iterator>
+#include <sstream>
+
+SCENARIO("Formatting to a new string", "[format]")
+{
+ GIVEN("a format string without any placeholders")
+ {
+ auto const & fmt = "This is a test";
+
+ WHEN("calling format with without any arguments.")
+ {
+ auto result = kstd::format(fmt);
+
+ THEN("the result is the unmodified string")
+ {
+ REQUIRE(result == "This is a test");
+ }
+ }
+
+ WHEN("calling format with additional arguments")
+ {
+ auto result = kstd::format(fmt, 1, 2, 3);
+
+ THEN("the result is the unmodified string")
+ {
+ REQUIRE(result == "This is a test");
+ }
+ }
+ }
+
+ GIVEN("a format string with placeholders")
+ {
+ auto const & fmt = "Here are some placeholders: {} {} {}";
+
+ WHEN("calling format with the same number of arguments as there are placeholders")
+ {
+ auto result = kstd::format(fmt, 1, true, 'a');
+
+ THEN("the result is the formatted string")
+ {
+ REQUIRE(result == "Here are some placeholders: 1 true a");
+ }
+ }
+
+ WHEN("calling format with too many arguments")
+ {
+ auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6);
+
+ THEN("the result is the formatted string")
+ {
+ REQUIRE(result == "Here are some placeholders: 2 false b");
+ }
+ }
+ }
+}
+
+SCENARIO("Formatting to an output iterator", "[format]")
+{
+ auto buffer = std::ostringstream{};
+
+ GIVEN("a format string without any placeholders")
+ {
+ auto const & fmt = "This is a test";
+
+ WHEN("calling format with without any arguments.")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt);
+
+ THEN("the unmodified string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "This is a test");
+ }
+ }
+
+ WHEN("calling format with additional arguments")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 1, 2, 3);
+
+ THEN("the unmodified string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "This is a test");
+ }
+ }
+ }
+
+ GIVEN("a format string with placeholders")
+ {
+ auto const & fmt = "Here are some placeholders: {} {} {}";
+
+ WHEN("calling format with the same number of arguments as there are placeholders")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 1, true, -100);
+
+ THEN("the formatted string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100");
+ }
+ }
+
+ WHEN("calling format with too many arguments")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 2, false, -200, 4, 5, 6);
+
+ THEN("the formatted string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/kstd/src/libc/stdlib.cpp b/libs/kstd/kstd/libc/stdlib.cpp
index 4a5c91f..7ed051f 100644
--- a/libs/kstd/src/libc/stdlib.cpp
+++ b/libs/kstd/kstd/libc/stdlib.cpp
@@ -1,4 +1,4 @@
-#include "kstd/os/error.hpp"
+#include <kstd/os/error.hpp>
namespace kstd::libc
{
diff --git a/libs/kstd/kstd/libc/string.cpp b/libs/kstd/kstd/libc/string.cpp
new file mode 100644
index 0000000..b7cdb82
--- /dev/null
+++ b/libs/kstd/kstd/libc/string.cpp
@@ -0,0 +1,78 @@
+#include <kstd/cstring>
+
+#include <algorithm>
+#include <bit>
+#include <cstddef>
+#include <iterator>
+#include <span>
+
+namespace kstd::libc
+{
+
+ auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void *
+ {
+ auto dest_span = std::span{static_cast<std::byte *>(dest), size};
+ auto src_span = std::span{static_cast<std::byte const *>(src), size};
+
+ for (std::size_t i = 0; i < size; ++i)
+ {
+ dest_span[i] = src_span[i];
+ }
+
+ return dest;
+ }
+
+ auto memset(void * dest, int value, std::size_t size) noexcept -> void *
+ {
+ auto const byte_value = static_cast<std::byte>(static_cast<unsigned char>(value));
+ auto dest_span = std::span{static_cast<std::byte *>(dest), size};
+
+ for (std::size_t i = 0; i < size; ++i)
+ {
+ dest_span[i] = byte_value;
+ }
+
+ return dest;
+ }
+
+ auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int
+ {
+ auto left_span = std::span{static_cast<std::byte const *>(lhs), size};
+ auto right_span = std::span{static_cast<std::byte const *>(rhs), size};
+ auto mismatched = std::ranges::mismatch(left_span, right_span);
+
+ if (mismatched.in1 == left_span.end())
+ {
+ return 0;
+ }
+
+ return std::bit_cast<char>(*mismatched.in1) - std::bit_cast<char>(*mismatched.in2);
+ }
+
+ auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void *
+ {
+ auto dest_span = std::span{static_cast<std::byte *>(dest), size};
+ auto src_span = std::span{static_cast<std::byte const *>(src), size};
+ if (dest < src)
+ {
+ for (std::size_t i = 0; i < size; ++i)
+ {
+ dest_span[i] = src_span[i];
+ }
+ }
+ else
+ {
+ for (std::size_t i = size; i > 0; --i)
+ {
+ dest_span[i - 1] = src_span[i - 1];
+ }
+ }
+ return dest;
+ }
+
+ auto strlen(char const * string) noexcept -> std::size_t
+ {
+ return std::distance(string, std::ranges::find(string, nullptr, '\0'));
+ }
+
+} // namespace kstd::libc \ No newline at end of file
diff --git a/libs/kstd/kstd/memory b/libs/kstd/kstd/memory
new file mode 100644
index 0000000..f108c6d
--- /dev/null
+++ b/libs/kstd/kstd/memory
@@ -0,0 +1,8 @@
+#ifndef KSTD_MEMORY_HPP
+#define KSTD_MEMORY_HPP
+
+#include <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/mutex b/libs/kstd/kstd/mutex
index b2a31aa..b2a31aa 100644
--- a/libs/kstd/include/kstd/mutex
+++ b/libs/kstd/kstd/mutex
diff --git a/libs/kstd/src/mutex.cpp b/libs/kstd/kstd/mutex.cpp
index d66cb98..7387657 100644
--- a/libs/kstd/src/mutex.cpp
+++ b/libs/kstd/kstd/mutex.cpp
@@ -1,6 +1,6 @@
-#include "kstd/mutex"
+#include <kstd/mutex>
-#include "kstd/os/error.hpp"
+#include <kstd/os/error.hpp>
#include <atomic>
diff --git a/libs/kstd/src/os/error.cpp b/libs/kstd/kstd/os/error.cpp
index b82158d..f969cb5 100644
--- a/libs/kstd/src/os/error.cpp
+++ b/libs/kstd/kstd/os/error.cpp
@@ -1,4 +1,4 @@
-#include "kstd/os/error.hpp"
+#include <kstd/os/error.hpp>
namespace kstd::os
{
diff --git a/libs/kstd/include/kstd/os/error.hpp b/libs/kstd/kstd/os/error.hpp
index 9d43fb1..9d43fb1 100644
--- a/libs/kstd/include/kstd/os/error.hpp
+++ b/libs/kstd/kstd/os/error.hpp
diff --git a/libs/kstd/include/kstd/os/print.hpp b/libs/kstd/kstd/os/print.hpp
index f189042..36cb43d 100644
--- a/libs/kstd/include/kstd/os/print.hpp
+++ b/libs/kstd/kstd/os/print.hpp
@@ -1,8 +1,8 @@
#ifndef KSTD_OS_PRINT_HPP
#define KSTD_OS_PRINT_HPP
-#include "kstd/bits/formatter.hpp"
-#include "kstd/bits/print_sink.hpp"
+#include <kstd/bits/format/args.hpp>
+#include <kstd/bits/print_sink.hpp>
#include <string_view>
diff --git a/libs/kstd/include/kstd/print b/libs/kstd/kstd/print
index ffafda9..1033f72 100644
--- a/libs/kstd/include/kstd/print
+++ b/libs/kstd/kstd/print
@@ -1,12 +1,10 @@
#ifndef KSTD_PRINT
#define KSTD_PRINT
-#include "bits/print_sink.hpp" // IWYU pragma: export
-#include "os/print.hpp"
-
+#include <kstd/bits/print_sink.hpp> // IWYU pragma: export
#include <kstd/format>
+#include <kstd/os/print.hpp>
-#include <array>
#include <type_traits>
namespace kstd
@@ -19,13 +17,10 @@ namespace kstd
//! @param args The arguments to use to place in the format string's placeholders.
template<typename... Args>
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
- auto print(kstd::format_string<std::type_identity_t<Args>...> format, Args &&... args) -> void
+ auto print(kstd::format_string<std::type_identity_t<Args>...> format, Args const &... args) -> void
{
- auto arguments = std::array<kstd::format_arg, sizeof...(Args)>{
- kstd::format_arg{&args, kstd::format_dispatcher<std::remove_cvref_t<Args>>}
- ...
- };
- os::vprint(print_sink::stdout, format.str, kstd::format_args{arguments.data(), sizeof...(Args)});
+ auto const arg_store = kstd::make_format_args(args...);
+ os::vprint(print_sink::stdout, format.str_view, arg_store.args);
}
//! @qualifier kernel-defined
@@ -37,11 +32,8 @@ namespace kstd
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
auto print(print_sink sink, kstd::format_string<std::type_identity_t<Args>...> format, Args &&... args) -> void
{
- auto arguments = std::array<kstd::format_arg, sizeof...(Args)>{
- kstd::format_arg{&args, kstd::format_dispatcher<std::remove_cvref_t<Args>>}
- ...
- };
- os::vprint(sink, format.str, kstd::format_args{arguments.data(), sizeof...(Args)});
+ auto const arg_store = kstd::make_format_args(args...);
+ os::vprint(sink, format.str_view, arg_store.args);
}
//! @qualifier kernel-defined
@@ -54,7 +46,7 @@ namespace kstd
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
auto println(kstd::format_string<std::type_identity_t<Args>...> format, Args &&... args) -> void
{
- print(format, std::forward<Args>(args)...);
+ print(print_sink::stdout, format, std::forward<Args>(args)...);
print(print_sink::stdout, "\n");
}
diff --git a/libs/kstd/kstd/ranges b/libs/kstd/kstd/ranges
new file mode 100644
index 0000000..78c3adb
--- /dev/null
+++ b/libs/kstd/kstd/ranges
@@ -0,0 +1,21 @@
+#ifndef KSTD_RANGES
+#define KSTD_RANGES
+
+#include <ranges> // IWYU pragma: export
+
+namespace kstd
+{
+#if __glibcxx_ranges_to_container
+ using std::from_range;
+ using std::from_range_t;
+#else
+ struct from_range_t
+ {
+ explicit from_range_t() = default;
+ };
+ constexpr auto inline from_range = from_range_t{};
+#endif
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/stack b/libs/kstd/kstd/stack
index 9750376..02e44ea 100644
--- a/libs/kstd/include/kstd/stack
+++ b/libs/kstd/kstd/stack
@@ -1,7 +1,8 @@
#ifndef KSTD_STACK_HPP
#define KSTD_STACK_HPP
-#include "kstd/vector"
+#include <kstd/vector>
+
#include <initializer_list>
#include <utility>
diff --git a/libs/kstd/kstd/string b/libs/kstd/kstd/string
new file mode 100644
index 0000000..9343b42
--- /dev/null
+++ b/libs/kstd/kstd/string
@@ -0,0 +1,369 @@
+#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>
+
+#include <algorithm>
+#include <compare>
+#include <concepts>
+#include <cstddef>
+#include <string_view>
+
+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<value_type>;
+ //! 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.
+ */
+ explicit string(std::string_view view)
+ : string()
+ {
+ append(view);
+ }
+
+ /**
+ * @brief Constructs a string from a null-terminated C-style string by copying the characters into owned storage.
+ * @param c_str The null-terminated C-style string to copy.
+ */
+ string(char const * c_str)
+ : string()
+ {
+ if (c_str != nullptr)
+ {
+ append(std::string_view{c_str});
+ }
+ }
+
+ /**
+ * @brief Constructs a string containing a single character.
+ * @param c The character to copy.
+ */
+ string(value_type c)
+ : string()
+ {
+ push_back(c);
+ }
+
+ /**
+ * @brief Constructs a string by copying another string.
+ * @param other The string to copy.
+ */
+ constexpr string(string const & other)
+ : m_storage{other.m_storage}
+ {}
+
+ /**
+ * @brief Destructs the string.
+ */
+ constexpr ~string() = default;
+
+ /**
+ * @brief Assigns the value of another string to this string.
+ * @param other The string to assign from.
+ * @return A reference to this string.
+ */
+ constexpr auto operator=(string const & other) -> string & = default;
+
+ constexpr auto operator=(std::string_view const & other) -> string &
+ {
+ clear();
+ append(other);
+ return *this;
+ }
+
+ constexpr auto operator=(char const * other) -> string &
+ {
+ clear();
+ append(std::string_view{other});
+ return *this;
+ }
+
+ //! Create a string view from this string.
+ constexpr operator std::string_view() const noexcept
+ {
+ return {data(), size()};
+ }
+
+ /**
+ * @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())
+ {
+ m_storage.reserve(size() + view.size() + 1);
+ 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(static_cast<std::string_view>(other));
+ }
+
+ /**
+ * @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;
+ }
+
+ //! Compare this string lexicographically to another string.
+ //!
+ //! @param other The string to compare to this one.
+ [[nodiscard]] constexpr auto operator<=>(string const & other) const noexcept -> std::strong_ordering = default;
+
+ [[nodiscard]] constexpr auto operator==(string const & other) const noexcept -> bool = default;
+
+ //! Compare this string lexicographically to a C-style string.
+ //!
+ //! @param other The C-style string to compare to this one.
+ //! @return The result of the comparison.
+ [[nodiscard]] constexpr auto operator<=>(char const * other) const noexcept
+ {
+ return static_cast<std::string_view>(*this) <=> other;
+ }
+
+ //! Check if this string compares equal to a C-style string.
+ //!
+ //! @param other The C-style string to compare to this one.
+ //! @return @p true iff. this string compares equal to @p other, @p false otherwise.
+ [[nodiscard]] constexpr auto operator==(char const * other) const noexcept -> bool
+ {
+ return static_cast<std::string_view>(*this) == other;
+ }
+
+ private:
+ //! The underlying storage of the string, which owns the characters and ensures null-termination.
+ storage_type m_storage{};
+ };
+
+ /**
+ * @brief Concatenates a strings and a character and returns the result as a new string.
+ * @param lhs The string to concatenate.
+ * @param rhs The string to concatenate.
+ * @return A new string that is the result of concatenating @p lhs and @p rhs.
+ */
+ [[nodiscard]] constexpr auto inline operator+(string const & lhs, string const & rhs) -> string
+ {
+ string result{lhs};
+ result += rhs;
+ return result;
+ }
+
+ /**
+ * @brief Converts an unsigned integer to a string by converting each digit to the corresponding character and
+ * concatenating them.
+ * @tparam N The type of the unsigned integer to convert.
+ * @param value The unsigned integer to convert.
+ * @return A string representation of the given unsigned integer.
+ */
+ template<typename N>
+ requires std::unsigned_integral<N>
+ [[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;
+ }
+
+ template<>
+ struct formatter<string> : formatter<std::string_view>
+ {
+ auto format(string const & str, format_context & context) const -> void
+ {
+ formatter<std::string_view>::format(static_cast<std::string_view>(str), context);
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp
new file mode 100644
index 0000000..b81cd3a
--- /dev/null
+++ b/libs/kstd/kstd/string.test.cpp
@@ -0,0 +1,445 @@
+#include <kstd/string>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <algorithm>
+#include <cstring>
+#include <string_view>
+
+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 == 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_FALSE(str.empty());
+ REQUIRE(str.size() == view.size());
+ }
+
+ THEN("the string contains the same characters as the view")
+ {
+ REQUIRE(str == 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_FALSE(str.empty());
+ REQUIRE(str.size() == std::strlen(c_str));
+ }
+
+ THEN("the string contains the same characters as the C-style string")
+ {
+ REQUIRE(str == 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_FALSE(str.empty());
+ REQUIRE(str.size() == 1);
+ }
+
+ THEN("the string contains the same character as the given character")
+ {
+ REQUIRE(str == 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(static_cast<std::string_view>(str) == static_cast<std::string_view>(other));
+ }
+ }
+
+ 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(static_cast<std::string_view>(str) == static_cast<std::string_view>(other));
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+
+ 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 == 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 == "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 == "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 == "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 == "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 == "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 == "Blub!");
+ }
+
+ THEN("the size of the string is one more than the original size")
+ {
+ REQUIRE(str.size() == 5);
+ }
+ }
+ }
+}
+
+SCENARIO("String conversion and comparison", "[string]")
+{
+ GIVEN("An unsigned integer")
+ {
+ constexpr auto value1 = 12345u;
+ constexpr auto value2 = 0u;
+
+ WHEN("converting the unsigned integer to a string")
+ {
+ auto str1 = kstd::to_string(value1);
+ auto str2 = kstd::to_string(value2);
+
+ THEN("the string contains the decimal representation of the unsigned integer")
+ {
+ REQUIRE(str1 == "12345");
+ REQUIRE(str2 == "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_FALSE(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_FALSE(str != view);
+ REQUIRE_FALSE(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 == 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 : static_cast<std::string_view>(str))
+ {
+ result.push_back(ch);
+ }
+
+ THEN("the iterated characters are the same as the characters in the string")
+ {
+ REQUIRE(result == str);
+ }
+ }
+
+ WHEN("using std::ranges::for_each to iterate over the characters of the string")
+ {
+ kstd::string result;
+
+ std::ranges::for_each(str, [&result](auto ch) { result.push_back(ch); });
+
+ THEN("the iterated characters are the same as the characters in the string")
+ {
+ REQUIRE(result == str);
+ }
+ }
+
+ WHEN("using front and back to access the first and last characters of the string")
+ {
+ THEN("front returns the first character of the string")
+ {
+ REQUIRE(str.front() == 'B');
+ }
+
+ THEN("back returns the last character of the string")
+ {
+ REQUIRE(str.back() == 'b');
+ }
+ }
+ }
+
+ GIVEN("A const string")
+ {
+ auto const str = kstd::string{"Blub"};
+
+ WHEN("iterating over the characters of the string as string_view using a range-based for loop")
+ {
+ kstd::string result;
+
+ for (auto ch : static_cast<std::string_view>(str))
+ {
+ result.push_back(ch);
+ }
+
+ THEN("the iterated characters are the same as the characters in the string")
+ {
+ REQUIRE(result == static_cast<std::string_view>(str));
+ }
+ }
+
+ WHEN("using front and back to access the first and last characters of the string")
+ {
+ THEN("front returns the first character of the string")
+ {
+ REQUIRE(str.front() == 'B');
+ }
+
+ THEN("back returns the last character of the string")
+ {
+ REQUIRE(str.back() == 'b');
+ }
+ }
+ }
+
+ GIVEN("An empty string")
+ {
+ auto str = kstd::string{};
+
+ WHEN("iterating over the characters of an empty string")
+ {
+ kstd::string result;
+
+ for (auto ch : static_cast<std::string_view>(str))
+ {
+ result.push_back(ch);
+ }
+
+ THEN("no characters are iterated and the result is an empty string")
+ {
+ REQUIRE(result.empty());
+ REQUIRE(result.size() == 0);
+ REQUIRE(static_cast<std::string_view>(result) == std::string_view{});
+ }
+ }
+ }
+}
diff --git a/libs/kstd/kstd/test_support/os_panic.hpp b/libs/kstd/kstd/test_support/os_panic.hpp
new file mode 100644
index 0000000..4396a9f
--- /dev/null
+++ b/libs/kstd/kstd/test_support/os_panic.hpp
@@ -0,0 +1,23 @@
+#ifndef KSTD_TESTS_OS_PANIC_HPP
+#define KSTD_TESTS_OS_PANIC_HPP
+
+#include <source_location>
+#include <stdexcept>
+#include <string>
+
+namespace kstd::tests
+{
+
+ struct os_panic : std::runtime_error
+ {
+ os_panic(std::string message, std::source_location location)
+ : std::runtime_error{message}
+ , location(location)
+ {}
+
+ std::source_location location;
+ };
+
+} // namespace kstd::tests
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/test_support/os_panic.test.cpp b/libs/kstd/kstd/test_support/os_panic.test.cpp
new file mode 100644
index 0000000..c30411a
--- /dev/null
+++ b/libs/kstd/kstd/test_support/os_panic.test.cpp
@@ -0,0 +1,15 @@
+#include <kstd/test_support/os_panic.hpp>
+
+#include <source_location>
+#include <string>
+#include <string_view>
+
+namespace kstd::os
+{
+
+ auto panic(std::string_view message, std::source_location location)
+ {
+ throw kstd::tests::os_panic{std::string{message}, location};
+ }
+
+} // namespace kstd::os \ No newline at end of file
diff --git a/libs/kstd/kstd/test_support/test_types.hpp b/libs/kstd/kstd/test_support/test_types.hpp
new file mode 100644
index 0000000..8231fd3
--- /dev/null
+++ b/libs/kstd/kstd/test_support/test_types.hpp
@@ -0,0 +1,313 @@
+#ifndef KSTD_TESTS_TEST_TYPES_HPP
+#define KSTD_TESTS_TEST_TYPES_HPP
+
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+
+namespace kstd::tests
+{
+
+ //! A type tracking copy and move operations
+ //!
+ //! This type is designed to test move and copy semantics of standard library containers implemented in kstd.
+ struct special_member_tracker
+ {
+ //! A value indicating that the object was moved from.
+ constexpr auto static moved_from_v = -1;
+
+ constexpr special_member_tracker()
+ : default_constructed_count{1}
+ {}
+
+ //! Construct a new move tracker with the given value, if any.
+ constexpr special_member_tracker(int v = 0)
+ : value{v}
+ , value_constructed_count{1}
+ {}
+
+ //! Construct a new move tracker by copying an existing one.
+ constexpr special_member_tracker(special_member_tracker const & other)
+ : value{other.value}
+ , copy_constructed_count{1}
+ {}
+
+ //! Construct a new move tracker by moving from an existing one.
+ constexpr special_member_tracker(special_member_tracker && other) noexcept
+ : value{other.value}
+ , move_constructed_count{1}
+ {
+ other.value = moved_from_v;
+ }
+
+ //! Copy assign a new move tracker from an existing one.
+ constexpr auto operator=(special_member_tracker const & other) -> special_member_tracker &
+ {
+ if (this != &other)
+ {
+ value = other.value;
+ ++copy_assigned_count;
+ ++other.copied_from_count;
+ }
+ return *this;
+ }
+
+ //! Move assign a new move tracker from an existing one.
+ //!
+ //! This function ensures that the moved-from state is marked.
+ constexpr auto operator=(special_member_tracker && other) noexcept -> special_member_tracker &
+ {
+ if (this != &other)
+ {
+ value = other.value;
+ ++move_assigned_count;
+ other.value = moved_from_v;
+ ++other.moved_from_count;
+ }
+ return *this;
+ }
+
+ ~special_member_tracker()
+ {
+ ++destroyed_count;
+ }
+
+ auto reset_counts() -> void
+ {
+ default_constructed_count = 0;
+ copy_constructed_count = 0;
+ move_constructed_count = 0;
+ value_constructed_count = 0;
+ copy_assigned_count = 0;
+ move_assigned_count = 0;
+ destroyed_count = 0;
+ copied_from_count = 0;
+ moved_from_count = 0;
+ }
+
+ //! A simple value to be able to track the move-from state.
+ int value{};
+ //! A counter to track how many times an instance of this type was default constructed.
+ std::size_t default_constructed_count{0};
+ //! A counter to track how many times an instance of this type was copy constructed.
+ std::size_t copy_constructed_count{0};
+ //! A counter to track how many times an instance of this type was move constructed.
+ std::size_t move_constructed_count{0};
+ //! A counter to track how many times an instance of this type was value constructed.
+ std::size_t value_constructed_count{0};
+ //! A counter to track how many times an instance of this type was copy assigned.
+ std::size_t copy_assigned_count{0};
+ //! A counter to track how many times an instance of this type was move assigned.
+ std::size_t move_assigned_count{0};
+ //! A counter to track how many times an instance of this type was destroyed.
+ std::size_t destroyed_count{0};
+ //! A counter to track how many times an instance of this type was copied from another instance.
+ mutable std::size_t copied_from_count{0};
+ //! A counter to track how many times an instance of this type was moved from another instance.
+ std::size_t moved_from_count{0};
+ };
+
+ //! A type that is not default constructible.
+ //!
+ //! This type is designed to test default construction semantics of standard library containers implemented in kstd.
+ struct non_default_constructible
+ {
+ //! A simple placeholder value.
+ int value{};
+
+ //! Construct a new non-default-constructible object with the given value.
+ constexpr explicit non_default_constructible(int v)
+ : value{v}
+ {}
+
+ //! Compare two non-default-constructible objects for equality.
+ [[nodiscard]] constexpr auto operator==(non_default_constructible const & other) const -> bool
+ {
+ return value == other.value;
+ }
+ };
+
+ //! An allocator that tracks the number of allocations.
+ //!
+ //! This allocator is designed to test allocation semantics of standard library containers implemented in kstd.
+ //!
+ //! @tparam T The type of the elements to be allocated.
+ template<typename T>
+ struct tracking_allocator
+ {
+ using value_type = T;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+
+ //! A pointer to a counter that is incremented on allocation.
+ //!
+ //! A pointer is used so that multiple allocators can share the same counter.
+ int * allocation_count{};
+
+ //! Construct a new tracking allocator referencing the given counter.
+ tracking_allocator(int * counter)
+ : allocation_count{counter}
+ {}
+
+ //! Construct a new tracking allocator by copying from another tracking allocator.
+ //!
+ //! This constructor is templated to allow for conversion from allocators of different types.
+ //!
+ //! @tparam U The type of the elements to be allocated by the other allocator.
+ template<typename U>
+ tracking_allocator(tracking_allocator<U> const & other) noexcept
+ : allocation_count{other.allocation_count}
+ {}
+
+ //! Allocate memory for n elements of type T.
+ //!
+ //! @param n The number of elements to allocate.
+ //! @return A pointer to the allocated memory.
+ [[nodiscard]] auto allocate(std::size_t n) -> T *
+ {
+ if (allocation_count != nullptr)
+ {
+ ++(*allocation_count);
+ }
+ return static_cast<T *>(::operator new(n * sizeof(T)));
+ }
+
+ //! Deallocate memory for n elements of type T.
+ //!
+ //! @param p A pointer to the memory to deallocate.
+ //! @param n The number of elements to deallocate.
+ auto deallocate(T * p, std::size_t n) noexcept -> void
+ {
+ ::operator delete(p, n * sizeof(T));
+ }
+
+ //! Compare two tracking allocators for equality.
+ //!
+ //! Two allocators are considered equal if they reference the same allocation counter.
+ //!
+ //! @param other The other tracking allocator to compare to.
+ //! @return True if the two tracking allocators are equal, false otherwise.
+ [[nodiscard]] auto operator==(tracking_allocator const & other) const -> bool
+ {
+ return allocation_count == other.allocation_count;
+ }
+
+ //! Compare two tracking_allocators for inequality.
+ //!
+ //! @param other The other tracking_allocator to compare to.
+ //! @return True if the two tracking_allocators are not equal, false otherwise.
+ [[nodiscard]] auto operator!=(tracking_allocator const & other) const -> bool
+ {
+ return allocation_count != other.allocation_count;
+ }
+ };
+
+ //! An allocator that propagates copy assignment.
+ //!
+ //! This allocator is designed to test copy assignment semantics of standard library containers implemented in kstd.
+ //!
+ //! @tparam T The type of the elements to be allocated.
+ template<typename T>
+ struct propagating_allocator : tracking_allocator<T>
+ {
+ //! A flag to indicate that the allocator propagates copy assignment.
+ using propagate_on_container_copy_assignment = std::true_type;
+
+ //! Construct a new propagating allocator referencing the given counter.
+ //!
+ //! @param counter A pointer to a counter that is incremented on allocation.
+ //! @see tracking_allocator::tracking_allocator(int*)
+ propagating_allocator(int * counter)
+ : tracking_allocator<T>{counter}
+ {}
+
+ //! Construct a new propagating allocator by copying from another propagating allocator.
+ //!
+ //! This constructor is templated to allow for conversion from allocators of different types.
+ //!
+ //! @tparam U The type of the elements to be allocated by the other allocator.
+ //! @see tracking_allocator::tracking_allocator(tracking_allocator<U> const&)
+ template<typename U>
+ propagating_allocator(propagating_allocator<U> const & other) noexcept
+ : tracking_allocator<T>{other.allocation_count}
+ {}
+ };
+
+ //! A test input iterator.
+ //!
+ //! This iterator is designed to test input iterator semantics of standard library containers implemented in kstd.
+ struct test_input_iterator
+ {
+ using iterator_concept = std::input_iterator_tag;
+ using iterator_category = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = int;
+ using reference = int const &;
+ using pointer = int const *;
+
+ //! The current element pointed to by the iterator.
+ int const * current;
+ //! The number of elements in the range.
+ std::size_t count;
+
+ explicit test_input_iterator()
+ : current{nullptr}
+ , count{0}
+ {}
+
+ //! Construct a new test input iterator.
+ //!
+ //! @param current The current element pointed to by the iterator.
+ //! @param count The number of elements in the range.
+ explicit test_input_iterator(int const * current, std::size_t count)
+ : current{current}
+ , count{count}
+ {}
+
+ //! Dereference the iterator to get the current element.
+ //!
+ //! @return The current element pointed to by the iterator.
+ [[nodiscard]] auto operator*() const -> int
+ {
+ return *current;
+ }
+
+ //! Increment the iterator to point to the next element.
+ //!
+ //! @return A reference to the incremented iterator.
+ auto operator++() -> test_input_iterator &
+ {
+ ++current;
+ --count;
+ return *this;
+ }
+
+ //! Increment the iterator to point to the next element.
+ auto operator++(int) -> void
+ {
+ ++*this;
+ }
+
+ //! Compare two test input iterators for equality.
+ //!
+ //! @param other The other test input iterator to compare to.
+ //! @return True if the two test input iterators are equal, false otherwise.
+ [[nodiscard]] auto operator==(test_input_iterator const & other) const -> bool
+ {
+ if (current == nullptr && other.current == nullptr)
+ {
+ return true;
+ }
+
+ if (current == nullptr || other.current == nullptr)
+ {
+ return count == other.count;
+ }
+
+ return current == other.current && count == other.count;
+ }
+ };
+
+} // namespace kstd::tests
+
+#endif
diff --git a/libs/kstd/kstd/unikstd.h b/libs/kstd/kstd/unikstd.h
new file mode 100644
index 0000000..aa60be6
--- /dev/null
+++ b/libs/kstd/kstd/unikstd.h
@@ -0,0 +1,12 @@
+#ifndef KSTD_UNIKSTD_HPP
+#define KSTD_UNIKSTD_HPP
+
+#include <cstddef>
+#include <type_traits>
+
+namespace kstd
+{
+ using ssize_t = std::make_signed_t<std::size_t>;
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/units b/libs/kstd/kstd/units
new file mode 100644
index 0000000..df5eb37
--- /dev/null
+++ b/libs/kstd/kstd/units
@@ -0,0 +1,149 @@
+#ifndef KSTD_UNITS_HPP
+#define KSTD_UNITS_HPP
+
+#include <compare>
+#include <concepts>
+#include <cstddef>
+
+namespace kstd
+{
+
+ //! A basic template for strongly typed units.
+ template<typename ValueType, typename Tag>
+ struct basic_unit
+ {
+ using value_type = ValueType;
+
+ constexpr basic_unit() noexcept
+ : value{}
+ {}
+
+ explicit constexpr basic_unit(value_type value) noexcept
+ : value{value}
+ {}
+
+ explicit constexpr operator value_type() const noexcept
+ {
+ return value;
+ }
+
+ constexpr auto operator+(basic_unit const & other) const noexcept -> basic_unit
+ {
+ return basic_unit{value + other.value};
+ }
+
+ constexpr auto operator+=(basic_unit const & other) noexcept -> basic_unit &
+ {
+ return *this = *this + other;
+ }
+
+ constexpr auto operator-(basic_unit const & other) const noexcept -> basic_unit
+ {
+ return basic_unit{value - other.value};
+ }
+
+ constexpr auto operator-=(basic_unit const & other) noexcept -> basic_unit &
+ {
+ return *this = *this - other;
+ }
+
+ constexpr auto operator*(std::integral auto factor) noexcept -> basic_unit
+ {
+ return basic_unit{value * factor};
+ }
+
+ constexpr auto operator*=(std::integral auto factor) noexcept -> basic_unit
+ {
+ return *this = *this * factor;
+ }
+
+ constexpr auto operator/(std::integral auto divisor) noexcept -> basic_unit
+ {
+ return basic_unit{value / divisor};
+ }
+
+ constexpr auto operator/=(std::integral auto divisor) noexcept -> basic_unit
+ {
+ return *this = *this / divisor;
+ }
+
+ constexpr auto operator/(basic_unit const & other) const noexcept
+ {
+ return value / other.value;
+ }
+
+ constexpr auto operator<=>(basic_unit const & other) const noexcept -> std::strong_ordering = default;
+
+ value_type value;
+ };
+
+ template<std::integral Factor, typename ValueType, typename Tag>
+ constexpr auto operator*(Factor factor, basic_unit<ValueType, Tag> const & unit) noexcept
+ -> basic_unit<ValueType, Tag>
+ {
+ return basic_unit<ValueType, Tag>{unit.value * factor};
+ }
+
+ namespace units
+ {
+ using bytes = basic_unit<std::size_t, struct bytes_tag>;
+
+ constexpr auto KiB(std::size_t value) noexcept -> bytes
+ {
+ return bytes{value * 1024};
+ }
+
+ constexpr auto MiB(std::size_t value) noexcept -> bytes
+ {
+ return bytes{value * 1024 * 1024};
+ }
+
+ constexpr auto GiB(std::size_t value) noexcept -> bytes
+ {
+ return bytes{value * 1024 * 1024 * 1024};
+ }
+
+ template<typename ValueType>
+ constexpr auto operator+(ValueType * pointer, bytes offset) -> ValueType *
+ {
+ return pointer + offset.value;
+ }
+
+ } // namespace units
+
+ namespace units_literals
+ {
+ constexpr auto operator""_B(unsigned long long value) noexcept -> units::bytes
+ {
+ return units::bytes{value};
+ }
+
+ constexpr auto operator""_KiB(unsigned long long value) noexcept -> units::bytes
+ {
+ return units::KiB(value);
+ }
+
+ constexpr auto operator""_MiB(unsigned long long value) noexcept -> units::bytes
+ {
+ return units::MiB(value);
+ }
+
+ constexpr auto operator""_GiB(unsigned long long value) noexcept -> units::bytes
+ {
+ return units::GiB(value);
+ }
+
+ } // namespace units_literals
+
+ template<typename ValueType>
+ constexpr auto object_size(ValueType const &) -> units::bytes
+ {
+ return units::bytes{sizeof(ValueType)};
+ }
+
+ template<typename T>
+ constexpr auto type_size = units::bytes{sizeof(T)};
+
+} // namespace kstd
+
+#endif
diff --git a/libs/kstd/kstd/vector b/libs/kstd/kstd/vector
new file mode 100644
index 0000000..736b854
--- /dev/null
+++ b/libs/kstd/kstd/vector
@@ -0,0 +1,1079 @@
+#ifndef KSTD_VECTOR_HPP
+#define KSTD_VECTOR_HPP
+
+#include <kstd/allocator>
+#include <kstd/bits/concepts.hpp>
+#include <kstd/os/error.hpp>
+#include <kstd/ranges>
+
+#include <algorithm>
+#include <concepts>
+#include <cstddef>
+#include <initializer_list>
+#include <iterator>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+namespace kstd
+{
+ //! A resizable, contiguous container.
+ //!
+ //! @tparam ValueType The type of values contained in this vector.
+ //! @tparam Allocator The type of allocator used for memory management by this container.
+ template<typename ValueType, typename Allocator = kstd::allocator<ValueType>>
+ struct vector
+ {
+ //! The type of the elements contained in this vector.
+ using value_type = ValueType;
+ //! The allocator used by this vector for memory management.
+ using allocator_type = Allocator;
+ //! The type of all sizes used in and with this vector.
+ using size_type = std::size_t;
+ //! The type of the difference between two iterators.
+ using difference_type = std::ptrdiff_t;
+ //! The type of references to elements in this vector.
+ using reference = value_type &;
+ //! The type of references to constant elements in this vector.
+ using const_reference = value_type const &;
+ //! The type of pointers to elements in this vector.
+ using pointer = std::allocator_traits<allocator_type>::pointer;
+ //! The type of pointers to constant elements in this vector.
+ using const_pointer = std::allocator_traits<allocator_type>::const_pointer;
+ //! The type of iterators into this container.
+ using iterator = pointer;
+ //! The type of constant iterators into this container.
+ using const_iterator = const_pointer;
+ //! The type of reverse iterators into this container.
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ //! The type of constant reverse iterators into this container.
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ //! Construct a new, empty vector.
+ constexpr vector() noexcept(std::is_nothrow_default_constructible_v<allocator_type>)
+ : vector(allocator_type{})
+ {}
+
+ //! Construct a new, empty vector with a given allocator.
+ //!
+ //! @param allocator The allocator to use in the vector.
+ explicit constexpr vector(allocator_type const & allocator) noexcept(
+ std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{0}
+ , m_capacity{0}
+ , m_data{allocate_n(m_capacity)}
+ {}
+
+ //! Construct a new vector and fill it with the given number of default constructed elements.
+ //!
+ //! @param count The number of element to create the vector with.
+ //! @param allocator The allocator to use in the vector.
+ explicit constexpr vector(size_type count, allocator_type const & allocator = allocator_type{}) noexcept(
+ std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{count}
+ , m_capacity{count}
+ , m_data{allocate_n(m_capacity)}
+ {
+ for (auto i = 0uz; i < count; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i);
+ }
+ }
+
+ //! Construct a new vector and fill it with the given number of copy constructed elements.
+ //!
+ //! @param count The number of element to create the vector with.
+ //! @param value The value to copy for each element
+ //! @param allocator The allocator to use in the vector.
+ constexpr vector(size_type count, const_reference value,
+ allocator_type const & allocator =
+ allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{count}
+ , m_capacity{m_size}
+ , m_data{allocate_n(m_capacity)}
+ {
+ for (auto i = 0uz; i < count; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i, value);
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given range.
+ //!
+ //! @tparam ForwardIterator An iterator type used to describe the source range.
+ //! @param first The start of the source range.
+ //! @param last The end of the source range.
+ template<std::forward_iterator ForwardIterator>
+ constexpr vector(ForwardIterator first, ForwardIterator last,
+ allocator_type const & allocator =
+ allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{static_cast<std::size_t>(std::ranges::distance(first, last))}
+ , m_capacity{m_size}
+ , m_data{allocate_n(m_capacity)}
+ {
+ for (auto destination = m_data; first != last; ++first, ++destination)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, destination, *first);
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given range.
+ //!
+ //! @tparam InputIterator An iterator type used to describe the source range.
+ //! @param first The start of the source range.
+ //! @param last The end of the source range.
+ template<std::input_iterator InputIterator>
+ constexpr vector(InputIterator first, InputIterator last,
+ allocator_type const & allocator =
+ allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{0}
+ , m_capacity{0}
+ , m_data{}
+ {
+ while (first != last)
+ {
+ emplace_back(*first);
+ ++first;
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given range.
+ //!
+ //!
+ template<typename Range>
+ requires((std::ranges::forward_range<Range> || std::ranges::sized_range<Range>) &&
+ kstd::bits::container_compatible_range<Range, value_type>)
+ constexpr vector(kstd::from_range_t, Range && range, allocator_type const & allocator = allocator_type{})
+ : m_allocator{allocator}
+ , m_size{std::ranges::size(range)}
+ , m_capacity{m_size}
+ , m_data{allocate_n(m_capacity)}
+ {
+ auto destination = m_data;
+ for (auto && element : std::forward<Range>(range))
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, destination++,
+ std::forward<std::ranges::range_reference_t<Range>>(element));
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements from a given vector.
+ //!
+ //! @param other The source vector.
+ constexpr vector(vector const & other)
+ : m_allocator{other.m_allocator}
+ , m_size{other.m_size}
+ , m_capacity{other.m_capacity}
+ , m_data(allocate_n(m_capacity))
+ {
+ uninitialized_copy_with_allocator(other.begin(), begin(), other.size());
+ }
+
+ //! Construct a new vector and initialize it's content by moving from a given vector.
+ //!
+ //! @param other The source vector.
+ constexpr vector(vector && other) noexcept
+ : m_allocator{std::move(other.m_allocator)}
+ , m_size{std::exchange(other.m_size, size_type{})}
+ , m_capacity(std::exchange(other.m_capacity, size_type{}))
+ , m_data(std::exchange(other.m_data, nullptr))
+ {}
+
+ //! Construct a new vector and initialize it's content by copying from a given vector.
+ //!
+ //! @param other The source vector.
+ //! @param allocator The allocator to use in the vector.
+ constexpr vector(vector const & other, std::type_identity_t<Allocator> const & allocator)
+ : m_allocator{allocator}
+ , m_size{other.m_size}
+ , m_capacity{other.m_capacity}
+ , m_data{allocate_n(m_capacity)}
+ {
+ uninitialized_copy_with_allocator(other.begin(), begin(), other.size());
+ }
+
+ //! Construct a new vector and initialize it's content by copying from a given vector.
+ //!
+ //! @param other The source vector.
+ //! @param allocator The allocator to use in the vector.
+ constexpr vector(vector && other, std::type_identity_t<Allocator> const & allocator)
+ : m_allocator{allocator}
+ , m_size{}
+ , m_capacity{}
+ , m_data{}
+ {
+ if constexpr (!std::allocator_traits<allocator_type>::is_always_equal::value)
+ {
+ if (m_allocator != other.m_allocator)
+ {
+ m_capacity = other.size();
+ m_data = allocate_n(capacity());
+ m_size = other.size();
+ uninitialized_move_with_allocator(other.begin(), begin(), other.size());
+ other.clear();
+ return;
+ }
+ }
+ m_size = std::exchange(other.m_size, size_type{});
+ m_capacity = std::exchange(other.m_capacity, size_type{});
+ m_data = std::exchange(other.m_data, nullptr);
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given initializer list.
+ //!
+ //! @param list The initializer list containing the source objects.
+ vector(std::initializer_list<value_type> list, allocator_type const & allocator = allocator_type{})
+ : vector{std::ranges::begin(list), std::ranges::end(list), allocator}
+ {}
+
+ //! Destroy this vector.
+ constexpr ~vector()
+ {
+ clear_and_deallocate();
+ }
+
+ //! Replace the contents of this vector by the copying from the given source vector.
+ //!
+ //! @param other The source vector.
+ constexpr auto operator=(vector const & other) -> vector &
+ {
+ if (this == std::addressof(other))
+ {
+ return *this;
+ }
+
+ if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value)
+ {
+ if (get_allocator() != other.get_allocator())
+ {
+ clear_and_deallocate();
+ }
+ m_allocator = other.get_allocator();
+ }
+
+ if (capacity() >= other.size())
+ {
+ auto const overlap = std::min(m_size, other.m_size);
+ std::ranges::copy(other.begin(), other.begin() + overlap, begin());
+
+ if (m_size < other.m_size)
+ {
+ uninitialized_copy_with_allocator(other.begin() + size(), begin() + size(), other.m_size - size());
+ }
+ else if (m_size > other.m_size)
+ {
+ destroy_n(begin() + other.size(), size() - other.size());
+ }
+ }
+ else
+ {
+ auto new_data = allocate_n(other.size());
+ uninitialized_copy_with_allocator(other.begin(), new_data, other.size());
+ clear_and_deallocate();
+ m_data = new_data;
+ m_capacity = other.size();
+ }
+
+ m_size = other.size();
+ return *this;
+ }
+
+ //! Replace the contents fo this vector by moving from the given source.
+ //!
+ //! @param other The source vector.
+ constexpr auto operator=(vector && other) noexcept(
+ std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value ||
+ std::allocator_traits<allocator_type>::is_always_equal::value) -> vector &
+ {
+ using std::swap;
+
+ if (this == std::addressof(other))
+ {
+ return *this;
+ }
+
+ if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
+ {
+ clear_and_deallocate();
+ swap(m_allocator, other.m_allocator);
+ swap(m_size, other.m_size);
+ swap(m_capacity, other.m_capacity);
+ swap(m_data, other.m_data);
+ }
+ else if (m_allocator == other.m_allocator)
+ {
+ clear_and_deallocate();
+ swap(m_size, other.m_size);
+ swap(m_capacity, other.m_capacity);
+ swap(m_data, other.m_data);
+ }
+ else
+ {
+ if (capacity() >= other.size())
+ {
+ auto const overlap = std::min(size(), other.size());
+ std::ranges::move(other.begin(), other.begin() + overlap, begin());
+
+ if (size() < other.size())
+ {
+ uninitialized_move_with_allocator(other.begin() + size(), begin() + size(), other.size() - size());
+ }
+ else if (size() > other.size())
+ {
+ destroy_n(begin() + other.size(), size() - other.size());
+ }
+ }
+ else
+ {
+ auto new_data = allocate_n(other.size());
+ uninitialized_move_with_allocator(other.begin(), new_data, other.size());
+ clear_and_deallocate();
+ m_data = new_data;
+ m_capacity = other.m_size;
+ }
+
+ other.destroy_n(other.begin(), other.size());
+ m_size = std::exchange(other.m_size, size_type{});
+ }
+
+ return *this;
+ }
+
+ //! Get a copy of the allocator associated with this vector.
+ [[nodiscard]] constexpr auto get_allocator() const noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ -> allocator_type
+ {
+ return m_allocator;
+ }
+
+ //! Get a reference to the element at the given index.
+ //!
+ //! This function will panic if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto at(size_type index) -> reference
+ {
+ panic_if_out_of_bounds(index);
+ return (*this)[index];
+ }
+
+ //! Get a reference to the element at the given index.
+ //!
+ //! This function will panic if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto at(size_type index) const -> const_reference
+ {
+ panic_if_out_of_bounds(index);
+ return (*this)[index];
+ }
+
+ //! Get a reference to the element at the given index.
+ //!
+ //! The behavior is undefined if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto operator[](size_type index) -> reference
+ {
+ return data()[index];
+ }
+
+ //! Get a reference to the element at the given index.
+ //!
+ //! The behavior is undefined if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto operator[](size_type index) const -> const_reference
+ {
+ return data()[index];
+ }
+
+ //! Get a reference to the first element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto front() -> reference
+ {
+ return *begin();
+ }
+
+ //! Get a reference to the first element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto front() const -> const_reference
+ {
+ return *begin();
+ }
+
+ //! Get a reference to the last element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto back() -> reference
+ {
+ return *rbegin();
+ }
+
+ //! Get a reference to the last element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto back() const -> const_reference
+ {
+ return *rbegin();
+ }
+
+ //! Get a pointer to the beginning of the underlying contiguous storage.
+ [[nodiscard]] constexpr auto data() noexcept -> pointer
+ {
+ return m_data;
+ }
+
+ //! Get a pointer to the beginning of the underlying contiguous storage.
+ [[nodiscard]] constexpr auto data() const noexcept -> const_pointer
+ {
+ return m_data;
+ }
+
+ //! Get an iterator to the first element of this vector.
+ //!
+ //! @return An iterator to the first element of this container, or end() if the container is empty.
+ [[nodiscard]] constexpr auto begin() noexcept -> iterator
+ {
+ return empty() ? end() : data();
+ }
+
+ //! Get an iterator to the first element of this vector.
+ //!
+ //! @return An iterator to the first element of this container, or end() if the container is empty.
+ [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator
+ {
+ return empty() ? end() : data();
+ }
+
+ //! Get an iterator to the first element of this vector.
+ //!
+ //! @return An iterator to the first element of this container, or end() if the container is empty.
+ [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator
+ {
+ return begin();
+ }
+
+ //! Get an iterator past the last element of this vector.
+ [[nodiscard]] constexpr auto end() noexcept -> pointer
+ {
+ return capacity() ? data() + size() : nullptr;
+ }
+
+ //! Get an iterator past the last element of this vector.
+ [[nodiscard]] constexpr auto end() const noexcept -> const_pointer
+ {
+ return capacity() ? data() + size() : nullptr;
+ }
+
+ //! Get an iterator past the last element of this vector.
+ [[nodiscard]] constexpr auto cend() const noexcept -> const_pointer
+ {
+ return end();
+ }
+
+ //! Get a reverse iterator to the reverse beginning.
+ [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator
+ {
+ return empty() ? rend() : reverse_iterator{end()};
+ }
+
+ //! Get a reverse iterator to the reverse beginning.
+ [[nodiscard]] constexpr auto rbegin() const noexcept -> const_reverse_iterator
+ {
+ return empty() ? rend() : const_reverse_iterator{end()};
+ }
+
+ //! Get a reverse iterator to the reverse beginning.
+ [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator
+ {
+ return rbegin();
+ }
+
+ //! Get a reverse iterator to the reverse end.
+ [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator
+ {
+ return reverse_iterator{begin()};
+ }
+
+ //! Get a reverse iterator to the reverse end.
+ [[nodiscard]] constexpr auto rend() const noexcept -> const_reverse_iterator
+ {
+ return const_reverse_iterator{begin()};
+ }
+
+ //! Get a reverse iterator to the reverse end.
+ [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator
+ {
+ return rend();
+ }
+
+ //! Check whether this vector is empty.
+ [[nodiscard]] constexpr auto empty() const noexcept -> bool
+ {
+ return !size();
+ }
+
+ //! Get the number of elements present in this vector.
+ [[nodiscard]] constexpr auto size() const noexcept -> size_type
+ {
+ return m_size;
+ }
+
+ //! Get the maximum possible number of element for this vector.
+ [[nodiscard]] constexpr auto max_size() const noexcept -> size_type
+ {
+ return std::allocator_traits<allocator_type>::max_size(m_allocator);
+ }
+
+ //! Reserve storage for at list the given number of elements.
+ constexpr auto reserve(size_type new_capacity) -> void
+ {
+ if (new_capacity <= capacity())
+ {
+ return;
+ }
+
+ if (new_capacity > max_size())
+ {
+ kstd::os::panic("[kstd:vector] Tried to reserve more space than theoretically possible.");
+ }
+
+ reallocate_exactly(new_capacity);
+ }
+
+ //! Resize this vector to contain @p new_size elements.
+ constexpr auto resize(size_type new_size) -> void
+ {
+ resize(new_size, value_type{});
+ }
+
+ //! Resize this vector to contain @p new_size elements, filling new elements with @p value.
+ constexpr auto resize(size_type new_size, const_reference value) -> void
+ {
+ if (new_size < size())
+ {
+ destroy_n(begin() + new_size, size() - new_size);
+ m_size = new_size;
+ return;
+ }
+
+ if (new_size == size())
+ {
+ return;
+ }
+
+ if (new_size > max_size())
+ {
+ kstd::os::panic("[kstd:vector] Tried to resize more space than theoretically possible.");
+ }
+
+ if (new_size > capacity())
+ {
+ reserve(new_size);
+ }
+
+ for (auto i = size(); i < new_size; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i, value);
+ }
+
+ m_size = new_size;
+ }
+
+ //! Get the number of element this vector has currently space for, including elements currently in this vector.
+ [[nodiscard]] constexpr auto capacity() const noexcept -> size_type
+ {
+ return m_capacity;
+ }
+
+ //! Try to release unused storage space.
+ constexpr auto shrink_to_fit() -> void
+ {
+ if (m_size == m_capacity)
+ {
+ return;
+ }
+
+ reallocate_exactly(m_size);
+ }
+
+ //! Clear the contents of this vector.
+ constexpr auto clear() noexcept -> void
+ {
+ destroy_n(begin(), size());
+ m_size = 0;
+ }
+
+ //! Insert an element at a given position.
+ //!
+ //! @param position The position to insert the element at.
+ //! @param value The value to insert.
+ //! @return An iterator to the inserted element.
+ constexpr auto insert(const_iterator position, value_type const & value) -> iterator
+ {
+ return do_insert(position, value);
+ }
+
+ //! Insert an element at a given position.
+ //!
+ //! @param position The position to insert the element at.
+ //! @param value The value to insert.
+ //! @return An iterator to the inserted element.
+ constexpr auto insert(const_iterator position, value_type && value) -> iterator
+ {
+ return do_insert(position, std::move(value));
+ }
+
+ //! Insert the element of a given range into the vector at a given position.
+ //!
+ //! @param range The source range to insert elements from.
+ //! @tparam SourceRange A container compatible range type.
+ template<typename SourceRange>
+ requires requires(allocator_type allocator, pointer destination, SourceRange range) {
+ requires kstd::bits::container_compatible_range<SourceRange, ValueType>;
+ requires std::move_constructible<value_type>;
+ requires std::is_move_assignable_v<value_type>;
+ requires std::swappable<value_type>;
+ std::allocator_traits<allocator_type>::construct(allocator, destination, *std::ranges::begin(range));
+ }
+ // NOLINTNEXTLINE(misc-no-recursion)
+ constexpr auto insert_range(const_iterator position, SourceRange && range) -> iterator
+ {
+ auto prefix_size = std::ranges::distance(begin(), position);
+
+ if (position == end())
+ {
+ append_range(std::forward<SourceRange>(range));
+ return begin() + prefix_size;
+ }
+
+ if constexpr (std::ranges::forward_range<SourceRange> || std::ranges::sized_range<SourceRange>)
+ {
+ auto number_of_elements = static_cast<size_type>(std::ranges::distance(range));
+ if (!number_of_elements)
+ {
+ return begin() + prefix_size;
+ }
+
+ if (capacity() - size() < number_of_elements)
+ {
+ reserve(size() + std::max(size(), number_of_elements));
+ }
+
+ auto insert_position = begin() + prefix_size;
+ auto suffix_size = static_cast<size_type>(std::ranges::distance(insert_position, end()));
+
+ if (number_of_elements >= suffix_size)
+ {
+ uninitialized_move_with_allocator(insert_position, insert_position + number_of_elements, suffix_size);
+ auto result = std::ranges::copy_n(std::ranges::begin(range), suffix_size, insert_position);
+ uninitialized_copy_with_allocator(std::move(result.in), end(), number_of_elements - suffix_size);
+ }
+ else
+ {
+ uninitialized_move_with_allocator(end() - number_of_elements, end(), number_of_elements);
+ std::ranges::move_backward(insert_position, end() - number_of_elements, end());
+ std::ranges::copy_n(std::ranges::begin(range), number_of_elements, insert_position);
+ }
+
+ m_size += number_of_elements;
+ return insert_position;
+ }
+
+ auto range_begin = std::ranges::begin(range);
+ auto range_end = std::ranges::end(range);
+
+ auto remainder = vector{get_allocator()};
+ for (; range_begin != range_end; ++range_begin)
+ {
+ remainder.emplace_back(*static_cast<std::ranges::iterator_t<SourceRange>>(range_begin));
+ }
+ reserve(size() + std::max(size(), remainder.size()));
+
+ return insert_range(begin() + prefix_size, remainder);
+ }
+
+ template<typename... Args>
+ constexpr auto emplace(const_iterator position, Args &&... args) -> iterator
+ {
+ auto prefix_size = std::ranges::distance(cbegin(), position);
+ if (position == cend())
+ {
+ emplace_back(std::forward<Args>(args)...);
+ }
+ else if (m_capacity == m_size)
+ {
+ reallocate_and_insert(begin() + prefix_size, std::forward<Args>(args)...);
+ }
+ else
+ {
+ auto to_insert = value_type{std::forward<Args>(args)...};
+ auto insert_position = begin() + prefix_size;
+ shift_back(insert_position);
+ *insert_position = std::move(to_insert);
+ ++m_size;
+ }
+
+ return begin() + prefix_size;
+ }
+
+ //! Erase an element at a given position.
+ //!
+ //! @note This function will panic if position == end()
+ //!
+ //! @param position An interator pointing to the element to delete
+ //! @return An iterator pointing to the element after the deleted element
+ constexpr auto erase(const_iterator position) -> iterator
+ {
+ if (position == end())
+ {
+ os::panic("[kstd:vector] Attempted to erase end()!");
+ }
+
+ auto prefix_size = std::ranges::distance(cbegin(), position);
+
+ std::ranges::move(begin() + prefix_size + 1, end(), begin() + prefix_size);
+ std::allocator_traits<allocator_type>::destroy(m_allocator, end() - 1);
+ --m_size;
+
+ return begin() + prefix_size;
+ }
+
+ //! Erase a range of elements from this vector.
+ //!
+ //! @param first The start of the range to erase.
+ //! @param last The end of the range to erase.
+ //! @return An iterator pointing to the element after the last deleted element.
+ constexpr auto erase(const_iterator first, const_iterator last) -> iterator
+ {
+ if (first == last)
+ {
+ return begin() + std::ranges::distance(cbegin(), first);
+ }
+
+ auto prefix_size = std::ranges::distance(cbegin(), first);
+ auto element_count = std::ranges::distance(first, last);
+
+ std::ranges::move(begin() + prefix_size + element_count, end(), begin() + prefix_size);
+ destroy_n(end() - element_count, element_count);
+ m_size -= element_count;
+
+ return begin() + prefix_size;
+ }
+
+ //! Append a given element to this vector via copy construction.
+ constexpr auto push_back(value_type const & value) -> void
+ {
+ emplace_back(value);
+ }
+
+ //! Append a given element to this vector via move construction.
+ constexpr auto push_back(value_type && value) -> void
+ {
+ emplace_back(std::move(value));
+ }
+
+ //! Append a given element to this vector via direct construction.
+ template<class... Args>
+ constexpr auto emplace_back(Args &&... args) -> reference
+ {
+ if (m_capacity == m_size)
+ {
+ reallocate_and_insert(end(), std::forward<Args>(args)...);
+ }
+ else
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, data() + size(), std::forward<Args>(args)...);
+ ++m_size;
+ }
+ return this->back();
+ }
+
+ //! Append the elements of a given range to this vector.
+ //!
+ //! @param range The range of elements to be appended.
+ //! @tparam SourceRange A container compatible range type.
+ template<kstd::bits::container_compatible_range<ValueType> SourceRange>
+ requires requires(Allocator allocator, pointer destination, SourceRange range) {
+ std::allocator_traits<Allocator>::construct(allocator, destination, *std::ranges::begin(range));
+ }
+ // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward, misc-no-recursion)
+ constexpr auto append_range(SourceRange && range) -> void
+ {
+ if constexpr (std::ranges::forward_range<SourceRange> || std::ranges::sized_range<SourceRange>)
+ {
+ auto number_of_elements = static_cast<size_type>(std::ranges::distance(range));
+
+ if (!capacity())
+ {
+ reserve(number_of_elements);
+ }
+
+ if (capacity() - size() >= number_of_elements)
+ {
+ uninitialized_copy_with_allocator(std::ranges::begin(range), end(), number_of_elements);
+ m_size += number_of_elements;
+ return;
+ }
+
+ auto new_capacity = m_capacity + std::max(size(), number_of_elements);
+ auto new_data = allocate_n(new_capacity);
+ auto old_size = size();
+
+ uninitialized_move_with_allocator(begin(), new_data, size());
+ uninitialized_copy_with_allocator(std::ranges::begin(range), new_data + size(), number_of_elements);
+ clear_and_deallocate();
+ m_data = new_data;
+ m_capacity = new_capacity;
+ m_size = old_size + number_of_elements;
+ return;
+ }
+
+ auto range_begin = std::ranges::begin(range);
+ auto range_end = std::ranges::end(range);
+
+ for (auto i = capacity() - size(); i > 0; --i, ++range_begin)
+ {
+ emplace_back(*range_begin);
+ }
+
+ if (range_begin == range_end)
+ {
+ return;
+ }
+
+ auto remainder = vector{get_allocator()};
+ for (; range_begin != range_end; ++range_begin)
+ {
+ remainder.emplace_back(*static_cast<std::ranges::iterator_t<SourceRange>>(range_begin));
+ }
+ reserve(size() + std::max(size(), remainder.size()));
+ append_range(remainder);
+ }
+
+ //! Remove the last element of this vector.
+ //!
+ //! If this vector is empty, the behavior is undefined.
+ constexpr auto pop_back() -> void
+ {
+ --m_size;
+ std::allocator_traits<allocator_type>::destroy(m_allocator, data() + size());
+ }
+
+ private:
+ //! Use the allocator of this vector to allocate enough space for the given number of elements.
+ //!
+ //! @param count The number of elements to allocate space for.
+ [[nodiscard]] constexpr auto allocate_n(std::size_t count) -> std::allocator_traits<allocator_type>::pointer
+ {
+ if (count)
+ {
+ return std::allocator_traits<allocator_type>::allocate(m_allocator, count);
+ }
+ return nullptr;
+ }
+
+ //! Clear this vector and release it's memory.
+ constexpr auto clear_and_deallocate() -> void
+ {
+ clear();
+ deallocate();
+ }
+
+ //! Release the memory of this vector.
+ constexpr auto deallocate()
+ {
+ if (m_data)
+ {
+ std::allocator_traits<allocator_type>::deallocate(m_allocator, m_data, m_capacity);
+ m_capacity = 0;
+ m_size = 0;
+ m_data = nullptr;
+ }
+ }
+
+ //! Insert an element into this vector at the given position.
+ //!
+ //! @param position The position to insert the element at.
+ //! @param value The value to insert.
+ template<typename U>
+ constexpr auto do_insert(const_iterator position, U && value)
+ {
+ auto prefix_size = std::ranges::distance(cbegin(), position);
+ if (position == cend())
+ {
+ push_back(std::forward<U>(value));
+ }
+ else if (m_capacity == m_size)
+ {
+ reallocate_and_insert(begin() + prefix_size, std::forward<U>(value));
+ }
+ else if (&value >= cbegin() && &value < cend())
+ {
+ auto temporary = std::forward<U>(value);
+ shift_back(begin() + prefix_size);
+ *(begin() + prefix_size) = std::move(temporary);
+ ++m_size;
+ }
+ else
+ {
+ shift_back(begin() + prefix_size);
+ *(begin() + prefix_size) = std::forward<U>(value);
+ ++m_size;
+ }
+ return begin() + prefix_size;
+ }
+
+ //! Destroy a number of elements in this vector.
+ //!
+ //! @param first The start of the range of the elements to be destroyed.
+ //! @param count The number of elements to destroy.
+ constexpr auto destroy_n(iterator first, std::size_t count) -> void
+ {
+ std::ranges::for_each(first, first + count, [&](auto & element) {
+ std::allocator_traits<allocator_type>::destroy(m_allocator, std::addressof(element));
+ });
+ }
+
+ //! Panic the kernel if the given index is out of bounds.
+ //!
+ //! @param index The index to check.
+ constexpr auto panic_if_out_of_bounds(size_type index) const -> void
+ {
+ if (index >= m_size)
+ {
+ os::panic("[kstd:vector] Attempted to read element at invalid index");
+ }
+ }
+
+ //! Copy a number of elements from a source range into the uninitialized destination range inside this vector.
+ //!
+ //! @param from The start of the source range.
+ //! @param to The start of the target range inside this vector.
+ //! @param count The number of elements to copy
+ template<typename SourceIterator>
+ constexpr auto uninitialized_copy_with_allocator(SourceIterator from, iterator to, size_type count)
+ {
+ for (auto i = 0uz; i < count; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, to++, *from++);
+ }
+ }
+
+ //! Move a number of elements from a source range into the uninitialized destination range inside this vector.
+ //!
+ //! @param from The start of the source range.
+ //! @param to The start of the target range inside this vector.
+ //! @param count The number of elements to copy
+ template<typename SourceIterator>
+ constexpr auto uninitialized_move_with_allocator(SourceIterator from, iterator to, size_type count)
+ {
+ for (auto i = 0uz; i < count; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, to++, std::move(*from++));
+ }
+ }
+
+ //! Reallocate the storage space to be exactly as large as the given size.
+ constexpr auto reallocate_exactly(size_type new_capacity) -> void
+ {
+ auto new_data = allocate_n(new_capacity);
+ auto old_size = size();
+ uninitialized_move_with_allocator(begin(), new_data, old_size);
+ clear_and_deallocate();
+ m_data = new_data;
+ m_capacity = new_capacity;
+ m_size = old_size;
+ }
+
+ //! Shift all elements, starting the given position, one position back inside the vector.
+ constexpr auto shift_back(iterator starting_at)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, end(), std::move(*(end() - 1)));
+ std::ranges::move_backward(starting_at, end() - 1, end());
+ }
+
+ //! Reallocate the storage of this vector and insert an element at the given position.
+ //!
+ //! @param position The position to insert the element at.
+ //! @param args The constructor arguments for the inserted element.
+ template<typename... Args>
+ constexpr auto reallocate_and_insert(iterator position, Args &&... args)
+ {
+ auto prefix_size = std::ranges::distance(begin(), position);
+ auto suffix_size = std::ranges::distance(position, end());
+ auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2;
+ auto new_data = allocate_n(new_capacity);
+ auto old_size = size();
+
+ std::allocator_traits<allocator_type>::construct(m_allocator, new_data + prefix_size,
+ std::forward<Args>(args)...);
+ uninitialized_move_with_allocator(begin(), new_data, prefix_size);
+ uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size);
+ destroy_n(begin(), old_size);
+ deallocate();
+ m_data = new_data;
+ m_capacity = new_capacity;
+ m_size = old_size + 1;
+ }
+
+ //! The allocator used by this vector.
+ [[no_unique_address]] allocator_type m_allocator{};
+
+ //! The number of elements in this vector.
+ size_type m_size{};
+
+ //! The number of elements this vector has room for.
+ size_type m_capacity{};
+
+ //! The pointer to the start of the memory managed by this vector.
+ value_type * m_data{};
+ };
+
+ //! Check if the content of two vectors is equal.
+ template<typename ValueType, typename Allocator>
+ constexpr auto operator==(vector<ValueType, Allocator> const & lhs, vector<ValueType, Allocator> const & rhs) -> bool
+ {
+ return std::ranges::equal(lhs, rhs);
+ }
+
+ //! Perform a lexicographical comparison of the content of two vectors.
+ template<typename ValueType, typename Allocator>
+ constexpr auto operator<=>(vector<ValueType, Allocator> const & lhs, vector<ValueType, Allocator> const & rhs)
+ -> decltype(std::declval<ValueType const &>() <=> std::declval<ValueType const &>())
+ {
+ return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+ }
+
+ //! Deduction guide for vector construction from an interator pair.
+ template<std::forward_iterator ForwardIterator,
+ typename Allocator = kstd::allocator<typename std::iterator_traits<ForwardIterator>::value_type>>
+ vector(ForwardIterator, ForwardIterator, Allocator = Allocator())
+ -> vector<typename std::iterator_traits<ForwardIterator>::value_type, Allocator>;
+
+ //! Deduction guide for vector construction from an interator pair.
+ template<std::input_iterator InputIterator,
+ typename Allocator = kstd::allocator<typename std::iterator_traits<InputIterator>::value_type>>
+ vector(InputIterator, InputIterator, Allocator = Allocator())
+ -> vector<typename std::iterator_traits<InputIterator>::value_type, Allocator>;
+
+ //! Deduction guide for vector construction from a range.
+ template<std::ranges::input_range Range, typename Allocator = kstd::allocator<std::ranges::range_value_t<Range>>>
+ vector(kstd::from_range_t, Range &&, Allocator = Allocator()) -> vector<std::ranges::range_value_t<Range>, Allocator>;
+
+} // namespace kstd
+
+#endif
diff --git a/libs/kstd/kstd/vector.test.cpp b/libs/kstd/kstd/vector.test.cpp
new file mode 100644
index 0000000..8bf8f79
--- /dev/null
+++ b/libs/kstd/kstd/vector.test.cpp
@@ -0,0 +1,2003 @@
+#include <kstd/vector>
+
+#include <kstd/ranges>
+#include <kstd/test_support/os_panic.hpp>
+#include <kstd/test_support/test_types.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <array>
+#include <iterator>
+#include <ranges>
+#include <utility>
+
+SCENARIO("Vector initialization and construction", "[vector]")
+{
+ GIVEN("An empty context")
+ {
+ WHEN("constructing by default")
+ {
+ auto v = kstd::vector<int>{};
+
+ THEN("the vector is empty")
+ {
+ REQUIRE(v.empty());
+ }
+
+ THEN("the size and capacity are zero")
+ {
+ REQUIRE(v.size() == 0);
+ REQUIRE(v.capacity() == 0);
+ }
+ }
+
+ WHEN("constructing with a specific size")
+ {
+ auto v = kstd::vector<int>(10);
+
+ THEN("the vector is not empty")
+ {
+ REQUIRE_FALSE(v.empty());
+ }
+
+ THEN("the size is and capacity match the specified value")
+ {
+ REQUIRE(v.size() == 10);
+ REQUIRE(v.capacity() == 10);
+ }
+ }
+
+ WHEN("constructing from an initializer list")
+ {
+ auto v = kstd::vector<int>{1, 2, 3, 4, 5};
+
+ THEN("the vector is not empty")
+ {
+ REQUIRE_FALSE(v.empty());
+ }
+
+ THEN("the size is and capacity match the specified value")
+ {
+ REQUIRE(v.size() == 5);
+ REQUIRE(v.capacity() == 5);
+ }
+
+ THEN("the elements are correctly initialized")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ REQUIRE(v[3] == 4);
+ REQUIRE(v[4] == 5);
+ }
+ }
+ }
+
+ GIVEN("A non-empty range")
+ {
+ auto range = std::array<int, 3>{1, 2, 3};
+
+ WHEN("constructing from a random-access iterator range")
+ {
+ auto v = kstd::vector<int>{std::begin(range), std::end(range)};
+
+ THEN("the vector is not empty")
+ {
+ REQUIRE_FALSE(v.empty());
+ }
+
+ THEN("the size and capacity match the range size")
+ {
+ REQUIRE(v.size() == std::size(range));
+ REQUIRE(v.capacity() == std::size(range));
+ }
+
+ THEN("the elements are correctly initialized")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+ }
+
+ WHEN("constructing from a range")
+ {
+ auto v = kstd::vector<int>{kstd::from_range, range};
+
+ THEN("the vector is not empty")
+ {
+ REQUIRE_FALSE(v.empty());
+ }
+
+ THEN("the size and capacity match the range size")
+ {
+ REQUIRE(v.size() == std::ranges::size(range));
+ REQUIRE(v.capacity() == std::ranges::size(range));
+ }
+
+ THEN("the elements are correctly initialized")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+ }
+ }
+
+ GIVEN("A populated vector")
+ {
+ auto source = kstd::vector<int>{1, 2, 3, 4, 5};
+
+ WHEN("copy constructing a new vector")
+ {
+ auto copy = kstd::vector<int>(source);
+
+ THEN("the copy matches the original")
+ {
+ REQUIRE(copy.size() == source.size());
+ REQUIRE(copy.capacity() == source.capacity());
+ REQUIRE(copy[0] == 1);
+ REQUIRE(copy[1] == 2);
+ REQUIRE(copy[2] == 3);
+ REQUIRE(copy[3] == 4);
+ REQUIRE(copy[4] == 5);
+ }
+
+ THEN("the original is left unchanged")
+ {
+ REQUIRE(source.size() == 5);
+ REQUIRE(source.capacity() == 5);
+ REQUIRE(source[0] == 1);
+ REQUIRE(source[1] == 2);
+ REQUIRE(source[2] == 3);
+ REQUIRE(source[3] == 4);
+ REQUIRE(source[4] == 5);
+ }
+ }
+
+ WHEN("move constructing a new vector")
+ {
+ auto moved = kstd::vector<int>(std::move(source));
+
+ THEN("The new vector has the original elements")
+ {
+ REQUIRE(moved.size() == 5);
+ REQUIRE(moved.capacity() == 5);
+ REQUIRE(moved[0] == 1);
+ REQUIRE(moved[1] == 2);
+ REQUIRE(moved[2] == 3);
+ REQUIRE(moved[3] == 4);
+ REQUIRE(moved[4] == 5);
+ }
+
+ THEN("The original vector is left in a valid but unspecified state")
+ {
+ REQUIRE(source.empty());
+ REQUIRE(source.size() == 0);
+ REQUIRE(source.capacity() == 0);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector element access", "[vector]")
+{
+ GIVEN("A populated vector")
+ {
+ auto v = kstd::vector<int>{10, 20, 30};
+
+ WHEN("accessing elements for reading")
+ {
+ THEN("operator[] and at() return the correct elements")
+ {
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+
+ REQUIRE(v.at(0) == 10);
+ REQUIRE(v.at(1) == 20);
+ REQUIRE(v.at(2) == 30);
+ }
+
+ THEN("front() and back() return the first and last elements")
+ {
+ REQUIRE(v.front() == 10);
+ REQUIRE(v.back() == 30);
+ }
+
+ THEN("data() return a pointer to the contiguous storage")
+ {
+ auto ptr = v.data();
+ REQUIRE(ptr);
+ REQUIRE(ptr[0] == 10);
+ REQUIRE(ptr[1] == 20);
+ REQUIRE(ptr[2] == 30);
+ }
+
+ THEN("accessing out of bounds elements panics")
+ {
+ REQUIRE_THROWS_AS(v.at(3), kstd::tests::os_panic);
+ }
+ }
+
+ WHEN("accessing elements for writing")
+ {
+ v[0] = 100;
+ v.at(1) = 200;
+ v.back() = 300;
+
+ THEN("the elements are correctly modified")
+ {
+ REQUIRE(v[0] == 100);
+ REQUIRE(v[1] == 200);
+ REQUIRE(v[2] == 300);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector iterators", "[vector]")
+{
+ GIVEN("A populated vector")
+ {
+ auto v = kstd::vector<int>{1, 2, 3};
+
+ WHEN("using forward iterators")
+ {
+ THEN("they navigate the elements in the correct forward order")
+ {
+ auto it = v.begin();
+ REQUIRE(it != v.end());
+ REQUIRE(*it == 1);
+
+ ++it;
+ REQUIRE(it != v.end());
+ REQUIRE(*it == 2);
+
+ ++it;
+ REQUIRE(it != v.end());
+ REQUIRE(*it == 3);
+
+ ++it;
+ REQUIRE(it == v.end());
+ }
+
+ THEN("const forward iterators provide correct access")
+ {
+ auto it = v.cbegin();
+ REQUIRE(it != v.cend());
+ REQUIRE(*it == 1);
+
+ ++it;
+ REQUIRE(it != v.cend());
+ REQUIRE(*it == 2);
+
+ ++it;
+ REQUIRE(it != v.cend());
+ REQUIRE(*it == 3);
+
+ ++it;
+ REQUIRE(it == v.cend());
+ }
+ }
+
+ WHEN("using reverse iterators")
+ {
+ THEN("they navigate the elements in the correct reverse order")
+ {
+ auto it = v.rbegin();
+ REQUIRE(it != v.rend());
+ REQUIRE(*it == 3);
+
+ ++it;
+ REQUIRE(it != v.rend());
+ REQUIRE(*it == 2);
+
+ ++it;
+ REQUIRE(it != v.rend());
+ REQUIRE(*it == 1);
+
+ ++it;
+ REQUIRE(it == v.rend());
+ }
+
+ THEN("const reverse iterators provide correct access")
+ {
+ auto it = v.crbegin();
+ REQUIRE(it != v.crend());
+ REQUIRE(*it == 3);
+
+ ++it;
+ REQUIRE(it != v.crend());
+ REQUIRE(*it == 2);
+
+ ++it;
+ REQUIRE(it != v.crend());
+ REQUIRE(*it == 1);
+
+ ++it;
+ REQUIRE(it == v.crend());
+ }
+ }
+ }
+
+ GIVEN("an empty vector")
+ {
+ auto v = kstd::vector<int>{};
+
+ WHEN("getting iterators")
+ {
+ THEN("begin() equals end() and cbegin() equals cend()")
+ {
+ REQUIRE(v.begin() == v.end());
+ REQUIRE(v.cbegin() == v.cend());
+ }
+
+ THEN("rbegin() equals rend() and crbegin() equals crend()")
+ {
+ REQUIRE(v.rbegin() == v.rend());
+ REQUIRE(v.crbegin() == v.crend());
+ }
+ }
+ }
+}
+
+SCENARIO("Vector capacity management", "[vector]")
+{
+ GIVEN("An empty vector")
+ {
+ auto v = kstd::vector<int>{};
+
+ WHEN("reserving space")
+ {
+ v.reserve(10);
+
+ THEN("the capacity is at least the reserved amount")
+ {
+ REQUIRE(v.capacity() >= 10);
+ }
+
+ THEN("the size is still zero")
+ {
+ REQUIRE(v.size() == 0);
+ }
+
+ THEN("the vector is still empty")
+ {
+ REQUIRE(v.empty());
+ }
+ }
+
+ WHEN("reserving space less than or equal to current capacity")
+ {
+ v.reserve(10);
+ auto const current_capacity = v.capacity();
+ v.reserve(5);
+
+ THEN("the capacity remains unchanged")
+ {
+ REQUIRE(v.capacity() == current_capacity);
+ }
+ }
+
+ WHEN("reserving space greater than max_size")
+ {
+ THEN("a panic is triggered")
+ {
+ REQUIRE_THROWS_AS(v.reserve(v.max_size() + 1), kstd::tests::os_panic);
+ }
+ }
+ }
+
+ GIVEN("A populated vector with excess capacity")
+ {
+ auto v = kstd::vector<int>{1, 2, 3};
+ v.reserve(10);
+
+ REQUIRE(v.capacity() == 10);
+
+ WHEN("calling shrink_to_fit")
+ {
+ v.shrink_to_fit();
+
+ THEN("the capacity is reduced to match the size")
+ {
+ REQUIRE(v.capacity() == 3);
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the elements remain unchanged")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector modifiers", "[vector]")
+{
+ GIVEN("An empty vector")
+ {
+ auto v = kstd::vector<int>{};
+
+ WHEN("push_back is called with a value")
+ {
+ v.push_back(10);
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v.back() == 10);
+ }
+ }
+
+ WHEN("emplace_back is called with constructor arguments")
+ {
+ v.emplace_back(20);
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v.back() == 20);
+ }
+ }
+
+ WHEN("elements are added while capacity is sufficient")
+ {
+ v.reserve(10);
+ auto const capacity = v.capacity();
+
+ v.push_back(10);
+ v.emplace_back(20);
+
+ THEN("the elements are added without reallocation")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v.capacity() == capacity);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ }
+ }
+
+ WHEN("emplace is called with the end iterator and constructor arguments")
+ {
+ v.emplace(v.end(), 20);
+
+ THEN("the element is appended and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v.back() == 20);
+ }
+ }
+
+ WHEN("emplace is called while capacity is sufficient")
+ {
+ v.reserve(10);
+ auto const capacity = v.capacity();
+
+ v.emplace(v.end(), 20);
+
+ THEN("the element is appended without reallocation")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() == capacity);
+ REQUIRE(v.back() == 20);
+ }
+ }
+
+ WHEN("inserting an element")
+ {
+ auto it = v.insert(v.cbegin(), 40);
+
+ THEN("the size and capacity increase and the element is inserted")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v[0] == 40);
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting an lvalue element")
+ {
+ auto const value = 40;
+ auto it = v.insert(v.cbegin(), value);
+
+ THEN("the size and capacity increase and the element is inserted")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v[0] == 40);
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("appending a range")
+ {
+ auto const range = std::views::iota(0, 3);
+ v.append_range(range);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 3);
+ }
+
+ THEN("the elements are appended")
+ {
+ REQUIRE(v[0] == 0);
+ REQUIRE(v[1] == 1);
+ REQUIRE(v[2] == 2);
+ }
+ }
+
+ WHEN("appending from an input range")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()};
+ auto const last = kstd::tests::test_input_iterator{};
+
+ v.append_range(std::ranges::subrange{first, last});
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 3);
+ }
+
+ THEN("the elements are appended")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+ }
+
+ WHEN("appending from an input range with sufficient capacity")
+ {
+ v.reserve(3);
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()};
+ auto const last = kstd::tests::test_input_iterator{};
+
+ v.append_range(std::ranges::subrange{first, last});
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity stays the same")
+ {
+ REQUIRE(v.capacity() == 3);
+ }
+
+ THEN("the elements are appended")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+ }
+
+ WHEN("resizing the vector to a greater size")
+ {
+ v.resize(3);
+
+ THEN("the size and capacity increase and the elements are value initialized")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v.capacity() >= 3);
+ REQUIRE(v[0] == 0);
+ REQUIRE(v[1] == 0);
+ REQUIRE(v[2] == 0);
+ }
+ }
+
+ WHEN("resizing the vector to a greater size with initial value")
+ {
+ v.resize(3, 2);
+
+ THEN("the size and capacity increase and the elements are initialized to the given value")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v.capacity() >= 3);
+ REQUIRE(v[0] == 2);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 2);
+ }
+ }
+
+ WHEN("inserting a range at the beginning")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto it = v.insert_range(v.begin(), arr);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting a range at the end")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto it = v.insert_range(v.end(), arr);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting from an input range without sufficient capacity")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()};
+ auto const last = kstd::tests::test_input_iterator{};
+ auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last});
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+ }
+
+ GIVEN("A populated vector")
+ {
+ auto v = kstd::vector<int>{10, 20, 30};
+ auto initial_capacity = v.capacity();
+
+ WHEN("push_back is called")
+ {
+ v.push_back(40);
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() >= initial_capacity);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(v[3] == 40);
+ }
+ }
+
+ WHEN("emplace_back is called with constructor arguments")
+ {
+ v.emplace_back(40);
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() >= initial_capacity);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(v[3] == 40);
+ }
+ }
+
+ WHEN("emplace is called with an iterator and constructor arguments")
+ {
+ auto it = v.emplace(v.begin() + 2, 25);
+
+ THEN("the element is inserted and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() >= initial_capacity);
+ REQUIRE(v.at(2) == 25);
+ }
+
+ THEN("the returned iterator points to the inserted element")
+ {
+ REQUIRE(it == v.cbegin() + 2);
+ REQUIRE(*it == 25);
+ }
+ }
+
+ WHEN("emplace is called with an iterator and sufficient capacity")
+ {
+ v.reserve(v.size() + 1);
+
+ auto it = v.emplace(v.begin() + 2, 25);
+
+ THEN("the element is inserted and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() >= initial_capacity);
+ REQUIRE(v.at(2) == 25);
+ }
+
+ THEN("the returned iterator points to the inserted element")
+ {
+ REQUIRE(it == v.cbegin() + 2);
+ REQUIRE(*it == 25);
+ }
+ }
+
+ WHEN("push_back is called with a reference to an internal element")
+ {
+ v.shrink_to_fit();
+ auto const original_value = v[0];
+
+ v.push_back(v[0]);
+
+ THEN("reallocation handles the internal reference safely without dangling")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == original_value);
+ REQUIRE(v.back() == original_value);
+ }
+ }
+
+ WHEN("pop_back is called")
+ {
+ v.pop_back();
+
+ THEN("the last element is removed and the size decreases")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v.capacity() == initial_capacity);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ }
+ }
+
+ WHEN("clear is called")
+ {
+ v.clear();
+
+ THEN("the vector is empty")
+ {
+ REQUIRE(v.empty());
+ REQUIRE(v.size() == 0);
+ REQUIRE(v.capacity() == initial_capacity);
+ }
+ }
+
+ WHEN("inserting at the beginning")
+ {
+ auto it = v.insert(v.cbegin(), 5);
+
+ THEN("the element is inserted at the front")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 5);
+ REQUIRE(v[1] == 10);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting in the middle")
+ {
+ auto it = v.insert(v.cbegin() + 1, 15);
+
+ THEN("the element is inserted in the middle")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 15);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting at the end")
+ {
+ auto it = v.insert(v.cend(), 40);
+
+ THEN("the element is inserted at the back")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(v[3] == 40);
+ REQUIRE(it == v.begin() + 3);
+ }
+ }
+
+ WHEN("inserting an lvalue at the end")
+ {
+ auto const value = 40;
+ auto it = v.insert(v.cend(), value);
+
+ THEN("the element is inserted at the back")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(v[3] == 40);
+ REQUIRE(it == v.begin() + 3);
+ }
+ }
+
+ WHEN("inserting when capacity is sufficient")
+ {
+ v.reserve(10);
+ auto const capacity = v.capacity();
+
+ auto it = v.insert(v.cbegin() + 1, 15);
+
+ THEN("the element is added without reallocation")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() == capacity);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 15);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting a reference to an existing element with reallocation")
+ {
+ v.shrink_to_fit();
+ REQUIRE(v.capacity() == v.size());
+ auto it = v.insert(v.cbegin() + 1, v[2]);
+
+ THEN("the element is correctly copied and inserted")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting a reference to an existing element without reallocation")
+ {
+ v.reserve(10);
+ REQUIRE(v.capacity() > v.size());
+ auto it = v.insert(v.cbegin() + 1, v[2]);
+
+ THEN("the element is correctly copied and inserted")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting an rvalue reference to an existing element with reallocation")
+ {
+ v.shrink_to_fit();
+ REQUIRE(v.capacity() == v.size());
+ auto it = v.insert(v.cbegin() + 1, std::move(v[2]));
+
+ THEN("the element is correctly moved and inserted")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting an rvalue reference to an existing element without reallocation")
+ {
+ v.reserve(10);
+ REQUIRE(v.capacity() > v.size());
+ auto it = v.insert(v.cbegin() + 1, std::move(v[2]));
+
+ THEN("the element is correctly moved and inserted")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("erasing the first element")
+ {
+ auto it = v.erase(v.cbegin());
+
+ THEN("the first element is removed and the size decreases")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v[0] == 20);
+ REQUIRE(v[1] == 30);
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("erasing a middle element")
+ {
+ auto it = v.erase(v.cbegin() + 1);
+
+ THEN("the middle element is removed and the size decreases")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("erasing the last element")
+ {
+ auto it = v.erase(v.cend() - 1);
+
+ THEN("the last element is removed and the size decreases")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(it == v.end());
+ }
+ }
+
+ WHEN("erasing the end() iterator")
+ {
+ THEN("a panic is triggered")
+ {
+ REQUIRE_THROWS_AS(v.erase(v.end()), kstd::tests::os_panic);
+ }
+ }
+
+ WHEN("erasing a range of elements")
+ {
+ auto it = v.erase(v.cbegin() + 1, v.cend() - 1);
+
+ THEN("the specified range is removed and the size decreases")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("erasing an empty range")
+ {
+ auto it = v.erase(v.cbegin() + 1, v.cbegin() + 1);
+
+ THEN("the vector is unchanged")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("appending a range")
+ {
+ auto initial_size = v.size();
+ v.append_range(std::views::iota(0, 3));
+
+ THEN("capacity is increased")
+ {
+ REQUIRE(v.capacity() >= initial_capacity);
+ }
+
+ THEN("size is increased")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ }
+
+ THEN("the elements are appended")
+ {
+ REQUIRE(v[initial_size + 0] == 0);
+ REQUIRE(v[initial_size + 1] == 1);
+ REQUIRE(v[initial_size + 2] == 2);
+ }
+ }
+
+ WHEN("resizing the vector to a greater size")
+ {
+ auto initial_size = v.size();
+ v.resize(initial_size + 3);
+
+ THEN("the size and capacity increase and the elements are value initialized")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ REQUIRE(v.capacity() >= initial_size + 3);
+ REQUIRE(v[initial_size + 0] == 0);
+ REQUIRE(v[initial_size + 1] == 0);
+ REQUIRE(v[initial_size + 2] == 0);
+ }
+ }
+
+ WHEN("resizing the vector to a greater size with initial value")
+ {
+ auto initial_size = v.size();
+ v.resize(initial_size + 3, 2);
+
+ THEN("the size and capacity increase and the elements are initialized to the given value")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ REQUIRE(v.capacity() >= initial_size + 3);
+ REQUIRE(v[initial_size + 0] == 2);
+ REQUIRE(v[initial_size + 1] == 2);
+ REQUIRE(v[initial_size + 2] == 2);
+ }
+ }
+
+ WHEN("resizing the vector to a smaller size")
+ {
+ v.resize(1);
+
+ THEN("the size decreases and the elements are destroyed")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v[0] == 10);
+ }
+ }
+
+ WHEN("inserting an empty range")
+ {
+ auto initial_size = v.size();
+ auto it = v.insert_range(v.begin(), std::views::empty<int>);
+
+ THEN("the size does not change")
+ {
+ REQUIRE(v.size() == initial_size);
+ }
+
+ THEN("the capacity does not change")
+ {
+ REQUIRE(v.capacity() == initial_capacity);
+ }
+
+ THEN("the content is unchanged")
+ {
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ }
+
+ THEN("the returned iterator points to the position of insertion")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting a range at the beginning")
+ {
+ auto initial_size = v.size();
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto it = v.insert_range(v.begin(), arr);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= initial_size + 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ REQUIRE(v[3] == 10);
+ REQUIRE(v[4] == 20);
+ REQUIRE(v[5] == 30);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting a range at the end")
+ {
+ auto initial_size = v.size();
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto it = v.insert_range(v.end(), arr);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= initial_size + 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(v[3] == 1);
+ REQUIRE(v[4] == 2);
+ REQUIRE(v[5] == 3);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin() + initial_size);
+ }
+ }
+
+ WHEN("inserting a range that causes reallocation")
+ {
+ auto initial_size = v.size();
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto it = v.insert_range(v.begin() + 1, arr);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= initial_size + 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 1);
+ REQUIRE(v[2] == 2);
+ REQUIRE(v[3] == 3);
+ REQUIRE(v[4] == 20);
+ REQUIRE(v[5] == 30);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting fewer elements than the suffix without reallocation")
+ {
+ v.reserve(10);
+ auto const capacity = v.capacity();
+ auto const arr = std::array<int, 1>{1};
+ auto it = v.insert_range(v.begin() + 1, arr);
+
+ THEN("the elements are correctly placed")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 1);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ }
+
+ THEN("no reallocation occurs")
+ {
+ REQUIRE(v.capacity() == capacity);
+ }
+
+ THEN("the returned iterator points to the inserted element")
+ {
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting fewer elements than the suffix without sufficient capacity")
+ {
+ v.shrink_to_fit();
+ auto const arr = std::array<int, 1>{1};
+ auto it = v.insert_range(v.begin() + 1, arr);
+
+ THEN("the elements are correctly placed")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 1);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ }
+
+ THEN("the returned iterator points to the inserted element")
+ {
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting from an input range without sufficient capacity")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()};
+ auto const last = kstd::tests::test_input_iterator{};
+ auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last});
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 6);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 6);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ REQUIRE(v[3] == 10);
+ REQUIRE(v[4] == 20);
+ REQUIRE(v[5] == 30);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+ }
+}
+
+SCENARIO("Vector comparison", "[vector]")
+{
+ GIVEN("Two identical vectors")
+ {
+ auto v1 = kstd::vector<int>{1, 2, 3};
+ auto v2 = kstd::vector<int>{1, 2, 3};
+
+ WHEN("comparing for equality")
+ {
+ THEN("the vectors are equal")
+ {
+ REQUIRE(v1 == v2);
+ }
+
+ THEN("the vectors and not not-equal")
+ {
+ REQUIRE_FALSE(v1 != v2);
+ }
+ }
+
+ WHEN("comparing using the spaceship operator")
+ {
+ THEN("the vectors are equivalent")
+ {
+ REQUIRE((v1 <=> v2) == 0);
+ REQUIRE(v1 <= v2);
+ REQUIRE(v1 >= v2);
+ }
+ }
+ }
+
+ GIVEN("Two vectors of different sizes")
+ {
+ auto v1 = kstd::vector<int>{1, 2, 3};
+ auto v2 = kstd::vector<int>{1, 2, 3, 4};
+
+ WHEN("comparing for equality")
+ {
+ THEN("the vectors are not equal")
+ {
+ REQUIRE_FALSE(v1 == v2);
+ }
+
+ THEN("the vectors are not-equal")
+ {
+ REQUIRE(v1 != v2);
+ }
+ }
+
+ WHEN("comparing for ordering")
+ {
+ THEN("the shorter vector evaluates as less than the longer vector")
+ {
+ REQUIRE(v1 < v2);
+ REQUIRE(v2 > v1);
+ }
+ }
+ }
+
+ GIVEN("Two vectors of the same size but different elements")
+ {
+ auto v1 = kstd::vector<int>{1, 2, 3};
+ auto v2 = kstd::vector<int>{1, 2, 4};
+
+ WHEN("comparing for ordering")
+ {
+ THEN("they are ordered lexicographically")
+ {
+ REQUIRE(v1 < v2);
+ REQUIRE(v2 > v1);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector with non-default-constructible types", "[vector]")
+{
+ GIVEN("A type without a default constructor")
+ {
+ WHEN("constructing an empty vector")
+ {
+ auto v = kstd::vector<kstd::tests::non_default_constructible>{};
+
+ THEN("the vector is empty")
+ {
+ REQUIRE(v.empty());
+ }
+ }
+
+ WHEN("using emplace_back")
+ {
+ auto v = kstd::vector<kstd::tests::non_default_constructible>{};
+ v.emplace_back(40);
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.back() == kstd::tests::non_default_constructible{40});
+ }
+ }
+ }
+}
+
+SCENARIO("Vector with custom allocator", "[vector]")
+{
+ GIVEN("a tracking allocator acting as the vector's memory manager")
+ {
+ auto allocations = 0;
+ auto allocator = kstd::tests::tracking_allocator<int>{&allocations};
+
+ WHEN("a vector uses this allocator to allocate memory")
+ {
+ auto v = kstd::vector<int, kstd::tests::tracking_allocator<int>>(allocator);
+ REQUIRE(allocations == 0);
+
+ v.reserve(10);
+
+ THEN("the allocator was used to allocate memory")
+ {
+ REQUIRE(allocations > 0);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector modifier move semantics", "[vector]")
+{
+ GIVEN("An empty vector and a move tracker element")
+ {
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
+ auto tracker = kstd::tests::special_member_tracker{40};
+
+ WHEN("push_back is called with the move tracker")
+ {
+ v.push_back(std::move(tracker));
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.back().move_constructed_count == 1);
+ REQUIRE(v.back().copy_constructed_count == 0);
+ REQUIRE(v.back().value == 40);
+ }
+
+ THEN("the original tracker is left in a valid but unspecified state")
+ {
+ REQUIRE(tracker.value == -1);
+ }
+ }
+ }
+
+ GIVEN("An empty vector")
+ {
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
+
+ WHEN("emplace_back is called with constructor arguments")
+ {
+ v.emplace_back(40);
+
+ THEN("the element is constructed directly, without moves or copies")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.back().move_constructed_count == 0);
+ REQUIRE(v.back().copy_constructed_count == 0);
+ REQUIRE(v.back().value == 40);
+ }
+ }
+ }
+
+ GIVEN("A populated vector of move trackers")
+ {
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
+ v.reserve(10);
+ v.emplace_back(10);
+ v.emplace_back(20);
+ v.emplace_back(30);
+
+ WHEN("inserting an element in the middle with sufficient capacity")
+ {
+ auto const tracker = kstd::tests::special_member_tracker{15};
+ v.insert(v.cbegin() + 1, tracker);
+
+ THEN("the shifted elements are move-assigned and the new element is copy-assigned")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0].value == 10);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
+ REQUIRE(v[1].value == 15);
+ REQUIRE(v[1].move_assigned_count == 0);
+ REQUIRE(v[1].copy_assigned_count == 1);
+ REQUIRE(tracker.copied_from_count == 1);
+ REQUIRE(v[2].value == 20);
+ REQUIRE(v[2].move_assigned_count == 1);
+ REQUIRE(v[2].copy_assigned_count == 0);
+ REQUIRE(v[3].value == 30);
+ REQUIRE(v[3].move_constructed_count == 1);
+ }
+ }
+
+ WHEN("inserting an rvalue element in the middle with sufficient capacity")
+ {
+ auto tracker = kstd::tests::special_member_tracker{15};
+ v.insert(v.cbegin() + 1, std::move(tracker));
+
+ THEN("the shifted elements are move-assigned and the new element is move-assigned")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0].value == 10);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
+ REQUIRE(v[1].value == 15);
+ REQUIRE(v[1].move_assigned_count == 1);
+ REQUIRE(v[1].copy_assigned_count == 0);
+ REQUIRE(tracker.moved_from_count == 1);
+ REQUIRE(v[2].value == 20);
+ REQUIRE(v[2].move_assigned_count == 1);
+ REQUIRE(v[3].value == 30);
+ REQUIRE(v[3].move_constructed_count == 1);
+ }
+ }
+
+ WHEN("erasing an element in the middle")
+ {
+ for (auto & elem : v)
+ {
+ elem.reset_counts();
+ }
+
+ auto it = v.erase(v.cbegin() + 1);
+
+ THEN("the subsequent elements are move-assigned leftwards")
+ {
+ REQUIRE(v.size() == 2);
+
+ REQUIRE(v[0].value == 10);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
+
+ REQUIRE(v[1].value == 30);
+ REQUIRE(v[1].move_assigned_count == 1);
+ REQUIRE(v[1].copy_assigned_count == 0);
+
+ REQUIRE(v.data()[2].destroyed_count == 1);
+ REQUIRE(v.data()[2].moved_from_count == 1);
+
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("erasing the last element")
+ {
+ for (auto & elem : v)
+ {
+ elem.reset_counts();
+ }
+
+ auto it = v.erase(v.cend() - 1);
+
+ THEN("no elements are moved, just the last element destroyed")
+ {
+ REQUIRE(v.size() == 2);
+
+ REQUIRE(v[0].value == 10);
+ REQUIRE(v[0].move_constructed_count == 0);
+ REQUIRE(v[0].copy_constructed_count == 0);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
+
+ REQUIRE(v[1].value == 20);
+ REQUIRE(v[1].move_constructed_count == 0);
+ REQUIRE(v[1].copy_constructed_count == 0);
+ REQUIRE(v[1].move_assigned_count == 0);
+ REQUIRE(v[1].copy_assigned_count == 0);
+
+ REQUIRE(v.data()[2].destroyed_count == 1);
+ REQUIRE(v.data()[2].moved_from_count == 0);
+ REQUIRE(v.data()[2].copied_from_count == 0);
+
+ REQUIRE(it == v.end());
+ }
+ }
+
+ WHEN("erasing a range of elements in the middle")
+ {
+ v.emplace_back(40);
+ v.emplace_back(50);
+
+ for (auto & elem : v)
+ {
+ elem.reset_counts();
+ }
+
+ auto it = v.erase(v.cbegin() + 1, v.cbegin() + 3);
+
+ THEN("the specified elements are destroyed and subsequent elements are move-assigned leftwards")
+ {
+ REQUIRE(v.size() == 3);
+
+ REQUIRE(v[0].value == 10);
+ REQUIRE(v[0].move_constructed_count == 0);
+ REQUIRE(v[0].copy_constructed_count == 0);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
+
+ REQUIRE(v[1].value == 40);
+ REQUIRE(v[1].move_constructed_count == 0);
+ REQUIRE(v[1].copy_constructed_count == 0);
+ REQUIRE(v[1].move_assigned_count == 1);
+ REQUIRE(v[1].copy_assigned_count == 0);
+
+ REQUIRE(v[2].value == 50);
+ REQUIRE(v[2].move_constructed_count == 0);
+ REQUIRE(v[2].copy_constructed_count == 0);
+ REQUIRE(v[2].move_assigned_count == 1);
+ REQUIRE(v[2].copy_assigned_count == 0);
+
+ REQUIRE(v.data()[3].destroyed_count == 1);
+ REQUIRE(v.data()[3].moved_from_count == 1);
+ REQUIRE(v.data()[3].copied_from_count == 0);
+
+ REQUIRE(v.data()[4].destroyed_count == 1);
+ REQUIRE(v.data()[4].moved_from_count == 1);
+ REQUIRE(v.data()[4].copied_from_count == 0);
+
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector advanced construction", "[vector]")
+{
+ GIVEN("A count and a default value")
+ {
+ WHEN("constructing with count and default argument")
+ {
+ auto const count = 5uz;
+ auto const value = 42;
+ auto v = kstd::vector<int>(count, value);
+
+ THEN("the vector is initialized with count copies of value")
+ {
+ REQUIRE(v.size() == 5);
+ REQUIRE(v.capacity() == 5);
+ REQUIRE(v.front() == 42);
+ REQUIRE(v.back() == 42);
+ }
+ }
+ }
+
+ GIVEN("A pure input iterator range")
+ {
+ WHEN("constructing from input iterators")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()};
+ auto const last = kstd::tests::test_input_iterator{};
+
+ auto v = kstd::vector<int>(first, last);
+
+ THEN("the vector is generated dynamically and initialized correctly")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[2] == 3);
+ }
+ }
+ }
+
+ GIVEN("A tracking allocator and a source vector")
+ {
+ auto allocs = 0;
+ auto allocator = kstd::tests::tracking_allocator<int>{&allocs};
+ auto source = kstd::vector<int, kstd::tests::tracking_allocator<int>>{allocator};
+ source.push_back(1);
+ source.push_back(2);
+ source.push_back(3);
+
+ allocs = 0;
+
+ WHEN("copy constructing with an allocator")
+ {
+ auto copy = kstd::vector<int, kstd::tests::tracking_allocator<int>>(source, allocator);
+
+ THEN("the copy succeeds and the allocator is used")
+ {
+ REQUIRE(copy.size() == 3);
+ REQUIRE(allocs > 0);
+ REQUIRE(copy[0] == 1);
+ REQUIRE(copy[2] == 3);
+ }
+ }
+
+ WHEN("move constructing with an identically comparing allocator")
+ {
+ auto moved = kstd::vector<int, kstd::tests::tracking_allocator<int>>(std::move(source), allocator);
+
+ THEN("the move succeeds and no new allocations are made (memory is stolen)")
+ {
+ REQUIRE(moved.size() == 3);
+ REQUIRE(allocs == 0);
+ REQUIRE(moved[0] == 1);
+ REQUIRE(moved[2] == 3);
+ }
+ }
+
+ WHEN("move constructing with a non-equal allocator")
+ {
+ auto allocs2 = 0;
+ auto allocator2 = kstd::tests::tracking_allocator<int>{&allocs2};
+
+ auto moved = kstd::vector<int, kstd::tests::tracking_allocator<int>>(std::move(source), allocator2);
+
+ THEN("the move allocates new memory and moves elements")
+ {
+ REQUIRE(allocs2 > 0);
+ REQUIRE(moved.size() == 3);
+ REQUIRE(source.empty());
+ }
+ }
+ }
+}
+
+SCENARIO("Vector assignment operators", "[vector]")
+{
+ GIVEN("A source vector and an empty target vector")
+ {
+ auto source = kstd::vector<int>{1, 2, 3};
+ auto target = kstd::vector<int>{};
+
+ WHEN("copy assigning")
+ {
+ target = source;
+
+ THEN("the target matches the source")
+ {
+ REQUIRE(target.size() == 3);
+ REQUIRE(target[0] == 1);
+ REQUIRE(target[2] == 3);
+ REQUIRE(source.size() == 3);
+ }
+ }
+
+ WHEN("move assigning")
+ {
+ target = std::move(source);
+
+ THEN("the target assumes the source's data")
+ {
+ REQUIRE(target.size() == 3);
+ REQUIRE(target[0] == 1);
+ REQUIRE(target[2] == 3);
+ REQUIRE(source.empty());
+ }
+ }
+ }
+
+ GIVEN("Vectors with propagating copy allocator")
+ {
+ auto allocs1 = 0;
+ auto allocs2 = 0;
+ auto alloc1 = kstd::tests::propagating_allocator<int>{&allocs1};
+ auto alloc2 = kstd::tests::propagating_allocator<int>{&allocs2};
+
+ auto v1 = kstd::vector<int, kstd::tests::propagating_allocator<int>>{alloc1};
+ v1.push_back(1);
+ v1.push_back(2);
+ auto v2 = kstd::vector<int, kstd::tests::propagating_allocator<int>>{alloc2};
+
+ WHEN("copy assigning")
+ {
+ v2 = v1;
+ THEN("the allocator propagates")
+ {
+ REQUIRE(v2.get_allocator() == v1.get_allocator());
+ }
+ }
+ }
+
+ GIVEN("Vectors for copy assignment overlap")
+ {
+ auto v1 = kstd::vector<int>{1, 2, 3};
+ auto v2 = kstd::vector<int>{4, 5};
+ v2.reserve(10);
+
+ WHEN("copy assigning a larger vector to a smaller one with enough capacity")
+ {
+ v2 = v1;
+ THEN("elements are copied and size is updated")
+ {
+ REQUIRE(v2.size() == 3);
+ REQUIRE(v2.capacity() >= 10);
+ REQUIRE(v2[2] == 3);
+ }
+ }
+
+ auto v3 = kstd::vector<int>{1, 2, 3, 4};
+ v3.reserve(10);
+ WHEN("copy assigning a smaller vector to a larger one")
+ {
+ v3 = v1;
+ THEN("excess elements are destroyed")
+ {
+ REQUIRE(v3.size() == 3);
+ REQUIRE(v3[0] == 1);
+ }
+ }
+ }
+
+ GIVEN("Vectors with the same tracking allocator")
+ {
+ auto allocs = 0;
+ auto alloc = kstd::tests::tracking_allocator<int>{&allocs};
+ auto v1 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc};
+ v1.push_back(1);
+ auto v2 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc};
+
+ WHEN("move assigning")
+ {
+ v2 = std::move(v1);
+ THEN("memory is stolen without allocation")
+ {
+ REQUIRE(v2.size() == 1);
+ REQUIRE(allocs == 1);
+ }
+ }
+ }
+
+ GIVEN("Vectors with different non-propagating tracking allocators")
+ {
+ auto allocs1 = 0;
+ auto allocs2 = 0;
+ auto alloc1 = kstd::tests::tracking_allocator<int>{&allocs1};
+ auto alloc2 = kstd::tests::tracking_allocator<int>{&allocs2};
+
+ auto v1 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc1};
+ v1.push_back(1);
+ v1.push_back(2);
+ v1.push_back(3);
+
+ auto v2 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc2};
+ v2.push_back(4);
+ v2.push_back(5);
+
+ WHEN("move assigning a larger vector to a smaller one without enough capacity")
+ {
+ v2.shrink_to_fit();
+ v2 = std::move(v1);
+ THEN("memory is reallocated and elements are moved")
+ {
+ REQUIRE(v2.size() == 3);
+ REQUIRE(allocs2 > 2);
+ }
+ }
+
+ auto v3 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc2};
+ v3.reserve(10);
+ v3.push_back(4);
+ v3.push_back(5);
+ WHEN("move assigning a larger vector to a smaller one with enough capacity")
+ {
+ v3 = std::move(v1);
+ THEN("elements are move-assigned over overlap and move-constructed over remainder")
+ {
+ REQUIRE(v3.size() == 3);
+ }
+ }
+
+ auto v4 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc2};
+ v4.reserve(10);
+ v4.push_back(4);
+ v4.push_back(5);
+ v4.push_back(6);
+ v4.push_back(7);
+ WHEN("move assigning a smaller vector to a larger one with enough capacity")
+ {
+ v4 = std::move(v1);
+ THEN("overlap is moved and excess is destroyed")
+ {
+ REQUIRE(v4.size() == 3);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector self-assignment operators", "[vector]")
+{
+ GIVEN("A populated vector")
+ {
+ auto v = kstd::vector<int>{1, 2, 3};
+ auto const initial_capacity = v.capacity();
+ auto const * initial_data = v.data();
+
+ WHEN("copy assigning to itself")
+ {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wunknown-warning-option"
+#pragma GCC diagnostic ignored "-Wself-assign-overloaded"
+ v = v;
+#pragma GCC diagnostic pop
+
+ THEN("the vector remains unchanged")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v.capacity() == initial_capacity);
+ REQUIRE(v.data() == initial_data);
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[2] == 3);
+ }
+ }
+
+ WHEN("move assigning to itself")
+ {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wunknown-warning-option"
+#pragma GCC diagnostic ignored "-Wself-move"
+ v = std::move(v);
+#pragma GCC diagnostic pop
+
+ THEN("the vector remains unchanged")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v.capacity() == initial_capacity);
+ REQUIRE(v.data() == initial_data);
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[2] == 3);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector const accessors and copy insertion", "[vector]")
+{
+ GIVEN("A const populated vector")
+ {
+ auto const v = kstd::vector<int>{10, 20, 30};
+
+ WHEN("calling const accessors")
+ {
+ THEN("elements are read correctly as const references")
+ {
+ REQUIRE(v.front() == 10);
+ REQUIRE(v.back() == 30);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v.at(1) == 20);
+ }
+ }
+ }
+
+ GIVEN("An empty vector and a const lvalue tracker")
+ {
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
+ auto const tracker = kstd::tests::special_member_tracker{42};
+
+ WHEN("push_back is called with the const lvalue")
+ {
+ v.push_back(tracker);
+
+ THEN("the element is gracefully copy-constructed")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.back().value == 42);
+ REQUIRE(v.back().copy_constructed_count == 1);
+ REQUIRE(v.back().move_constructed_count == 0);
+ }
+ }
+
+ WHEN("push_back is called with a const lvalue when capacity is sufficient")
+ {
+ v.reserve(10);
+ auto const current_capacity = v.capacity();
+ v.push_back(tracker);
+
+ THEN("the element is copy-constructed without reallocation")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() == current_capacity);
+ REQUIRE(v.back().value == 42);
+ REQUIRE(v.back().copy_constructed_count == 1);
+ REQUIRE(v.back().move_constructed_count == 0);
+ }
+ }
+ }
+}
diff --git a/libs/kstd/kstd/vformat.cpp b/libs/kstd/kstd/vformat.cpp
new file mode 100644
index 0000000..b7c5121
--- /dev/null
+++ b/libs/kstd/kstd/vformat.cpp
@@ -0,0 +1,209 @@
+#include <kstd/format>
+#include <kstd/string>
+
+#include <cstddef>
+#include <iterator>
+#include <string_view>
+#include <utility>
+
+namespace kstd::bits::format
+{
+
+ auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void
+ {
+ auto context = kstd::format_context{buffer, args};
+ auto parse_context = kstd::format_parse_context{format, args.size()};
+
+ auto it = parse_context.begin();
+ auto end = parse_context.end();
+
+ while (it != end)
+ {
+ if (*it != '{' && *it != '}')
+ {
+ auto start = it;
+ while (it != end && *it != '{' && *it != '}')
+ {
+ std::advance(it, 1);
+ }
+ parse_context.advance_to(it);
+ context.push(std::string_view(start, it - start));
+ continue;
+ }
+
+ if (*it == '{')
+ {
+ std::advance(it, 1);
+ if (it != end && *it == '{')
+ {
+ context.push('{');
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ continue;
+ }
+
+ parse_context.advance_to(it);
+ auto index = 0uz;
+
+ if (it != end && *it >= '0' && *it <= '9')
+ {
+ while (it != end && *it >= '0' && *it <= '9')
+ {
+ index = index * 10 + static_cast<std::size_t>(*it - '0');
+ std::advance(it, 1);
+ }
+ parse_context.check_arg_id(index);
+ }
+ else
+ {
+ index = parse_context.next_arg_id();
+ }
+
+ if (it != end && *it == ':')
+ {
+ std::advance(it, 1);
+ }
+
+ parse_context.advance_to(it);
+
+ if (index < args.size())
+ {
+ auto const & arg = args[index];
+ switch (arg.type)
+ {
+ case kstd::bits::format::arg_type::boolean:
+ {
+ auto fmt = kstd::formatter<bool>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.boolean, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::character:
+ {
+ auto fmt = kstd::formatter<char>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.character, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::integer:
+ {
+ auto fmt = kstd::formatter<long long>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.integer, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::unsigned_integer:
+ {
+ auto fmt = kstd::formatter<unsigned long long>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.unsigned_integer, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::string_view:
+ {
+ auto fmt = kstd::formatter<std::string_view>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.string_view, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::c_string:
+ {
+ auto fmt = kstd::formatter<char const *>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.c_string, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::pointer:
+ {
+ auto fmt = kstd::formatter<void const *>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.pointer, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::user_defined:
+ {
+ if (arg.value.user_defined.format)
+ {
+ arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context);
+ }
+ else
+ {
+ context.push("{?}");
+ }
+ break;
+ }
+ default:
+ {
+ context.push("{fmt-err: unknown-type}");
+ break;
+ }
+ }
+ }
+ else
+ {
+ context.push("{fmt-err: bound}");
+ }
+
+ it = parse_context.begin();
+
+ if (it != end && *it == '}')
+ {
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ }
+ else
+ {
+ context.push("{fmt-err: unconsumed}");
+ while (it != end && *it != '}')
+ {
+ std::advance(it, 1);
+ }
+
+ if (it != end)
+ {
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ }
+ }
+ }
+ else if (*it == '}')
+ {
+ std::advance(it, 1);
+ if (it != end && *it == '}')
+ {
+ context.push('}');
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ }
+ else
+ {
+ context.push("{fmt-err: unescaped}");
+ parse_context.advance_to(it);
+ }
+ }
+ }
+ }
+
+ auto string_writer::push(std::string_view text) -> void
+ {
+ m_result.append(text);
+ }
+
+ auto string_writer::push(char character) -> void
+ {
+ m_result.push_back(character);
+ }
+
+ auto string_writer::release() -> string &&
+ {
+ return std::move(m_result);
+ }
+
+} // namespace kstd::bits::format \ No newline at end of file
diff --git a/libs/kstd/src/libc/string.cpp b/libs/kstd/src/libc/string.cpp
deleted file mode 100644
index b7d4c6b..0000000
--- a/libs/kstd/src/libc/string.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#include <algorithm>
-#include <bit>
-#include <cstddef>
-#include <iterator>
-#include <span>
-
-namespace kstd::libc
-{
-
- extern "C"
- {
- auto strlen(char const * string) -> std::size_t
- {
- return std::distance(string, std::ranges::find(string, nullptr, '\0'));
- }
-
- auto memcmp(void const * lhs, void const * rhs, std::size_t size) -> std::size_t
- {
- auto left_span = std::span{static_cast<std::byte const *>(lhs), size};
- auto right_span = std::span{static_cast<std::byte const *>(rhs), size};
- auto mismatched = std::ranges::mismatch(left_span, right_span);
-
- if (mismatched.in1 == left_span.end())
- {
- return 0;
- }
-
- return std::bit_cast<char>(*mismatched.in1) - std::bit_cast<char>(*mismatched.in2);
- }
-
- auto memmove(void * dest, void const * src, std::size_t size) -> void *
- {
- auto dest_span = std::span{static_cast<std::byte *>(dest), size};
- auto src_span = std::span{static_cast<std::byte const *>(src), size};
- if (dest < src)
- {
- for (std::size_t i = 0; i < size; ++i)
- {
- dest_span[i] = src_span[i];
- }
- }
- else
- {
- for (std::size_t i = size; i > 0; --i)
- {
- dest_span[i - 1] = src_span[i - 1];
- }
- }
- return dest;
- }
- }
-
-} // namespace kstd::libc \ No newline at end of file