diff options
Diffstat (limited to 'asl/types')
-rw-r--r-- | asl/types/BUILD.bazel | 135 | ||||
-rw-r--r-- | asl/types/box.hpp | 134 | ||||
-rw-r--r-- | asl/types/box_tests.cpp | 102 | ||||
-rw-r--r-- | asl/types/maybe_uninit.hpp | 72 | ||||
-rw-r--r-- | asl/types/maybe_uninit_tests.cpp | 22 | ||||
-rw-r--r-- | asl/types/option.hpp | 509 | ||||
-rw-r--r-- | asl/types/option_tests.cpp | 330 | ||||
-rw-r--r-- | asl/types/span.hpp | 235 | ||||
-rw-r--r-- | asl/types/span_tests.cpp | 444 | ||||
-rw-r--r-- | asl/types/status.cpp | 96 | ||||
-rw-r--r-- | asl/types/status.hpp | 157 | ||||
-rw-r--r-- | asl/types/status_or.hpp | 186 | ||||
-rw-r--r-- | asl/types/status_or_tests.cpp | 84 | ||||
-rw-r--r-- | asl/types/status_tests.cpp | 82 |
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 |