diff --git a/AK/Optional.h b/AK/Optional.h index 97aa5523aff..470ce7767ba 100644 --- a/AK/Optional.h +++ b/AK/Optional.h @@ -46,7 +46,7 @@ template class Optional; struct OptionalNone { - explicit OptionalNone() = default; + explicit constexpr OptionalNone() = default; }; template> @@ -55,24 +55,24 @@ public: using ValueType = T; template V> - Self& operator=(V) + ALWAYS_INLINE constexpr Self& operator=(V) { static_cast(*this).clear(); return static_cast(*this); } - [[nodiscard]] ALWAYS_INLINE T* ptr() & + [[nodiscard]] ALWAYS_INLINE constexpr T* ptr() & { return static_cast(*this).has_value() ? __builtin_launder(reinterpret_cast(&static_cast(*this).value())) : nullptr; } - [[nodiscard]] ALWAYS_INLINE T const* ptr() const& + [[nodiscard]] ALWAYS_INLINE constexpr T const* ptr() const& { return static_cast(*this).has_value() ? __builtin_launder(reinterpret_cast(&static_cast(*this).value())) : nullptr; } template - [[nodiscard]] ALWAYS_INLINE O value_or(Fallback const& fallback) const& + [[nodiscard]] ALWAYS_INLINE constexpr O value_or(Fallback const& fallback) const& { if (static_cast(*this).has_value()) return static_cast(*this).value(); @@ -81,7 +81,7 @@ public: template requires(!IsLvalueReference && !IsRvalueReference) - [[nodiscard]] ALWAYS_INLINE O value_or(Fallback&& fallback) && + [[nodiscard]] ALWAYS_INLINE constexpr O value_or(Fallback&& fallback) && { if (static_cast(*this).has_value()) return move(static_cast(*this).value()); @@ -89,7 +89,7 @@ public: } template - [[nodiscard]] ALWAYS_INLINE O value_or_lazy_evaluated(Callback callback) const + [[nodiscard]] ALWAYS_INLINE constexpr O value_or_lazy_evaluated(Callback callback) const { if (static_cast(*this).has_value()) return static_cast(*this).value(); @@ -97,7 +97,7 @@ public: } template - [[nodiscard]] ALWAYS_INLINE Optional value_or_lazy_evaluated_optional(Callback callback) const + [[nodiscard]] ALWAYS_INLINE constexpr Optional value_or_lazy_evaluated_optional(Callback callback) const { if (static_cast(*this).has_value()) return static_cast(*this).value(); @@ -105,7 +105,7 @@ public: } template - [[nodiscard]] ALWAYS_INLINE ErrorOr try_value_or_lazy_evaluated(Callback callback) const + [[nodiscard]] ALWAYS_INLINE constexpr ErrorOr try_value_or_lazy_evaluated(Callback callback) const { if (static_cast(*this).has_value()) return static_cast(*this).value(); @@ -113,21 +113,21 @@ public: } template - [[nodiscard]] ALWAYS_INLINE ErrorOr> try_value_or_lazy_evaluated_optional(Callback callback) const + [[nodiscard]] ALWAYS_INLINE constexpr ErrorOr> try_value_or_lazy_evaluated_optional(Callback callback) const { if (static_cast(*this).has_value()) return static_cast(*this).value(); return TRY(callback()); } - [[nodiscard]] ALWAYS_INLINE T const& operator*() const { return static_cast(*this).value(); } - [[nodiscard]] ALWAYS_INLINE T& operator*() { return static_cast(*this).value(); } + [[nodiscard]] ALWAYS_INLINE constexpr T const& operator*() const { return static_cast(*this).value(); } + [[nodiscard]] ALWAYS_INLINE constexpr T& operator*() { return static_cast(*this).value(); } - ALWAYS_INLINE T const* operator->() const { return &static_cast(*this).value(); } - ALWAYS_INLINE T* operator->() { return &static_cast(*this).value(); } + ALWAYS_INLINE constexpr T const* operator->() const { return &static_cast(*this).value(); } + ALWAYS_INLINE constexpr T* operator->() { return &static_cast(*this).value(); } template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> - ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) + ALWAYS_INLINE constexpr Conditional, OptionalType> map(F&& mapper) { if constexpr (IsErrorOr) { if (static_cast(*this).has_value()) @@ -142,7 +142,7 @@ public: } template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> - ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) const + ALWAYS_INLINE constexpr Conditional, OptionalType> map(F&& mapper) const { if constexpr (IsErrorOr) { if (static_cast(*this).has_value()) @@ -167,13 +167,19 @@ requires(!IsLvalueReference) class [[nodiscard]] Optional : public Optiona public: using ValueType = T; - ALWAYS_INLINE Optional() = default; + ALWAYS_INLINE constexpr Optional() + { + construct_null_if_necessary(); + } template V> - Optional(V) { } + ALWAYS_INLINE constexpr Optional(V) + { + construct_null_if_necessary(); + } template V> - Optional& operator=(V) + ALWAYS_INLINE constexpr Optional& operator=(V) { clear(); return *this; @@ -183,78 +189,85 @@ public: AK_MAKE_CONDITIONALLY_NONMOVABLE(Optional, ); AK_MAKE_CONDITIONALLY_DESTRUCTIBLE(Optional, ); - ALWAYS_INLINE Optional(Optional const& other) + ALWAYS_INLINE constexpr Optional(Optional const& other) requires(!IsTriviallyCopyConstructible) : m_has_value(other.m_has_value) { if (other.has_value()) - new (&m_storage) T(other.value()); + construct_at>(&m_storage, other.value()); + else + construct_null_if_necessary(); } - ALWAYS_INLINE Optional(Optional&& other) + ALWAYS_INLINE constexpr Optional(Optional&& other) + requires(!IsTriviallyMoveConstructible) : m_has_value(other.m_has_value) { if (other.has_value()) - new (&m_storage) T(other.release_value()); + construct_at>(&m_storage, other.release_value()); + else + construct_null_if_necessary(); } template - requires(IsConstructible && !IsSpecializationOf && !IsSpecializationOf && (!IsLvalueReference || IsTriviallyCopyConstructible)) ALWAYS_INLINE explicit Optional(Optional const& other) + requires(IsConstructible && !IsSpecializationOf && !IsSpecializationOf && (!IsLvalueReference || IsTriviallyCopyConstructible)) ALWAYS_INLINE explicit constexpr Optional(Optional const& other) : m_has_value(other.has_value()) { if (other.has_value()) - new (&m_storage) T(other.value()); + construct_at>(&m_storage, other.value()); + else + construct_null_if_necessary(); } template - requires(IsConstructible && !IsSpecializationOf && !IsSpecializationOf && (!IsLvalueReference || IsTriviallyMoveConstructible)) ALWAYS_INLINE explicit Optional(Optional&& other) + requires(IsConstructible && !IsSpecializationOf && !IsSpecializationOf && (!IsLvalueReference || IsTriviallyMoveConstructible)) ALWAYS_INLINE explicit constexpr Optional(Optional&& other) : m_has_value(other.has_value()) { if (other.has_value()) - new (&m_storage) T(other.release_value()); + construct_at>(&m_storage, other.release_value()); + else + construct_null_if_necessary(); } template requires(!IsSame>) - ALWAYS_INLINE explicit(!IsConvertible) Optional(U&& value) + ALWAYS_INLINE explicit(!IsConvertible) constexpr Optional(U&& value) requires(!IsSame, Optional> && IsConstructible) : m_has_value(true) { - new (&m_storage) T(forward(value)); + construct_at>(&m_storage, forward(value)); } - ALWAYS_INLINE Optional& operator=(Optional const& other) + ALWAYS_INLINE constexpr Optional& operator=(Optional const& other) requires(!IsTriviallyCopyConstructible || !IsTriviallyDestructible) { if (this != &other) { clear(); m_has_value = other.m_has_value; - if (other.has_value()) { - new (&m_storage) T(other.value()); - } + if (other.has_value()) + construct_at>(&m_storage, other.value()); } return *this; } - ALWAYS_INLINE Optional& operator=(Optional&& other) + ALWAYS_INLINE constexpr Optional& operator=(Optional&& other) { if (this != &other) { clear(); m_has_value = other.m_has_value; - if (other.has_value()) { - new (&m_storage) T(other.release_value()); - } + if (other.has_value()) + construct_at>(&m_storage, other.release_value()); } return *this; } - ALWAYS_INLINE ~Optional() + ALWAYS_INLINE constexpr ~Optional() requires(!IsTriviallyDestructible && IsDestructible) { clear(); } - ALWAYS_INLINE void clear() + ALWAYS_INLINE constexpr void clear() { if (m_has_value) { value().~T(); @@ -263,41 +276,41 @@ public: } template - ALWAYS_INLINE void emplace(Parameters&&... parameters) + ALWAYS_INLINE constexpr void emplace(Parameters&&... parameters) { clear(); m_has_value = true; - new (&m_storage) T(forward(parameters)...); + construct_at>(&m_storage, forward(parameters)...); } template - ALWAYS_INLINE void lazy_emplace(Callable callable) + ALWAYS_INLINE constexpr void lazy_emplace(Callable callable) { clear(); m_has_value = true; - new (&m_storage) T { callable() }; + construct_at>(&m_storage, callable()); } - [[nodiscard]] ALWAYS_INLINE bool has_value() const { return m_has_value; } + [[nodiscard]] ALWAYS_INLINE constexpr bool has_value() const { return m_has_value; } - [[nodiscard]] ALWAYS_INLINE T& value() & + [[nodiscard]] ALWAYS_INLINE constexpr T& value() & { VERIFY(m_has_value); - return *__builtin_launder(reinterpret_cast(&m_storage)); + return m_storage; } - [[nodiscard]] ALWAYS_INLINE T const& value() const& + [[nodiscard]] ALWAYS_INLINE constexpr T const& value() const& { VERIFY(m_has_value); - return *__builtin_launder(reinterpret_cast(&m_storage)); + return m_storage; } - [[nodiscard]] ALWAYS_INLINE T value() && + [[nodiscard]] ALWAYS_INLINE constexpr T value() && { return release_value(); } - [[nodiscard]] ALWAYS_INLINE T release_value() + [[nodiscard]] ALWAYS_INLINE constexpr T release_value() { VERIFY(m_has_value); T released_value = move(value()); @@ -307,7 +320,27 @@ public: } private: - alignas(T) u8 m_storage[sizeof(T)]; + ALWAYS_INLINE constexpr void construct_null_if_necessary(bool should_construct = is_constant_evaluated()) + { + // OPTIMIZATION: Only construct the `m_null` member when we are constant-evaluating. + // Otherwise, this generates an unnecessary zero-fill. +#if defined(AK_COMPILER_GCC) + // NOTE: GCCs -Wuninitialized warning ends up checking this as well. + should_construct = true; +#endif + if (should_construct) + construct_at(&m_null); + } + + union { + // FIXME: GCC seems to have an issue with uninitialized unions and non trivial types, + // which forces us to have an equally sized trivial null member in the union + // to pseudo-initialize the union. + struct { + u8 _[sizeof(T)]; + } m_null; + RemoveConst m_storage; + }; bool m_has_value { false }; }; @@ -325,46 +358,46 @@ requires(IsLvalueReference) class [[nodiscard]] Optional { public: using ValueType = T; - ALWAYS_INLINE Optional() = default; + ALWAYS_INLINE constexpr Optional() = default; template V> - Optional(V) { } + ALWAYS_INLINE constexpr Optional(V) { } template V> - Optional& operator=(V) + ALWAYS_INLINE constexpr Optional& operator=(V) { clear(); return *this; } template - ALWAYS_INLINE Optional(U& value) + ALWAYS_INLINE constexpr Optional(U& value) requires(CanBePlacedInOptional) : m_pointer(&value) { } - ALWAYS_INLINE Optional(RemoveReference& value) + ALWAYS_INLINE constexpr Optional(RemoveReference& value) : m_pointer(&value) { } template - ALWAYS_INLINE Optional(Optional& other) + ALWAYS_INLINE constexpr Optional(Optional& other) requires(CanBePlacedInOptional) : m_pointer(other.ptr()) { } template - ALWAYS_INLINE Optional(Optional const& other) + ALWAYS_INLINE constexpr Optional(Optional const& other) requires(CanBePlacedInOptional) : m_pointer(other.ptr()) { } template - ALWAYS_INLINE Optional(Optional&& other) + ALWAYS_INLINE constexpr Optional(Optional&& other) requires(CanBePlacedInOptional) : m_pointer(other.ptr()) { @@ -372,7 +405,7 @@ public: } template - ALWAYS_INLINE Optional& operator=(Optional& other) + ALWAYS_INLINE constexpr Optional& operator=(Optional& other) requires(CanBePlacedInOptional) { m_pointer = other.ptr(); @@ -380,7 +413,7 @@ public: } template - ALWAYS_INLINE Optional& operator=(Optional const& other) + ALWAYS_INLINE constexpr Optional& operator=(Optional const& other) requires(CanBePlacedInOptional) { m_pointer = other.ptr(); @@ -388,7 +421,7 @@ public: } template - ALWAYS_INLINE Optional& operator=(Optional&& other) + ALWAYS_INLINE constexpr Optional& operator=(Optional&& other) requires(CanBePlacedInOptional && IsLvalueReference) { m_pointer = other.m_pointer; @@ -398,7 +431,7 @@ public: template requires(!IsSame>) - ALWAYS_INLINE Optional& operator=(U& value) + ALWAYS_INLINE constexpr Optional& operator=(U& value) requires(CanBePlacedInOptional) { m_pointer = &value; @@ -412,12 +445,12 @@ public: requires(CanBePlacedInOptional) = delete; - ALWAYS_INLINE void clear() + ALWAYS_INLINE constexpr void clear() { m_pointer = nullptr; } - [[nodiscard]] ALWAYS_INLINE bool has_value() const { return m_pointer != nullptr; } + [[nodiscard]] ALWAYS_INLINE constexpr bool has_value() const { return m_pointer != nullptr; } [[nodiscard]] ALWAYS_INLINE RemoveReference* ptr() { @@ -429,20 +462,20 @@ public: return m_pointer; } - [[nodiscard]] ALWAYS_INLINE T value() + [[nodiscard]] ALWAYS_INLINE constexpr T value() { VERIFY(m_pointer); return *m_pointer; } - [[nodiscard]] ALWAYS_INLINE AddConstToReferencedType value() const + [[nodiscard]] ALWAYS_INLINE constexpr AddConstToReferencedType value() const { VERIFY(m_pointer); return *m_pointer; } template - requires(IsBaseOf, U>) [[nodiscard]] ALWAYS_INLINE AddConstToReferencedType value_or(U& fallback) const + requires(IsBaseOf, U>) [[nodiscard]] ALWAYS_INLINE constexpr AddConstToReferencedType value_or(U& fallback) const { if (m_pointer) return value(); @@ -450,26 +483,26 @@ public: } // Note that this ends up copying the value. - [[nodiscard]] ALWAYS_INLINE RemoveCVReference value_or(RemoveCVReference fallback) const + [[nodiscard]] ALWAYS_INLINE constexpr RemoveCVReference value_or(RemoveCVReference fallback) const { if (m_pointer) return value(); return fallback; } - [[nodiscard]] ALWAYS_INLINE T release_value() + [[nodiscard]] ALWAYS_INLINE constexpr T release_value() { return *exchange(m_pointer, nullptr); } - ALWAYS_INLINE AddConstToReferencedType operator*() const { return value(); } - ALWAYS_INLINE T operator*() { return value(); } + ALWAYS_INLINE constexpr AddConstToReferencedType operator*() const { return value(); } + ALWAYS_INLINE constexpr T operator*() { return value(); } ALWAYS_INLINE RawPtr>> operator->() const { return &value(); } ALWAYS_INLINE RawPtr> operator->() { return &value(); } // Conversion operators from Optional -> Optional, implicit when T is trivially copyable. - ALWAYS_INLINE operator Optional>() const + ALWAYS_INLINE constexpr operator Optional>() const requires(IsTriviallyCopyable>) { if (has_value()) @@ -478,7 +511,7 @@ public: } // Conversion operators from Optional -> Optional, explicit when T is not trivially copyable, since this is usually a mistake. - ALWAYS_INLINE explicit operator Optional>() const + ALWAYS_INLINE explicit constexpr operator Optional>() const requires(!IsTriviallyCopyable>) { if (has_value()) @@ -492,7 +525,7 @@ public: } template - [[nodiscard]] ALWAYS_INLINE T value_or_lazy_evaluated(Callback callback) const + [[nodiscard]] ALWAYS_INLINE constexpr T value_or_lazy_evaluated(Callback callback) const { if (m_pointer != nullptr) return value(); @@ -500,7 +533,7 @@ public: } template - [[nodiscard]] ALWAYS_INLINE Optional value_or_lazy_evaluated_optional(Callback callback) const + [[nodiscard]] ALWAYS_INLINE constexpr Optional value_or_lazy_evaluated_optional(Callback callback) const { if (m_pointer != nullptr) return value(); @@ -508,7 +541,7 @@ public: } template - [[nodiscard]] ALWAYS_INLINE ErrorOr try_value_or_lazy_evaluated(Callback callback) const + [[nodiscard]] ALWAYS_INLINE constexpr ErrorOr try_value_or_lazy_evaluated(Callback callback) const { if (m_pointer != nullptr) return value(); @@ -516,7 +549,7 @@ public: } template - [[nodiscard]] ALWAYS_INLINE ErrorOr> try_value_or_lazy_evaluated_optional(Callback callback) const + [[nodiscard]] ALWAYS_INLINE constexpr ErrorOr> try_value_or_lazy_evaluated_optional(Callback callback) const { if (m_pointer != nullptr) return value(); @@ -524,7 +557,7 @@ public: } template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> - ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) + ALWAYS_INLINE constexpr Conditional, OptionalType> map(F&& mapper) { if constexpr (IsErrorOr) { if (m_pointer != nullptr) @@ -539,7 +572,7 @@ public: } template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> - ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) const + ALWAYS_INLINE constexpr Conditional, OptionalType> map(F&& mapper) const { if constexpr (IsErrorOr) { if (m_pointer != nullptr) @@ -558,20 +591,20 @@ private: }; template -ALWAYS_INLINE bool operator==(Optional const& first, Optional const& second) +ALWAYS_INLINE constexpr bool operator==(Optional const& first, Optional const& second) { return first.has_value() == second.has_value() && (!first.has_value() || first.value() == second.value()); } template -ALWAYS_INLINE bool operator==(Optional const& first, T2 const& second) +ALWAYS_INLINE constexpr bool operator==(Optional const& first, T2 const& second) { return first.has_value() && first.value() == second; } template -ALWAYS_INLINE bool operator==(Optional const& first, OptionalNone) +ALWAYS_INLINE constexpr bool operator==(Optional const& first, OptionalNone) { return !first.has_value(); } diff --git a/AK/StdLibExtras.h b/AK/StdLibExtras.h index 051e82dff37..7feb7a5a0d2 100644 --- a/AK/StdLibExtras.h +++ b/AK/StdLibExtras.h @@ -10,6 +10,7 @@ #include #include +#include #include namespace AK { @@ -31,6 +32,7 @@ requires(AK::Detail::IsIntegral) template void compiletime_fail(Args...); +using std::construct_at; using std::forward; using std::move; } diff --git a/Libraries/LibTest/Macros.h b/Libraries/LibTest/Macros.h index a79dd65bbb9..d656bbb0312 100644 --- a/Libraries/LibTest/Macros.h +++ b/Libraries/LibTest/Macros.h @@ -130,6 +130,9 @@ bool assume(T const& expression, StringView expression_string, SourceLocation lo return true; } +template +consteval void expect_consteval(T) { } + } #define EXPECT(...) \ @@ -179,6 +182,8 @@ bool assume(T const& expression, StringView expression_string, SourceLocation lo ::Test::set_current_test_result(::Test::TestResult::Failed); \ } while (false) +#define EXPECT_CONSTEVAL(...) ::Test::expect_consteval(__VA_ARGS__) + // To use, specify the lambda to execute in a sub process and verify it exits: // EXPECT_CRASH("This should fail", []{ // return Test::Crash::Failure::DidNotCrash; diff --git a/Tests/AK/TestOptional.cpp b/Tests/AK/TestOptional.cpp index 92a6d855740..77f7944ffc8 100644 --- a/Tests/AK/TestOptional.cpp +++ b/Tests/AK/TestOptional.cpp @@ -18,7 +18,7 @@ class NonCopyable { AK_MAKE_DEFAULT_MOVABLE(NonCopyable); public: - NonCopyable() { } + constexpr NonCopyable() { } ~NonCopyable() = default; int x { 13 }; @@ -29,7 +29,7 @@ class NonTriviallyCopyable { AK_MAKE_DEFAULT_MOVABLE(NonTriviallyCopyable); public: - NonTriviallyCopyable() = default; + constexpr NonTriviallyCopyable() = default; ~NonTriviallyCopyable() = default; ByteString x { "13" }; @@ -40,7 +40,7 @@ class TriviallyCopyable { AK_MAKE_DEFAULT_MOVABLE(TriviallyCopyable); public: - TriviallyCopyable() = default; + constexpr TriviallyCopyable() = default; ~TriviallyCopyable() = default; int x { 13 }; @@ -144,6 +144,56 @@ TEST_CASE(comparison_with_numeric_types) EXPECT_NE(opt1, -2); } +TEST_CASE(test_constexpr) +{ + int i = 13; + NonCopyable dcm {}; + + EXPECT_CONSTEVAL(Optional {}); + EXPECT_CONSTEVAL(Optional {}); + EXPECT_CONSTEVAL(Optional {}); + EXPECT_CONSTEVAL(Optional {}); + EXPECT_CONSTEVAL(Optional {}); + EXPECT_CONSTEVAL(Optional {}); + EXPECT_CONSTEVAL(Optional {}); + EXPECT_CONSTEVAL(Optional {}); + + EXPECT_CONSTEVAL(Optional { 13 }); + EXPECT_CONSTEVAL(Optional { NonCopyable {} }); + EXPECT_CONSTEVAL(Optional { 13 }); + EXPECT_CONSTEVAL(Optional { NonCopyable {} }); + EXPECT_CONSTEVAL(Optional { i }); + EXPECT_CONSTEVAL(Optional { dcm }); + EXPECT_CONSTEVAL(Optional { 13 }); + EXPECT_CONSTEVAL(Optional { NonCopyable {} }); + + static_assert(!Optional {}.has_value()); + static_assert(!Optional {}.has_value()); + static_assert(!Optional {}.has_value()); + static_assert(!Optional {}.has_value()); + static_assert(!Optional {}.has_value()); + static_assert(!Optional {}.has_value()); + static_assert(!Optional {}.has_value()); + static_assert(!Optional {}.has_value()); + + static_assert(Optional { 13 }.has_value()); + static_assert(Optional { NonCopyable {} }.has_value()); + static_assert(Optional { 13 }.has_value()); + static_assert(Optional { NonCopyable {} }.has_value()); + static_assert(Optional { i }.has_value()); + static_assert(Optional { dcm }.has_value()); + static_assert(Optional { 13 }.has_value()); + static_assert(Optional { NonCopyable {} }.has_value()); + + static_assert(Optional { 13 }.value() == 13); + static_assert(Optional { NonCopyable {} }.value().x == 13); + static_assert(Optional { 13 }.value() == 13); + static_assert(Optional { 13 }.value() == 13); + static_assert(Optional { NonCopyable {} }.value().x == 13); + + static_assert(!(Optional { 1 } = {}).has_value(), "Assigning a `{}` should clear the Optional, even for scalar types^^"); +} + TEST_CASE(test_copy_ctor_and_dtor_called) { #ifdef AK_HAVE_CONDITIONALLY_TRIVIAL @@ -293,6 +343,63 @@ TEST_CASE(comparison_reference) EXPECT_NE(opt1, opt3); } +TEST_CASE(uninitialized_constructor) +{ + static bool was_constructed = false; + struct Internal { + Internal() { was_constructed = true; } + }; + + struct ShouldNotBeDefaultConstructed { + bool m_default_constructed { true }; + Internal m_internal; + ShouldNotBeDefaultConstructed() = default; + ShouldNotBeDefaultConstructed(bool) + : m_default_constructed(false) + { + } + }; + static_assert(IsConstructible); + + Optional opt; + EXPECT(!was_constructed); + EXPECT(!opt.has_value()); + + opt = ShouldNotBeDefaultConstructed { true }; + EXPECT(was_constructed); + EXPECT(opt.has_value()); + EXPECT(!opt.value().m_default_constructed); +} + +consteval bool test_constexpr() +{ + Optional none; + if (none.has_value()) + return false; + + Optional x; + x = 3; + if (!x.has_value()) + return false; + + if (x.value() != 3) + return false; + + Optional y; + y = x.release_value(); + if (!y.has_value()) + return false; + + if (y.value() != 3) + return false; + + if (x.has_value()) + return false; + + return true; +} +static_assert(test_constexpr()); + template struct CheckAssignments;