summaryrefslogtreecommitdiff
path: root/asl/types
diff options
context:
space:
mode:
Diffstat (limited to 'asl/types')
-rw-r--r--asl/types/BUILD.bazel135
-rw-r--r--asl/types/box.hpp134
-rw-r--r--asl/types/box_tests.cpp102
-rw-r--r--asl/types/maybe_uninit.hpp72
-rw-r--r--asl/types/maybe_uninit_tests.cpp22
-rw-r--r--asl/types/option.hpp509
-rw-r--r--asl/types/option_tests.cpp330
-rw-r--r--asl/types/span.hpp235
-rw-r--r--asl/types/span_tests.cpp444
-rw-r--r--asl/types/status.cpp96
-rw-r--r--asl/types/status.hpp157
-rw-r--r--asl/types/status_or.hpp186
-rw-r--r--asl/types/status_or_tests.cpp84
-rw-r--r--asl/types/status_tests.cpp82
14 files changed, 2588 insertions, 0 deletions
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<is_object T, allocator Allocator = DefaultAllocator>
+class box
+{
+ T* m_ptr;
+ ASL_NO_UNIQUE_ADDRESS Allocator m_alloc;
+
+public:
+ explicit constexpr box(niche_t)
+ requires default_constructible<Allocator>
+ : 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<is_object U>
+ requires convertible_from<T*, U*>
+ constexpr box(box<U, Allocator>&& 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<T>());
+ 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<typename H>
+ requires hashable<T>
+ friend H AslHashValue(H h, const box& b)
+ {
+ return H::combine(ASL_MOVE(h), *b);
+ }
+
+ template<is_object U, allocator A>
+ friend constexpr U* leak(box<U, A>&&);
+
+ template<is_object U, allocator A>
+ friend class box;
+};
+
+template<is_object T, allocator Allocator = DefaultAllocator, typename... Args>
+constexpr box<T, Allocator> make_box_in(Allocator allocator, Args&&... args)
+ requires constructible_from<T, Args&&...>
+{
+ void* raw_ptr = allocator.alloc(layout::of<T>());
+ auto* ptr = construct_at<T>(raw_ptr, ASL_FWD(args)...);
+ return box(ptr, ASL_MOVE(allocator));
+}
+
+template<is_object T, allocator Allocator = DefaultAllocator, typename... Args>
+constexpr box<T, Allocator> make_box(Args&&... args)
+ requires default_constructible<Allocator> && constructible_from<T, Args&&...>
+{
+ Allocator allocator{};
+ void* raw_ptr = allocator.alloc(layout::of<T>());
+ auto* ptr = construct_at<T>(raw_ptr, ASL_FWD(args)...);
+ return box<T>(ptr, ASL_MOVE(allocator));
+}
+
+template<is_object T, allocator A>
+constexpr T* leak(box<T, A>&& 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<int>) == sizeof(int*));
+static_assert(!asl::copyable<asl::box<int>>);
+static_assert(asl::moveable<asl::box<int>>);
+static_assert(asl::has_niche<asl::box<int>>);
+static_assert(sizeof(asl::option<asl::box<int>>) == sizeof(int*));
+
+ASL_TEST(destructor)
+{
+ bool d = false;
+
+ {
+ auto box = asl::make_box<DestructorObserver>(&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<int>(24);
+ ASL_TEST_EXPECT(*b == 24);
+
+ auto b2 = ASL_MOVE(b);
+ ASL_TEST_EXPECT(*b2 == 24);
+}
+
+ASL_TEST(ptr)
+{
+ auto b = asl::make_box<int>(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<Struct>(45);
+ ASL_TEST_EXPECT(b->a == 45);
+}
+
+ASL_TEST(niche)
+{
+ static_assert(sizeof(asl::box<int>) == sizeof(asl::option<asl::box<int>>));
+
+ asl::option<asl::box<int>> opt;
+ ASL_TEST_EXPECT(!opt.has_value());
+
+ opt = asl::make_box<int>(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<DestructorObserver>(&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<Base>, asl::box<Derived>>);
+static_assert(asl::convertible_from<asl::box<Base>, asl::box<Base>>);
+static_assert(!asl::convertible_from<asl::box<Derived>, asl::box<Base>>);
+static_assert(!asl::convertible_from<asl::box<int>, asl::box<float>>);
+
+ASL_TEST(derived)
+{
+ asl::box<Base> obj = asl::make_box<Derived>();
+ 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<is_object T>
+union maybe_uninit
+{
+private:
+ T m_value;
+
+public:
+ constexpr maybe_uninit() requires trivially_default_constructible<T> = default;
+ constexpr maybe_uninit() requires (!trivially_default_constructible<T>) {} // NOLINT
+
+ template<typename... Args>
+ explicit constexpr maybe_uninit(in_place_t, Args&&... args)
+ requires constructible_from<T, Args&&...>
+ : m_value{ASL_FWD(args)...}
+ {}
+
+ constexpr maybe_uninit(const maybe_uninit&) requires trivially_copy_constructible<T> = default;
+ constexpr maybe_uninit(const maybe_uninit&) requires (!trivially_copy_constructible<T>) {} // NOLINT
+
+ constexpr maybe_uninit(maybe_uninit&&) requires trivially_move_constructible<T> = default;
+ constexpr maybe_uninit(maybe_uninit&&) requires (!trivially_move_constructible<T>) {} // NOLINT
+
+ constexpr maybe_uninit& operator=(const maybe_uninit&) requires trivially_copy_assignable<T> = default;
+ constexpr maybe_uninit& operator=(const maybe_uninit&) requires (!trivially_copy_assignable<T>) {}
+
+ constexpr maybe_uninit& operator=(maybe_uninit&&) requires trivially_move_assignable<T> = default;
+ constexpr maybe_uninit& operator=(maybe_uninit&&) requires (!trivially_move_assignable<T>) {}
+
+ constexpr ~maybe_uninit() requires trivially_destructible<T> = default;
+ constexpr ~maybe_uninit() requires (!trivially_destructible<T>) {} // NOLINT
+
+ // @Safety Value must not have been initialized yet
+ template<typename... Args>
+ constexpr void construct_unsafe(Args&&... args)
+ requires constructible_from<T, Args&&...>
+ {
+ construct_at<T>(&m_value, ASL_FWD(args)...);
+ }
+
+ // @Safety Value must have been initialized
+ template<typename U>
+ constexpr void assign_unsafe(U&& value)
+ requires assignable_from<T&, U&&>
+ {
+ m_value = ASL_FWD(value);
+ }
+
+ // @Safety Value must have been initialized
+ constexpr void destroy_unsafe()
+ {
+ if constexpr (!trivially_destructible<T>)
+ {
+ 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<int>() == asl::layout::of<asl::maybe_uninit<int>>());
+static_assert(asl::size_of<int> == asl::size_of<asl::maybe_uninit<int>>);
+static_assert(asl::align_of<int> == asl::align_of<asl::maybe_uninit<int>>);
+
+#define TEST_TYPE_PROPERTIES(PRP) \
+ static_assert(asl::PRP<asl::maybe_uninit<TrivialType>> == asl::PRP<TrivialType>); \
+ static_assert(asl::PRP<asl::maybe_uninit<TrivialTypeDefaultValue>> == asl::PRP<TrivialTypeDefaultValue>); \
+ static_assert(asl::PRP<asl::maybe_uninit<WithDestructor>> == asl::PRP<WithDestructor>); \
+ static_assert(asl::PRP<asl::maybe_uninit<Copyable>> == asl::PRP<Copyable>); \
+ static_assert(asl::PRP<asl::maybe_uninit<MoveableOnly>> == asl::PRP<MoveableOnly>); \
+ static_assert(asl::PRP<asl::maybe_uninit<Pinned>> == asl::PRP<Pinned>);
+
+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<is_object T> class option;
+
+namespace option_internal
+{
+
+template<typename T, typename U>
+concept not_constructible_from_option =
+ !constructible_from<T, option<U>&> &&
+ !constructible_from<T, const option<U>&> &&
+ !constructible_from<T, option<U>&&> &&
+ !constructible_from<T, const option<U>&&>;
+
+template<typename T, typename U>
+concept not_assignable_from_option =
+ !assignable_from<T&, option<U>&> &&
+ !assignable_from<T&, const option<U>&> &&
+ !assignable_from<T&, option<U>&&> &&
+ !assignable_from<T&, const option<U>&&>;
+
+template<typename T, typename U>
+concept not_constructible_assignable_from_option =
+ not_constructible_from_option<T, U> &&
+ not_assignable_from_option<T, U>;
+
+} // namespace option_internal
+
+template<typename T>
+concept is_option = requires
+{
+ typename T::type;
+ requires same_as<un_cvref_t<T>, option<typename T::type>>;
+};
+
+template<is_object T>
+class option
+{
+ static constexpr bool kHasNiche = has_niche<T>;
+
+ using HasValueMarker = select_t<kHasNiche, empty, bool>;
+
+ maybe_uninit<T> m_payload{};
+ ASL_NO_UNIQUE_ADDRESS HasValueMarker m_has_value{};
+
+ template<is_object U>
+ friend class option;
+
+ template<typename... Args>
+ 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<T>)
+ {
+ m_payload.assign_unsafe(ASL_MOVE(T{ASL_FWD(args)...}));
+ }
+ else
+ {
+ m_payload.destroy_unsafe();
+ m_payload.construct_unsafe(ASL_FWD(args)...);
+ }
+ }
+ }
+
+ template<typename U>
+ 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<typename U = T>
+ constexpr explicit (!convertible_from<T, U&&>)
+ option(U&& value)
+ requires (
+ kHasNiche &&
+ constructible_from<T, U&&> &&
+ !same_as<un_cvref_t<U>, option>
+ )
+ : m_payload{in_place, ASL_FWD(value)}
+ {}
+
+ template<typename U = T>
+ constexpr explicit (!convertible_from<T, U&&>)
+ option(U&& value)
+ requires (
+ !kHasNiche &&
+ constructible_from<T, U&&> &&
+ !is_option<U>
+ )
+ : m_payload{in_place, ASL_FWD(value)}
+ , m_has_value{true}
+ {}
+
+ constexpr option(const option& other) requires trivially_copy_constructible<T> = default;
+ constexpr option(const option& other) requires (!copy_constructible<T>) = delete;
+
+ constexpr option(const option& other)
+ requires copy_constructible<T> && (!trivially_copy_constructible<T>)
+ : option{nullopt}
+ {
+ if (other.has_value())
+ {
+ construct(other.m_payload.as_init_unsafe());
+ }
+ }
+
+ constexpr option(option&& other) requires trivially_move_constructible<T> = default;
+ constexpr option(option&& other) requires (!move_constructible<T>) = delete;
+
+ constexpr option(option&& other)
+ requires move_constructible<T> && (!trivially_move_constructible<T>)
+ : option{nullopt}
+ {
+ if (other.has_value())
+ {
+ construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
+ }
+ }
+
+ template<typename U>
+ constexpr explicit (!convertible_from<T, const U&>)
+ option(const option<U>& other)
+ requires (
+ constructible_from<T, const U&> &&
+ option_internal::not_constructible_from_option<T, U>
+ )
+ : option{nullopt}
+ {
+ if (other.has_value())
+ {
+ construct(other.m_payload.as_init_unsafe());
+ }
+ }
+
+ template<typename U>
+ constexpr explicit (!convertible_from<T, U&&>)
+ option(option<U>&& other)
+ requires (
+ constructible_from<T, U&&> &&
+ option_internal::not_constructible_from_option<T, U>
+ )
+ : option{nullopt}
+ {
+ if (other.has_value())
+ {
+ construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
+ }
+ }
+
+ constexpr option& operator=(nullopt_t) &
+ {
+ reset();
+ return *this;
+ }
+
+ template<typename U = T>
+ constexpr option& operator=(U&& value) &
+ requires (
+ assignable_from<T&, U&&> &&
+ constructible_from<T, U&&> &&
+ !is_option<U>
+ )
+ {
+ if (has_value())
+ {
+ assign(ASL_FWD(value));
+ }
+ else
+ {
+ construct(ASL_FWD(value));
+ }
+
+ return *this;
+ }
+
+ constexpr option& operator=(const option& other) &
+ requires (!copy_assignable<T>) = delete;
+
+ constexpr option& operator=(const option& other) &
+ requires trivially_copy_assignable<T> = default;
+
+ constexpr option& operator=(const option& other) &
+ requires copy_assignable<T> && (!trivially_copy_constructible<T>)
+ {
+ 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<T>) = delete;
+
+ constexpr option& operator=(option&& other) &
+ requires trivially_move_assignable<T> = default;
+
+ constexpr option& operator=(option&& other) &
+ requires move_assignable<T> && (!trivially_move_constructible<T>)
+ {
+ 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<typename U = T>
+ constexpr option& operator=(const option<U>& other) &
+ requires (
+ constructible_from<T, const U&> &&
+ assignable_from<T&, const U&> &&
+ option_internal::not_constructible_assignable_from_option<T, U>
+ )
+ {
+ 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<typename U = T>
+ constexpr option& operator=(option<U>&& other) &
+ requires (
+ constructible_from<T, U&&> &&
+ assignable_from<T&, U&&> &&
+ option_internal::not_constructible_assignable_from_option<T, U>
+ )
+ {
+ 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<T> = default;
+ constexpr ~option() requires (!trivially_destructible<T>)
+ {
+ reset();
+ }
+
+ constexpr void reset()
+ {
+ if (!has_value()) { return; }
+
+ if constexpr (kHasNiche)
+ {
+ if constexpr (move_assignable<T>)
+ {
+ 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<typename U>
+ constexpr T value_or(U&& other_value) const&
+ requires copy_constructible<T> && convertible_from<T, U&&>
+ {
+ return has_value() ? value() : static_cast<T>(ASL_FWD(other_value));
+ }
+
+ template<typename U>
+ constexpr T value_or(U&& other_value) &&
+ requires move_constructible<T> && convertible_from<T, U&&>
+ {
+ return has_value() ? ASL_MOVE(value()) : static_cast<T>(ASL_FWD(other_value));
+ }
+
+ template<typename... Args>
+ constexpr T& emplace(Args&&... args) &
+ requires constructible_from<T, Args&&...>
+ {
+ if (has_value()) { reset(); }
+ construct(ASL_FWD(args)...);
+ return value();
+ }
+
+ template<typename F>
+ constexpr auto and_then(F&& f) &
+ requires is_option<result_of_t<F(T&)>>
+ {
+ if (has_value())
+ {
+ return invoke(ASL_FWD(f), value());
+ }
+ return un_cvref_t<result_of_t<F(T&)>>{};
+ }
+
+ template<typename F>
+ constexpr auto and_then(F&& f) const&
+ requires is_option<result_of_t<F(const T&)>>
+ {
+ if (has_value())
+ {
+ return invoke(ASL_FWD(f), value());
+ }
+ return un_cvref_t<result_of_t<F(const T&)>>{};
+ }
+
+ template<typename F>
+ constexpr auto and_then(F&& f) &&
+ requires is_option<result_of_t<F(T)>>
+ {
+ if (has_value())
+ {
+ return invoke(ASL_FWD(f), ASL_MOVE(value()));
+ }
+ return un_cvref_t<result_of_t<F(T)>>{};
+ }
+
+ template<typename F>
+ constexpr auto transform(F&& f) &
+ {
+ using U = un_cvref_t<result_of_t<F(T&)>>;
+ if (has_value())
+ {
+ return option<U>{ invoke(ASL_FWD(f), value()) };
+ }
+ return option<U>{};
+ }
+
+ template<typename F>
+ constexpr auto transform(F&& f) const&
+ {
+ using U = un_cvref_t<result_of_t<F(const T&)>>;
+ if (has_value())
+ {
+ return option<U>{ invoke(ASL_FWD(f), value()) };
+ }
+ return option<U>{};
+ }
+
+ template<typename F>
+ constexpr auto transform(F&& f) &&
+ {
+ using U = un_cvref_t<result_of_t<F(T)>>;
+ if (has_value())
+ {
+ return option<U>{ invoke(ASL_FWD(f), ASL_MOVE(value())) };
+ }
+ return option<U>{};
+ }
+
+ template<typename F>
+ constexpr option or_else(F&& f) const&
+ requires same_as<un_cvref_t<result_of_t<F()>>, option>
+ {
+ return has_value() ? *this : invoke(ASL_FWD(f));
+ }
+
+ template<typename F>
+ constexpr option or_else(F&& f) &&
+ requires same_as<un_cvref_t<result_of_t<F()>>, option>
+ {
+ return has_value() ? ASL_MOVE(*this) : invoke(ASL_FWD(f));
+ }
+
+ template<typename H>
+ requires (!uniquely_represented<option>) && hashable<T>
+ friend H AslHashValue(H h, const option& opt)
+ {
+ if (!opt.has_value())
+ {
+ return H::combine(ASL_MOVE(h), 0);
+ }
+ return H::combine(ASL_MOVE(h), 1, opt.value());
+ }
+};
+
+template<typename T>
+requires has_niche<T> && uniquely_represented<T>
+struct is_uniquely_represented<option<T>> : true_type {};
+
+template<typename T>
+option(T) -> option<T>;
+
+} // namespace asl
diff --git a/asl/types/option_tests.cpp b/asl/types/option_tests.cpp
new file mode 100644
index 0000000..272b40a
--- /dev/null
+++ b/asl/types/option_tests.cpp
@@ -0,0 +1,330 @@
+#include "asl/types/option.hpp"
+#include "asl/tests/types.hpp"
+#include "asl/testing/testing.hpp"
+
+class Base {};
+class Derived : public Base {};
+
+struct NonZero
+{
+ int value;
+
+ constexpr explicit NonZero(int x) : value(x)
+ {
+ ASL_ASSERT(x != 0);
+ }
+
+ constexpr explicit NonZero(asl::niche_t) : value(0) {}
+
+ constexpr bool operator==(asl::niche_t) const { return value == 0; }
+};
+static_assert(asl::has_niche<NonZero>);
+static_assert(!asl::has_niche<int>);
+
+static_assert(sizeof(asl::option<int>) >= sizeof(int));
+static_assert(sizeof(asl::option<NonZero>) == sizeof(NonZero));
+
+static_assert(!asl::is_option<int>);
+static_assert(asl::is_option<asl::option<int>>);
+static_assert(asl::is_option<const asl::option<int>>);
+
+static_assert(asl::trivially_destructible<asl::option<TrivialType>>);
+static_assert(!asl::trivially_destructible<asl::option<WithDestructor>>);
+
+static_assert(asl::trivially_copy_constructible<asl::option<int>>);
+static_assert(asl::trivially_copy_constructible<asl::option<NonZero>>);
+static_assert(asl::copy_constructible<asl::option<Copyable>>);
+static_assert(!asl::copy_constructible<asl::option<MoveableOnly>>);
+static_assert(!asl::copy_constructible<asl::option<Pinned>>);
+
+static_assert(asl::trivially_move_constructible<asl::option<int>>);
+static_assert(asl::trivially_move_constructible<asl::option<NonZero>>);
+static_assert(asl::move_constructible<asl::option<Copyable>>);
+static_assert(asl::move_constructible<asl::option<MoveableOnly>>);
+static_assert(!asl::move_constructible<asl::option<Pinned>>);
+
+static_assert(asl::trivially_copy_assignable<asl::option<int>>);
+static_assert(asl::trivially_copy_assignable<asl::option<NonZero>>);
+static_assert(asl::copy_assignable<asl::option<Copyable>>);
+static_assert(!asl::copy_assignable<asl::option<MoveableOnly>>);
+static_assert(!asl::copy_assignable<asl::option<Pinned>>);
+
+static_assert(asl::trivially_move_assignable<asl::option<int>>);
+static_assert(asl::trivially_move_assignable<asl::option<NonZero>>);
+static_assert(asl::move_assignable<asl::optio