From a141c401f78467bc15f62882fca5d55a007cacbb Mon Sep 17 00:00:00 2001 From: Steven Le Rouzic Date: Mon, 17 Feb 2025 00:21:48 +0100 Subject: Reorganize everything --- asl/types/BUILD.bazel | 135 +++++++++++ asl/types/box.hpp | 134 +++++++++++ asl/types/box_tests.cpp | 102 ++++++++ asl/types/maybe_uninit.hpp | 72 ++++++ asl/types/maybe_uninit_tests.cpp | 22 ++ asl/types/option.hpp | 509 +++++++++++++++++++++++++++++++++++++++ asl/types/option_tests.cpp | 330 +++++++++++++++++++++++++ asl/types/span.hpp | 235 ++++++++++++++++++ asl/types/span_tests.cpp | 444 ++++++++++++++++++++++++++++++++++ asl/types/status.cpp | 96 ++++++++ asl/types/status.hpp | 157 ++++++++++++ asl/types/status_or.hpp | 186 ++++++++++++++ asl/types/status_or_tests.cpp | 84 +++++++ asl/types/status_tests.cpp | 82 +++++++ 14 files changed, 2588 insertions(+) create mode 100644 asl/types/BUILD.bazel create mode 100644 asl/types/box.hpp create mode 100644 asl/types/box_tests.cpp create mode 100644 asl/types/maybe_uninit.hpp create mode 100644 asl/types/maybe_uninit_tests.cpp create mode 100644 asl/types/option.hpp create mode 100644 asl/types/option_tests.cpp create mode 100644 asl/types/span.hpp create mode 100644 asl/types/span_tests.cpp create mode 100644 asl/types/status.cpp create mode 100644 asl/types/status.hpp create mode 100644 asl/types/status_or.hpp create mode 100644 asl/types/status_or_tests.cpp create mode 100644 asl/types/status_tests.cpp (limited to 'asl/types') diff --git a/asl/types/BUILD.bazel b/asl/types/BUILD.bazel new file mode 100644 index 0000000..d39586b --- /dev/null +++ b/asl/types/BUILD.bazel @@ -0,0 +1,135 @@ +cc_library( + name = "box", + hdrs = [ + "box.hpp", + ], + deps = [ + "//asl/base", + "//asl/memory", + "//asl/hashing", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "span", + hdrs = [ + "span.hpp", + ], + deps = [ + "//asl/base", + "//asl/memory", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "maybe_uninit", + hdrs = [ + "maybe_uninit.hpp", + ], + deps = [ + "//asl/base", + "//asl/memory", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "option", + hdrs = [ + "option.hpp", + ], + deps = [ + "//asl/base", + "//asl/types:maybe_uninit", + "//asl/hashing", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "status", + hdrs = [ + "status.hpp", + "status_or.hpp", + ], + srcs = [ + "status.cpp", + ], + deps = [ + "//asl/base", + "//asl/strings:string", + "//asl/strings:string_builder", + "//asl/formatting", + "//asl/memory", + "//asl/synchronization:atomic", + "//asl/types:maybe_uninit", + "//asl/hashing", + ], + visibility = ["//visibility:public"], +) + +cc_test( + name = "box_tests", + srcs = ["box_tests.cpp"], + deps = [ + "//asl/tests:utils", + "//asl/testing", + "//asl/types:box", + "//asl/types:option", + "//asl/types:maybe_uninit", + "//asl/hashing", + ], +) + +cc_test( + name = "maybe_uninit_tests", + srcs = ["maybe_uninit_tests.cpp"], + deps = [ + "//asl/tests:utils", + "//asl/testing", + "//asl/types:maybe_uninit", + ], +) + +cc_test( + name = "span_tests", + srcs = ["span_tests.cpp"], + deps = [ + "//asl/tests:utils", + "//asl/testing", + "//asl/types:span", + ], +) + +cc_test( + name = "option_tests", + srcs = ["option_tests.cpp"], + deps = [ + "//asl/tests:utils", + "//asl/testing", + "//asl/types:option", + ], +) + +cc_test( + name = "status_tests", + srcs = ["status_tests.cpp"], + deps = [ + "//asl/tests:utils", + "//asl/testing", + "//asl/types:status", + ], +) + +cc_test( + name = "status_or_tests", + srcs = ["status_or_tests.cpp"], + deps = [ + "//asl/tests:utils", + "//asl/testing", + "//asl/types:status", + ], +) + diff --git a/asl/types/box.hpp b/asl/types/box.hpp new file mode 100644 index 0000000..d8577ec --- /dev/null +++ b/asl/types/box.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include "asl/base/assert.hpp" +#include "asl/base/annotations.hpp" +#include "asl/base/utility.hpp" +#include "asl/memory/memory.hpp" +#include "asl/memory/allocator.hpp" +#include "asl/hashing/hash.hpp" + +namespace asl +{ + +template +class box +{ + T* m_ptr; + ASL_NO_UNIQUE_ADDRESS Allocator m_alloc; + +public: + explicit constexpr box(niche_t) + requires default_constructible + : m_ptr{nullptr} + , m_alloc{} + {} + + constexpr box(T* ptr, Allocator alloc) + : m_ptr{ptr} + , m_alloc{ASL_MOVE(alloc)} + { + ASL_ASSERT(m_ptr != nullptr); + } + + constexpr box(box&& other) + : m_ptr{exchange(other.m_ptr, nullptr)} + , m_alloc{ASL_MOVE(other.m_alloc)} + {} + + template + requires convertible_from + constexpr box(box&& other) // NOLINT(*-explicit-conversions) + : m_ptr{exchange(other.m_ptr, nullptr)} + , m_alloc{ASL_MOVE(other.m_alloc)} + {} + + constexpr box& operator=(box&& other) + { + if (this == &other) { return *this; } + + if (m_ptr != nullptr) { reset(); } + + m_ptr = exchange(other.m_ptr, nullptr); + m_alloc = ASL_MOVE(other.m_alloc); + + return *this; + } + + box(const box&) = delete; + box& operator=(const box&) = delete; + + constexpr ~box() + { + reset(); + } + + constexpr void reset() + { + if (m_ptr != nullptr) + { + destroy(m_ptr); + m_alloc.dealloc(m_ptr, layout::of()); + m_ptr = nullptr; + } + } + + constexpr T* get() const { return m_ptr; } + + constexpr T& operator*() const + { + ASL_ASSERT(m_ptr != nullptr); + return *m_ptr; + } + + constexpr T* operator->() const + { + ASL_ASSERT(m_ptr != nullptr); + return m_ptr; + } + + constexpr bool operator==(niche_t) const + { + return m_ptr == nullptr; + } + + template + requires hashable + friend H AslHashValue(H h, const box& b) + { + return H::combine(ASL_MOVE(h), *b); + } + + template + friend constexpr U* leak(box&&); + + template + friend class box; +}; + +template +constexpr box make_box_in(Allocator allocator, Args&&... args) + requires constructible_from +{ + void* raw_ptr = allocator.alloc(layout::of()); + auto* ptr = construct_at(raw_ptr, ASL_FWD(args)...); + return box(ptr, ASL_MOVE(allocator)); +} + +template +constexpr box make_box(Args&&... args) + requires default_constructible && constructible_from +{ + Allocator allocator{}; + void* raw_ptr = allocator.alloc(layout::of()); + auto* ptr = construct_at(raw_ptr, ASL_FWD(args)...); + return box(ptr, ASL_MOVE(allocator)); +} + +template +constexpr T* leak(box&& b) +{ + return exchange(b.m_ptr, nullptr); +} + +} // namespace asl + diff --git a/asl/types/box_tests.cpp b/asl/types/box_tests.cpp new file mode 100644 index 0000000..58eb4f6 --- /dev/null +++ b/asl/types/box_tests.cpp @@ -0,0 +1,102 @@ +#include "asl/types/box.hpp" +#include "asl/types/option.hpp" + +#include "asl/testing/testing.hpp" +#include "asl/tests/types.hpp" + +static_assert(sizeof(asl::box) == sizeof(int*)); +static_assert(!asl::copyable>); +static_assert(asl::moveable>); +static_assert(asl::has_niche>); +static_assert(sizeof(asl::option>) == sizeof(int*)); + +ASL_TEST(destructor) +{ + bool d = false; + + { + auto box = asl::make_box(&d); + ASL_TEST_ASSERT(!d); + + + auto box3 = ASL_MOVE(box); + ASL_TEST_ASSERT(!d); + } + + ASL_TEST_ASSERT(d); +} + +ASL_TEST(value) +{ + auto b = asl::make_box(24); + ASL_TEST_EXPECT(*b == 24); + + auto b2 = ASL_MOVE(b); + ASL_TEST_EXPECT(*b2 == 24); +} + +ASL_TEST(ptr) +{ + auto b = asl::make_box(24); + auto* ptr1 = b.get(); + + auto b2 = ASL_MOVE(b); + auto* ptr2 = b2.get(); + ASL_TEST_EXPECT(ptr1 == ptr2); +} + +struct Struct { int a; }; + +ASL_TEST(arrow) +{ + auto b = asl::make_box(45); + ASL_TEST_EXPECT(b->a == 45); +} + +ASL_TEST(niche) +{ + static_assert(sizeof(asl::box) == sizeof(asl::option>)); + + asl::option> opt; + ASL_TEST_EXPECT(!opt.has_value()); + + opt = asl::make_box(66); + ASL_TEST_EXPECT(opt.has_value()); + ASL_TEST_EXPECT(*opt.value() == 66); + + opt = asl::nullopt; + ASL_TEST_EXPECT(!opt.has_value()); + + bool destroyed = false; + asl::option opt2 = asl::make_box(&destroyed); + ASL_TEST_EXPECT(opt2.has_value()); + ASL_TEST_EXPECT(!destroyed); + + opt2.reset(); + ASL_TEST_EXPECT(!opt2.has_value()); + ASL_TEST_EXPECT(destroyed); +} + +class Base +{ +public: + virtual ~Base() = default; + virtual int number() { return 1; } +}; + +class Derived : public Base +{ +public: + int number() override { return 2; } +}; + +static_assert(asl::convertible_from, asl::box>); +static_assert(asl::convertible_from, asl::box>); +static_assert(!asl::convertible_from, asl::box>); +static_assert(!asl::convertible_from, asl::box>); + +ASL_TEST(derived) +{ + asl::box obj = asl::make_box(); + ASL_TEST_ASSERT(obj->number() == 2); +} diff --git a/asl/types/maybe_uninit.hpp b/asl/types/maybe_uninit.hpp new file mode 100644 index 0000000..9134fe9 --- /dev/null +++ b/asl/types/maybe_uninit.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "asl/base/meta.hpp" +#include "asl/base/utility.hpp" +#include "asl/memory/memory.hpp" + +namespace asl +{ + +template +union maybe_uninit +{ +private: + T m_value; + +public: + constexpr maybe_uninit() requires trivially_default_constructible = default; + constexpr maybe_uninit() requires (!trivially_default_constructible) {} // NOLINT + + template + explicit constexpr maybe_uninit(in_place_t, Args&&... args) + requires constructible_from + : m_value{ASL_FWD(args)...} + {} + + constexpr maybe_uninit(const maybe_uninit&) requires trivially_copy_constructible = default; + constexpr maybe_uninit(const maybe_uninit&) requires (!trivially_copy_constructible) {} // NOLINT + + constexpr maybe_uninit(maybe_uninit&&) requires trivially_move_constructible = default; + constexpr maybe_uninit(maybe_uninit&&) requires (!trivially_move_constructible) {} // NOLINT + + constexpr maybe_uninit& operator=(const maybe_uninit&) requires trivially_copy_assignable = default; + constexpr maybe_uninit& operator=(const maybe_uninit&) requires (!trivially_copy_assignable) {} + + constexpr maybe_uninit& operator=(maybe_uninit&&) requires trivially_move_assignable = default; + constexpr maybe_uninit& operator=(maybe_uninit&&) requires (!trivially_move_assignable) {} + + constexpr ~maybe_uninit() requires trivially_destructible = default; + constexpr ~maybe_uninit() requires (!trivially_destructible) {} // NOLINT + + // @Safety Value must not have been initialized yet + template + constexpr void construct_unsafe(Args&&... args) + requires constructible_from + { + construct_at(&m_value, ASL_FWD(args)...); + } + + // @Safety Value must have been initialized + template + constexpr void assign_unsafe(U&& value) + requires assignable_from + { + m_value = ASL_FWD(value); + } + + // @Safety Value must have been initialized + constexpr void destroy_unsafe() + { + if constexpr (!trivially_destructible) + { + destroy(&m_value); + } + } + + // @Safety Value must have been initialized + constexpr const T& as_init_unsafe() const& { return m_value; } + constexpr T& as_init_unsafe() & { return m_value; } + constexpr T&& as_init_unsafe() && { return ASL_MOVE(m_value); } +}; + +} // namespace asl diff --git a/asl/types/maybe_uninit_tests.cpp b/asl/types/maybe_uninit_tests.cpp new file mode 100644 index 0000000..4b414cd --- /dev/null +++ b/asl/types/maybe_uninit_tests.cpp @@ -0,0 +1,22 @@ +#include "asl/types/maybe_uninit.hpp" +#include "asl/tests/types.hpp" + +static_assert(asl::layout::of() == asl::layout::of>()); +static_assert(asl::size_of == asl::size_of>); +static_assert(asl::align_of == asl::align_of>); + +#define TEST_TYPE_PROPERTIES(PRP) \ + static_assert(asl::PRP> == asl::PRP); \ + static_assert(asl::PRP> == asl::PRP); \ + static_assert(asl::PRP> == asl::PRP); \ + static_assert(asl::PRP> == asl::PRP); \ + static_assert(asl::PRP> == asl::PRP); \ + static_assert(asl::PRP> == asl::PRP); + +TEST_TYPE_PROPERTIES(trivially_default_constructible); +TEST_TYPE_PROPERTIES(trivially_copy_constructible); +TEST_TYPE_PROPERTIES(trivially_move_constructible); +TEST_TYPE_PROPERTIES(trivially_copy_assignable); +TEST_TYPE_PROPERTIES(trivially_move_assignable); +TEST_TYPE_PROPERTIES(trivially_destructible); + diff --git a/asl/types/option.hpp b/asl/types/option.hpp new file mode 100644 index 0000000..86cfca3 --- /dev/null +++ b/asl/types/option.hpp @@ -0,0 +1,509 @@ +#pragma once + +#include "asl/base/assert.hpp" +#include "asl/base/meta.hpp" +#include "asl/base/functional.hpp" +#include "asl/base/annotations.hpp" +#include "asl/types/maybe_uninit.hpp" +#include "asl/hashing/hash.hpp" + +namespace asl +{ + +struct nullopt_t {}; +static constexpr nullopt_t nullopt{}; + +// @Todo(option) Reference +// @Todo(option) Function +// @Todo(option) Arrays + +template class option; + +namespace option_internal +{ + +template +concept not_constructible_from_option = + !constructible_from&> && + !constructible_from&> && + !constructible_from&&> && + !constructible_from&&>; + +template +concept not_assignable_from_option = + !assignable_from&> && + !assignable_from&> && + !assignable_from&&> && + !assignable_from&&>; + +template +concept not_constructible_assignable_from_option = + not_constructible_from_option && + not_assignable_from_option; + +} // namespace option_internal + +template +concept is_option = requires +{ + typename T::type; + requires same_as, option>; +}; + +template +class option +{ + static constexpr bool kHasNiche = has_niche; + + using HasValueMarker = select_t; + + maybe_uninit m_payload{}; + ASL_NO_UNIQUE_ADDRESS HasValueMarker m_has_value{}; + + template + friend class option; + + template + constexpr void construct(Args&&... args) + { + ASL_ASSERT(!has_value()); + + if constexpr (!kHasNiche) + { + m_payload.construct_unsafe(ASL_FWD(args)...); + m_has_value = true; + } + else + { + if constexpr (move_assignable) + { + m_payload.assign_unsafe(ASL_MOVE(T{ASL_FWD(args)...})); + } + else + { + m_payload.destroy_unsafe(); + m_payload.construct_unsafe(ASL_FWD(args)...); + } + } + } + + template + constexpr void assign(U&& arg) + { + ASL_ASSERT(has_value()); + m_payload.assign_unsafe(ASL_FWD(arg)); + } + +public: + using type = T; + + constexpr option() : option{nullopt} {} + + // NOLINTNEXTLINE(*-explicit-conversions) + constexpr option(nullopt_t) requires (!kHasNiche) {} + + // NOLINTNEXTLINE(*-explicit-conversions) + constexpr option(nullopt_t) requires kHasNiche : m_payload{in_place, niche_t{}} {} + + template + constexpr explicit (!convertible_from) + option(U&& value) + requires ( + kHasNiche && + constructible_from && + !same_as, option> + ) + : m_payload{in_place, ASL_FWD(value)} + {} + + template + constexpr explicit (!convertible_from) + option(U&& value) + requires ( + !kHasNiche && + constructible_from && + !is_option + ) + : m_payload{in_place, ASL_FWD(value)} + , m_has_value{true} + {} + + constexpr option(const option& other) requires trivially_copy_constructible = default; + constexpr option(const option& other) requires (!copy_constructible) = delete; + + constexpr option(const option& other) + requires copy_constructible && (!trivially_copy_constructible) + : option{nullopt} + { + if (other.has_value()) + { + construct(other.m_payload.as_init_unsafe()); + } + } + + constexpr option(option&& other) requires trivially_move_constructible = default; + constexpr option(option&& other) requires (!move_constructible) = delete; + + constexpr option(option&& other) + requires move_constructible && (!trivially_move_constructible) + : option{nullopt} + { + if (other.has_value()) + { + construct(ASL_MOVE(other.m_payload.as_init_unsafe())); + } + } + + template + constexpr explicit (!convertible_from) + option(const option& other) + requires ( + constructible_from && + option_internal::not_constructible_from_option + ) + : option{nullopt} + { + if (other.has_value()) + { + construct(other.m_payload.as_init_unsafe()); + } + } + + template + constexpr explicit (!convertible_from) + option(option&& other) + requires ( + constructible_from && + option_internal::not_constructible_from_option + ) + : option{nullopt} + { + if (other.has_value()) + { + construct(ASL_MOVE(other.m_payload.as_init_unsafe())); + } + } + + constexpr option& operator=(nullopt_t) & + { + reset(); + return *this; + } + + template + constexpr option& operator=(U&& value) & + requires ( + assignable_from && + constructible_from && + !is_option + ) + { + if (has_value()) + { + assign(ASL_FWD(value)); + } + else + { + construct(ASL_FWD(value)); + } + + return *this; + } + + constexpr option& operator=(const option& other) & + requires (!copy_assignable) = delete; + + constexpr option& operator=(const option& other) & + requires trivially_copy_assignable = default; + + constexpr option& operator=(const option& other) & + requires copy_assignable && (!trivially_copy_constructible) + { + if (&other == this) { return *this; } + + if (other.has_value()) + { + if (has_value()) + { + assign(other.m_payload.as_init_unsafe()); + } + else + { + construct(other.m_payload.as_init_unsafe()); + } + } + else if (has_value()) + { + reset(); + } + + return *this; + } + + constexpr option& operator=(option&& other) & + requires (!move_assignable) = delete; + + constexpr option& operator=(option&& other) & + requires trivially_move_assignable = default; + + constexpr option& operator=(option&& other) & + requires move_assignable && (!trivially_move_constructible) + { + if (&other == this) { return *this; } + + if (other.has_value()) + { + if (has_value()) + { + assign(ASL_MOVE(other.m_payload.as_init_unsafe())); + } + else + { + construct(ASL_MOVE(other.m_payload.as_init_unsafe())); + } + } + else if (has_value()) + { + reset(); + } + + return *this; + } + + template + constexpr option& operator=(const option& other) & + requires ( + constructible_from && + assignable_from && + option_internal::not_constructible_assignable_from_option + ) + { + if (other.has_value()) + { + if (has_value()) + { + assign(other.m_payload.as_init_unsafe()); + } + else + { + construct(other.m_payload.as_init_unsafe()); + } + } + else if (has_value()) + { + reset(); + } + + return *this; + } + + template + constexpr option& operator=(option&& other) & + requires ( + constructible_from && + assignable_from && + option_internal::not_constructible_assignable_from_option + ) + { + if (other.has_value()) + { + if (has_value()) + { + assign(ASL_MOVE(other.m_payload.as_init_unsafe())); + } + else + { + construct(ASL_MOVE(other.m_payload.as_init_unsafe())); + } + } + else if (has_value()) + { + reset(); + } + + return *this; + } + + constexpr ~option() requires trivially_destructible = default; + constexpr ~option() requires (!trivially_destructible) + { + reset(); + } + + constexpr void reset() + { + if (!has_value()) { return; } + + if constexpr (kHasNiche) + { + if constexpr (move_assignable) + { + m_payload.assign_unsafe(ASL_MOVE(T{niche_t{}})); + } + else + { + m_payload.destroy_unsafe(); + m_payload.construct_unsafe(niche_t{}); + } + } + else + { + m_has_value = false; + m_payload.destroy_unsafe(); + } + } + + constexpr bool has_value() const + { + if constexpr (kHasNiche) + { + return m_payload.as_init_unsafe() != niche_t{}; + } + else + { + return m_has_value; + } + } + + // @Todo(C++23) Deducing this + constexpr T&& value() && + { + ASL_ASSERT_RELEASE(has_value()); + return ASL_MOVE(m_payload).as_init_unsafe(); + } + + constexpr T& value() & + { + ASL_ASSERT_RELEASE(has_value()); + return m_payload.as_init_unsafe(); + } + + constexpr const T& value() const& + { + ASL_ASSERT_RELEASE(has_value()); + return m_payload.as_init_unsafe(); + } + + template + constexpr T value_or(U&& other_value) const& + requires copy_constructible && convertible_from + { + return has_value() ? value() : static_cast(ASL_FWD(other_value)); + } + + template + constexpr T value_or(U&& other_value) && + requires move_constructible && convertible_from + { + return has_value() ? ASL_MOVE(value()) : static_cast(ASL_FWD(other_value)); + } + + template + constexpr T& emplace(Args&&... args) & + requires constructible_from + { + if (has_value()) { reset(); } + construct(ASL_FWD(args)...); + return value(); + } + + template + constexpr auto and_then(F&& f) & + requires is_option> + { + if (has_value()) + { + return invoke(ASL_FWD(f), value()); + } + return un_cvref_t>{}; + } + + template + constexpr auto and_then(F&& f) const& + requires is_option> + { + if (has_value()) + { + return invoke(ASL_FWD(f), value()); + } + return un_cvref_t>{}; + } + + template + constexpr auto and_then(F&& f) && + requires is_option> + { + if (has_value()) + { + return invoke(ASL_FWD(f), ASL_MOVE(value())); + } + return un_cvref_t>{}; + } + + template + constexpr auto transform(F&& f) & + { + using U = un_cvref_t>; + if (has_value()) + { + return option{ invoke(ASL_FWD(f), value()) }; + } + return option{}; + } + + template + constexpr auto transform(F&& f) const& + { + using U = un_cvref_t>; + if (has_value()) + { + return option{ invoke(ASL_FWD(f), value()) }; + } + return option{}; + } + + template + constexpr auto transform(F&& f) && + { + using U = un_cvref_t>; + if (has_value()) + { + return option{ invoke(ASL_FWD(f), ASL_MOVE(value())) }; + } + return option{}; + } + + template + constexpr option or_else(F&& f) const& + requires same_as>, option> + { + return has_value() ? *this : invoke(ASL_FWD(f)); + } + + template + constexpr option or_else(F&& f) && + requires same_as>, option> + { + return has_value() ? ASL_MOVE(*this) : invoke(ASL_FWD(f)); + } + + template + requires (!uniquely_represented