From e123d480436b21a52c3b5697f659022a57bd4e94 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Wed, 18 Mar 2026 20:43:56 +0100 Subject: [PATCH] AK: Add `SentinelOptional` We specialize `Optional` for value types that inherently support some kind of "empty" value or whose value range allow for a unlikely to be useful sentinel value that can mean "empty", instead of the boolean flag a regular Optional needs to store. Because of padding, this often means saving 4 to 8 bytes per instance. By extending the new `SentinelOptional`, these specializations are significantly simplified to just having to define what the sentinel value is, and how to identify a sentinel value. --- AK/FlyString.h | 100 ++------------------ AK/Optional.h | 68 +++++++++++++ AK/String.h | 98 ++----------------- AK/Utf16FlyString.h | 96 ++----------------- Libraries/LibJS/Bytecode/IdentifierTable.h | 98 ++----------------- Libraries/LibJS/Bytecode/Operand.h | 98 ++----------------- Libraries/LibJS/Bytecode/PropertyKeyTable.h | 98 ++----------------- Libraries/LibJS/Bytecode/StringTable.h | 98 ++----------------- Libraries/LibJS/Runtime/Completion.h | 89 ++--------------- Libraries/LibJS/Runtime/PropertyKey.h | 100 ++------------------ Libraries/LibJS/Runtime/Utf16String.h | 98 ++----------------- Libraries/LibJS/Runtime/Value.h | 98 ++----------------- Tests/AK/TestOptional.cpp | 64 +++++++++++++ 13 files changed, 215 insertions(+), 988 deletions(-) diff --git a/AK/FlyString.h b/AK/FlyString.h index be5f0f7cd0a..f909d396ffd 100644 --- a/AK/FlyString.h +++ b/AK/FlyString.h @@ -84,7 +84,7 @@ public: } private: - friend class Optional; + friend struct SentinelOptionalTraits; explicit constexpr FlyString(nullptr_t) : m_data(nullptr) @@ -102,99 +102,15 @@ private: }; template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static constexpr FlyString sentinel_value() { return FlyString(nullptr); } + static constexpr bool is_sentinel(FlyString const& value) { return value.is_invalid(); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = FlyString; - - constexpr Optional() = default; - - template V> - constexpr Optional(V) { } - - constexpr Optional(Optional const& other) - { - if (other.has_value()) - m_value = other.m_value; - } - - constexpr Optional(Optional&& other) - : m_value(move(other.m_value)) - { - } - - template - requires(!IsSame>) - explicit(!IsConvertible) constexpr Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - template V> - constexpr Optional& operator=(V) - { - clear(); - return *this; - } - - constexpr Optional& operator=(Optional const& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - constexpr Optional& operator=(Optional&& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - constexpr void clear() - { - m_value = FlyString(nullptr); - } - - [[nodiscard]] constexpr bool has_value() const - { - return !m_value.is_invalid(); - } - - [[nodiscard]] constexpr FlyString& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] constexpr FlyString const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] constexpr FlyString value() && - { - return release_value(); - } - - [[nodiscard]] constexpr FlyString release_value() - { - VERIFY(has_value()); - FlyString released_value = move(m_value); - clear(); - return released_value; - } - -private: - FlyString m_value = FlyString(nullptr); + using SentinelOptional::SentinelOptional; }; template<> diff --git a/AK/Optional.h b/AK/Optional.h index a9c5e1dfe71..1fc27230dff 100644 --- a/AK/Optional.h +++ b/AK/Optional.h @@ -661,9 +661,77 @@ struct Traits> : public DefaultTraits> { } }; +template +struct SentinelOptionalTraits; + +template> +class SentinelOptional : public OptionalBase { +public: + SentinelOptional() = default; + + template V> + constexpr SentinelOptional(V) { } + + template + requires(!IsSame>) + explicit(!IsConvertible) constexpr SentinelOptional(U&& value) + requires(!IsSame, SentinelOptional> && !IsSame, Optional> && IsConstructible) + : m_value(forward(value)) + { + } + + template + requires(!IsOneOfIgnoringCVReference, OptionalNone> && IsConstructible) + constexpr SentinelOptional& operator=(U&& value) + { + m_value = T(forward(value)); + return *this; + } + + constexpr void clear() + { + m_value = Traits::sentinel_value(); + } + + [[nodiscard]] constexpr bool has_value() const + { + return !Traits::is_sentinel(m_value); + } + + template + [[nodiscard]] constexpr auto& value(this Self& self) + { + VERIFY(self.has_value()); + return self.m_value; + } + + [[nodiscard]] constexpr T value() && + { + return release_value(); + } + + template + [[nodiscard]] constexpr auto& unchecked_value(this Self& self) + { + ASSERT(self.has_value()); + return self.m_value; + } + + [[nodiscard]] constexpr T release_value() + { + VERIFY(has_value()); + return exchange(m_value, Traits::sentinel_value()); + } + +private: + T m_value { Traits::sentinel_value() }; +}; + } #if USING_AK_GLOBALLY using AK::Optional; using AK::OptionalNone; +using AK::SentinelOptional; +using AK::SentinelOptionalTraits; #endif diff --git a/AK/String.h b/AK/String.h index 71a34efd7ff..f37805c1b2e 100644 --- a/AK/String.h +++ b/AK/String.h @@ -217,7 +217,7 @@ public: private: friend class ::AK::FlyString; - friend class Optional; + friend struct SentinelOptionalTraits; using ShortString = Detail::ShortString; @@ -238,97 +238,15 @@ private: }; template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static constexpr String sentinel_value() { return String(nullptr); } + static constexpr bool is_sentinel(String const& value) { return value.is_invalid(); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = String; - - constexpr Optional() = default; - - template V> - constexpr Optional(V) { } - - constexpr Optional(Optional const& other) - { - if (other.has_value()) - m_value = other.m_value; - } - - constexpr Optional(Optional&& other) - : m_value(move(other.m_value)) - { - } - - template - requires(!IsSame>) - explicit(!IsConvertible) constexpr Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - template V> - constexpr Optional& operator=(V) - { - clear(); - return *this; - } - - constexpr Optional& operator=(Optional const& other) - { - if (this != &other) { - m_value = other.m_value; - } - return *this; - } - - constexpr Optional& operator=(Optional&& other) - { - if (this != &other) { - m_value = move(other.m_value); - } - return *this; - } - - constexpr void clear() - { - m_value = String(nullptr); - } - - [[nodiscard]] constexpr bool has_value() const - { - return !m_value.is_invalid(); - } - - [[nodiscard]] constexpr String& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] constexpr String const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] constexpr String value() && - { - return release_value(); - } - - [[nodiscard]] constexpr String release_value() - { - VERIFY(has_value()); - String released_value = move(m_value); - clear(); - return released_value; - } - -private: - String m_value { nullptr }; + using SentinelOptional::SentinelOptional; }; template<> diff --git a/AK/Utf16FlyString.h b/AK/Utf16FlyString.h index 513087e463a..5e7513b38ab 100644 --- a/AK/Utf16FlyString.h +++ b/AK/Utf16FlyString.h @@ -136,12 +136,12 @@ public: [[nodiscard]] ALWAYS_INLINE size_t code_unit_offset_of(size_t code_point_offset) const { return m_data.code_unit_offset_of(code_point_offset); } [[nodiscard]] ALWAYS_INLINE size_t code_point_offset_of(size_t code_unit_offset) const { return m_data.code_point_offset_of(code_unit_offset); } - constexpr Utf16FlyString(Badge>, nullptr_t) + constexpr Utf16FlyString(Badge>, nullptr_t) : m_data(Badge {}, nullptr) { } - [[nodiscard]] constexpr bool is_invalid(Badge>) const { return m_data.raw({}) == 0; } + [[nodiscard]] constexpr bool is_invalid(Badge>) const { return m_data.raw({}) == 0; } // This is primarily interesting to unit tests. [[nodiscard]] static size_t number_of_utf16_fly_strings(); @@ -159,93 +159,15 @@ private: }; template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static constexpr Utf16FlyString sentinel_value() { return Utf16FlyString({}, nullptr); } + static constexpr bool is_sentinel(Utf16FlyString const& value) { return value.is_invalid({}); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = Utf16FlyString; - - constexpr Optional() = default; - - template V> - constexpr Optional(V) { } - - constexpr Optional(Optional const& other) - : m_value(other.m_value) - { - } - - constexpr Optional(Optional&& other) - : m_value(move(other.m_value)) - { - } - - template - requires(!IsSame>) - explicit(!IsConvertible) constexpr Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - template V> - constexpr Optional& operator=(V) - { - clear(); - return *this; - } - - constexpr Optional& operator=(Optional const& other) - { - if (this != &other) - m_value = other.m_value; - return *this; - } - - constexpr Optional& operator=(Optional&& other) - { - if (this != &other) - m_value = other.m_value; - return *this; - } - - constexpr void clear() - { - m_value = empty_value; - } - - [[nodiscard]] constexpr bool has_value() const - { - return !m_value.is_invalid({}); - } - - [[nodiscard]] constexpr Utf16FlyString& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] constexpr Utf16FlyString const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] constexpr Utf16FlyString value() && - { - return release_value(); - } - - [[nodiscard]] constexpr Utf16FlyString release_value() - { - VERIFY(has_value()); - return exchange(m_value, empty_value); - } - -private: - static constexpr Utf16FlyString empty_value { {}, nullptr }; - Utf16FlyString m_value { empty_value }; + using SentinelOptional::SentinelOptional; }; template<> diff --git a/Libraries/LibJS/Bytecode/IdentifierTable.h b/Libraries/LibJS/Bytecode/IdentifierTable.h index ae1cce57af9..f82c3ebdafd 100644 --- a/Libraries/LibJS/Bytecode/IdentifierTable.h +++ b/Libraries/LibJS/Bytecode/IdentifierTable.h @@ -41,99 +41,15 @@ private: namespace AK { template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static constexpr JS::Bytecode::IdentifierTableIndex sentinel_value() { return { JS::Bytecode::IdentifierTableIndex::invalid }; } + static constexpr bool is_sentinel(JS::Bytecode::IdentifierTableIndex const& value) { return !value.is_valid(); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = JS::Bytecode::IdentifierTableIndex; - - Optional() = default; - - template V> - Optional(V) { } - - Optional(Optional const& other) - { - if (other.has_value()) - m_value = other.m_value; - } - - Optional(Optional&& other) - : m_value(other.m_value) - { - } - - template - requires(!IsSame>) - explicit(!IsConvertible) Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - template V> - Optional& operator=(V) - { - clear(); - return *this; - } - - Optional& operator=(Optional const& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - Optional& operator=(Optional&& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - void clear() - { - m_value.value = JS::Bytecode::IdentifierTableIndex::invalid; - } - - [[nodiscard]] bool has_value() const - { - return m_value.is_valid(); - } - - [[nodiscard]] JS::Bytecode::IdentifierTableIndex& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::Bytecode::IdentifierTableIndex const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::Bytecode::IdentifierTableIndex value() && - { - return release_value(); - } - - [[nodiscard]] JS::Bytecode::IdentifierTableIndex release_value() - { - VERIFY(has_value()); - JS::Bytecode::IdentifierTableIndex released_value = m_value; - clear(); - return released_value; - } - -private: - JS::Bytecode::IdentifierTableIndex m_value { JS::Bytecode::IdentifierTableIndex::invalid }; + using SentinelOptional::SentinelOptional; }; } diff --git a/Libraries/LibJS/Bytecode/Operand.h b/Libraries/LibJS/Bytecode/Operand.h index 72978128e82..bd8d88bcf87 100644 --- a/Libraries/LibJS/Bytecode/Operand.h +++ b/Libraries/LibJS/Bytecode/Operand.h @@ -62,99 +62,15 @@ private: namespace AK { template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static JS::Bytecode::Operand sentinel_value() { return JS::Bytecode::Operand { JS::Bytecode::Operand::ShouldMakeInvalid::Indeed }; } + static bool is_sentinel(JS::Bytecode::Operand const& value) { return value.is_invalid(); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = JS::Bytecode::Operand; - - Optional() = default; - - template V> - Optional(V) { } - - Optional(Optional const& other) - { - if (other.has_value()) - m_value = other.m_value; - } - - Optional(Optional&& other) - : m_value(other.m_value) - { - } - - template - requires(!IsSame>) - explicit(!IsConvertible) Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - template V> - Optional& operator=(V) - { - clear(); - return *this; - } - - Optional& operator=(Optional const& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - Optional& operator=(Optional&& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - void clear() - { - m_value = JS::Bytecode::Operand { JS::Bytecode::Operand::ShouldMakeInvalid::Indeed }; - } - - [[nodiscard]] bool has_value() const - { - return !m_value.is_invalid(); - } - - [[nodiscard]] JS::Bytecode::Operand& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::Bytecode::Operand const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::Bytecode::Operand value() && - { - return release_value(); - } - - [[nodiscard]] JS::Bytecode::Operand release_value() - { - VERIFY(has_value()); - JS::Bytecode::Operand released_value = m_value; - clear(); - return released_value; - } - -private: - JS::Bytecode::Operand m_value { JS::Bytecode::Operand::ShouldMakeInvalid::Indeed }; + using SentinelOptional::SentinelOptional; }; } diff --git a/Libraries/LibJS/Bytecode/PropertyKeyTable.h b/Libraries/LibJS/Bytecode/PropertyKeyTable.h index b84987ba978..d976749be9f 100644 --- a/Libraries/LibJS/Bytecode/PropertyKeyTable.h +++ b/Libraries/LibJS/Bytecode/PropertyKeyTable.h @@ -47,99 +47,15 @@ private: namespace AK { template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static constexpr JS::Bytecode::PropertyKeyTableIndex sentinel_value() { return { JS::Bytecode::PropertyKeyTableIndex::invalid }; } + static constexpr bool is_sentinel(JS::Bytecode::PropertyKeyTableIndex const& value) { return !value.is_valid(); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = JS::Bytecode::PropertyKeyTableIndex; - - Optional() = default; - - template V> - Optional(V) { } - - Optional(Optional const& other) - { - if (other.has_value()) - m_value = other.m_value; - } - - Optional(Optional&& other) - : m_value(other.m_value) - { - } - - template - requires(!IsSame>) - explicit(!IsConvertible) Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - template V> - Optional& operator=(V) - { - clear(); - return *this; - } - - Optional& operator=(Optional const& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - Optional& operator=(Optional&& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - void clear() - { - m_value.value = JS::Bytecode::PropertyKeyTableIndex::invalid; - } - - [[nodiscard]] bool has_value() const - { - return m_value.is_valid(); - } - - [[nodiscard]] JS::Bytecode::PropertyKeyTableIndex& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::Bytecode::PropertyKeyTableIndex const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::Bytecode::PropertyKeyTableIndex value() && - { - return release_value(); - } - - [[nodiscard]] JS::Bytecode::PropertyKeyTableIndex release_value() - { - VERIFY(has_value()); - JS::Bytecode::PropertyKeyTableIndex released_value = m_value; - clear(); - return released_value; - } - -private: - JS::Bytecode::PropertyKeyTableIndex m_value { JS::Bytecode::PropertyKeyTableIndex::invalid }; + using SentinelOptional::SentinelOptional; }; } diff --git a/Libraries/LibJS/Bytecode/StringTable.h b/Libraries/LibJS/Bytecode/StringTable.h index e98a10b3f69..88da45fe1e2 100644 --- a/Libraries/LibJS/Bytecode/StringTable.h +++ b/Libraries/LibJS/Bytecode/StringTable.h @@ -39,99 +39,15 @@ private: namespace AK { template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static constexpr JS::Bytecode::StringTableIndex sentinel_value() { return { JS::Bytecode::StringTableIndex::invalid }; } + static constexpr bool is_sentinel(JS::Bytecode::StringTableIndex const& value) { return !value.is_valid(); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = JS::Bytecode::StringTableIndex; - - Optional() = default; - - template V> - Optional(V) { } - - Optional(Optional const& other) - { - if (other.has_value()) - m_value = other.m_value; - } - - Optional(Optional&& other) - : m_value(other.m_value) - { - } - - template - requires(!IsSame>) - explicit(!IsConvertible) Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - template V> - Optional& operator=(V) - { - clear(); - return *this; - } - - Optional& operator=(Optional const& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - Optional& operator=(Optional&& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - void clear() - { - m_value.value = JS::Bytecode::StringTableIndex::invalid; - } - - [[nodiscard]] bool has_value() const - { - return m_value.is_valid(); - } - - [[nodiscard]] JS::Bytecode::StringTableIndex& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::Bytecode::StringTableIndex const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::Bytecode::StringTableIndex value() && - { - return release_value(); - } - - [[nodiscard]] JS::Bytecode::StringTableIndex release_value() - { - VERIFY(has_value()); - JS::Bytecode::StringTableIndex released_value = m_value; - clear(); - return released_value; - } - -private: - JS::Bytecode::StringTableIndex m_value { JS::Bytecode::StringTableIndex::invalid }; + using SentinelOptional::SentinelOptional; }; } diff --git a/Libraries/LibJS/Runtime/Completion.h b/Libraries/LibJS/Runtime/Completion.h index f7fb5aaf7ce..ff8c9bad5bc 100644 --- a/Libraries/LibJS/Runtime/Completion.h +++ b/Libraries/LibJS/Runtime/Completion.h @@ -113,7 +113,7 @@ public: private: class EmptyTag { }; - friend AK::Optional; + friend struct AK::SentinelOptionalTraits; constexpr Completion(EmptyTag) : m_type(Type::Empty) @@ -135,88 +135,15 @@ private: namespace AK { template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static constexpr JS::Completion sentinel_value() { return JS::Completion(JS::Completion::EmptyTag {}); } + static constexpr bool is_sentinel(JS::Completion const& value) { return value.is_empty(); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = JS::Completion; - - constexpr Optional() = default; - - constexpr Optional(Optional const& other) - { - if (other.has_value()) - m_value = other.m_value; - } - - constexpr Optional(Optional&& other) - : m_value(move(other.m_value)) - { - } - - template - explicit(!IsConvertible) constexpr Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - constexpr Optional& operator=(Optional const& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - constexpr Optional& operator=(Optional&& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - constexpr void clear() - { - m_value = JS::Completion(JS::Completion::EmptyTag {}); - } - - [[nodiscard]] constexpr bool has_value() const - { - return !m_value.is_empty(); - } - - [[nodiscard]] constexpr JS::Completion& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] constexpr JS::Completion const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] constexpr JS::Completion value() && - { - return release_value(); - } - - [[nodiscard]] constexpr JS::Completion release_value() - { - VERIFY(has_value()); - JS::Completion released_value = m_value; - clear(); - return released_value; - } - -private: - JS::Completion m_value { JS::Completion::EmptyTag {} }; + using SentinelOptional::SentinelOptional; }; } diff --git a/Libraries/LibJS/Runtime/PropertyKey.h b/Libraries/LibJS/Runtime/PropertyKey.h index 4bb954d8057..7e7443f49ff 100644 --- a/Libraries/LibJS/Runtime/PropertyKey.h +++ b/Libraries/LibJS/Runtime/PropertyKey.h @@ -179,7 +179,7 @@ public: private: friend Traits; - friend class Optional; + friend struct AK::SentinelOptionalTraits; enum class ShouldMakeEmptyOptional { Indeed, @@ -242,99 +242,15 @@ struct Formatter : Formatter { }; template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static JS::PropertyKey sentinel_value() { return JS::PropertyKey { JS::PropertyKey::ShouldMakeEmptyOptional::Indeed }; } + static bool is_sentinel(JS::PropertyKey const& value) { return value.is_empty_optional(); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = JS::PropertyKey; - - Optional() = default; - - template V> - Optional(V) { } - - Optional(Optional const& other) - { - if (other.has_value()) - m_value = other.m_value; - } - - Optional(Optional&& other) - : m_value(other.m_value) - { - } - - template - requires(!IsSame>) - explicit(!IsConvertible) Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - template V> - Optional& operator=(V) - { - clear(); - return *this; - } - - Optional& operator=(Optional const& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - Optional& operator=(Optional&& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - void clear() - { - m_value = JS::PropertyKey { JS::PropertyKey::ShouldMakeEmptyOptional::Indeed }; - } - - [[nodiscard]] bool has_value() const - { - return !m_value.is_empty_optional(); - } - - [[nodiscard]] JS::PropertyKey& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::PropertyKey const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::PropertyKey value() && - { - return release_value(); - } - - [[nodiscard]] JS::PropertyKey release_value() - { - VERIFY(has_value()); - JS::PropertyKey released_value = m_value; - clear(); - return released_value; - } - -private: - JS::PropertyKey m_value { JS::PropertyKey::ShouldMakeEmptyOptional::Indeed }; + using SentinelOptional::SentinelOptional; }; } diff --git a/Libraries/LibJS/Runtime/Utf16String.h b/Libraries/LibJS/Runtime/Utf16String.h index 5892daa64a7..1f54cbd2082 100644 --- a/Libraries/LibJS/Runtime/Utf16String.h +++ b/Libraries/LibJS/Runtime/Utf16String.h @@ -99,99 +99,15 @@ struct Traits : public DefaultTraits { }; template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static JS::Utf16String sentinel_value() { return JS::Utf16String::invalid(); } + static bool is_sentinel(JS::Utf16String const& value) { return !value.is_valid(); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = JS::Utf16String; - - Optional() = default; - - template V> - Optional(V) { } - - Optional(Optional const& other) - { - if (other.has_value()) - m_value = other.m_value; - } - - Optional(Optional&& other) - : m_value(other.m_value) - { - } - - template - requires(!IsSame>) - explicit(!IsConvertible) Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - template V> - Optional& operator=(V) - { - clear(); - return *this; - } - - Optional& operator=(Optional const& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - Optional& operator=(Optional&& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - void clear() - { - m_value = JS::Utf16String::invalid(); - } - - [[nodiscard]] bool has_value() const - { - return m_value.is_valid(); - } - - [[nodiscard]] JS::Utf16String& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::Utf16String const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] JS::Utf16String value() && - { - return release_value(); - } - - [[nodiscard]] JS::Utf16String release_value() - { - VERIFY(has_value()); - JS::Utf16String released_value = m_value; - clear(); - return released_value; - } - -private: - JS::Utf16String m_value { JS::Utf16String::invalid() }; + using SentinelOptional::SentinelOptional; }; } diff --git a/Libraries/LibJS/Runtime/Value.h b/Libraries/LibJS/Runtime/Value.h index 97682857ebe..30e701a9baa 100644 --- a/Libraries/LibJS/Runtime/Value.h +++ b/Libraries/LibJS/Runtime/Value.h @@ -608,99 +608,15 @@ namespace AK { static_assert(sizeof(JS::Value) == sizeof(double)); template<> -class Optional : public OptionalBase { - template - friend class Optional; +struct SentinelOptionalTraits { + static constexpr JS::Value sentinel_value() { return JS::js_special_empty_value(); } + static constexpr bool is_sentinel(JS::Value const& value) { return value.is_special_empty_value(); } +}; +template<> +class Optional : public SentinelOptional { public: - using ValueType = JS::Value; - - constexpr Optional() = default; - - template V> - constexpr Optional(V) { } - - constexpr Optional(Optional const& other) - { - if (other.has_value()) - m_value = other.m_value; - } - - constexpr Optional(Optional&& other) - : m_value(other.m_value) - { - } - - template - requires(!IsSame>) - explicit(!IsConvertible) constexpr Optional(U&& value) - requires(!IsSame, Optional> && IsConstructible) - : m_value(forward(value)) - { - } - - template V> - constexpr Optional& operator=(V) - { - clear(); - return *this; - } - - constexpr Optional& operator=(Optional const& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - constexpr Optional& operator=(Optional&& other) - { - if (this != &other) { - clear(); - m_value = other.m_value; - } - return *this; - } - - constexpr void clear() - { - m_value = JS::js_special_empty_value(); - } - - [[nodiscard]] constexpr bool has_value() const - { - return !m_value.is_special_empty_value(); - } - - [[nodiscard]] constexpr JS::Value& value() & - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] constexpr JS::Value const& value() const& - { - VERIFY(has_value()); - return m_value; - } - - [[nodiscard]] constexpr JS::Value value() && - { - return release_value(); - } - - [[nodiscard]] constexpr JS::Value release_value() - { - VERIFY(has_value()); - JS::Value released_value = m_value; - clear(); - return released_value; - } - -private: - JS::Value m_value { JS::js_special_empty_value() }; + using SentinelOptional::SentinelOptional; }; } diff --git a/Tests/AK/TestOptional.cpp b/Tests/AK/TestOptional.cpp index 3773eec0282..014e9ca3091 100644 --- a/Tests/AK/TestOptional.cpp +++ b/Tests/AK/TestOptional.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -662,3 +663,66 @@ TEST_CASE(flystring_specialization) EXPECT_EQ((Optional {}).value_or("fallback_value"_fly_string), "fallback_value"sv); } } + +struct SentinelInt { + u32 value { 0 }; + static constexpr u32 invalid = NumericLimits::max(); + bool is_valid() const { return value != invalid; } +}; + +template<> +struct AK::SentinelOptionalTraits { + static constexpr SentinelInt sentinel_value() { return { SentinelInt::invalid }; } + static constexpr bool is_sentinel(SentinelInt const& v) { return !v.is_valid(); } +}; + +template<> +class AK::Optional : public SentinelOptional { +public: + using SentinelOptional::SentinelOptional; +}; + +TEST_CASE(sentinel_optional) +{ + static_assert(sizeof(Optional) == sizeof(SentinelInt)); + static_assert(sizeof(Optional) == sizeof(FlyString)); + static_assert(sizeof(Optional) == sizeof(String)); + + { + Optional empty; + EXPECT(!empty.has_value()); + + Optional with_value { SentinelInt { 42 } }; + EXPECT(with_value.has_value()); + EXPECT_EQ(with_value->value, 42u); + + with_value = {}; + EXPECT(!with_value.has_value()); + } + + { + Optional a { SentinelInt { 7 } }; + Optional b = a; + EXPECT(b.has_value()); + EXPECT_EQ(b->value, 7u); + + Optional c = move(a); + EXPECT(c.has_value()); + EXPECT_EQ(c->value, 7u); + } + + { + Optional opt { SentinelInt { 5 } }; + auto released = opt.release_value(); + EXPECT_EQ(released.value, 5u); + EXPECT(!opt.has_value()); + } + + { + Optional opt; + EXPECT_EQ(opt.value_or(SentinelInt { 99 }).value, 99u); + + opt = SentinelInt { 10 }; + EXPECT_EQ(opt.value_or(SentinelInt { 99 }).value, 10u); + } +}