AK: Add SentinelOptional

We specialize `Optional<T>` 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<T> needs to store. Because of padding, this often
means saving 4 to 8 bytes per instance.

By extending the new `SentinelOptional<T, Traits>`, these
specializations are significantly simplified to just having to define
what the sentinel value is, and how to identify a sentinel value.
This commit is contained in:
Jelle Raaijmakers
2026-03-18 20:43:56 +01:00
committed by Jelle Raaijmakers
parent 9e245b014c
commit e123d48043
Notes: github-actions[bot] 2026-03-20 11:04:58 +00:00
13 changed files with 215 additions and 988 deletions

View File

@@ -84,7 +84,7 @@ public:
}
private:
friend class Optional<FlyString>;
friend struct SentinelOptionalTraits<FlyString>;
explicit constexpr FlyString(nullptr_t)
: m_data(nullptr)
@@ -102,99 +102,15 @@ private:
};
template<>
class Optional<FlyString> : public OptionalBase<FlyString> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<FlyString> {
static constexpr FlyString sentinel_value() { return FlyString(nullptr); }
static constexpr bool is_sentinel(FlyString const& value) { return value.is_invalid(); }
};
template<>
class Optional<FlyString> : public SentinelOptional<FlyString> {
public:
using ValueType = FlyString;
constexpr Optional() = default;
template<SameAs<OptionalNone> V>
constexpr Optional(V) { }
constexpr Optional(Optional<FlyString> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
constexpr Optional(Optional&& other)
: m_value(move(other.m_value))
{
}
template<typename U = FlyString>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, FlyString>) constexpr Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<FlyString>> && IsConstructible<FlyString, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> 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<>

View File

@@ -661,9 +661,77 @@ struct Traits<Optional<T>> : public DefaultTraits<Optional<T>> {
}
};
template<typename T>
struct SentinelOptionalTraits;
template<typename T, typename Traits = SentinelOptionalTraits<T>>
class SentinelOptional : public OptionalBase<T> {
public:
SentinelOptional() = default;
template<SameAs<OptionalNone> V>
constexpr SentinelOptional(V) { }
template<typename U = T>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, T>) constexpr SentinelOptional(U&& value)
requires(!IsSame<RemoveCVReference<U>, SentinelOptional> && !IsSame<RemoveCVReference<U>, Optional<T>> && IsConstructible<T, U &&>)
: m_value(forward<U>(value))
{
}
template<typename U = T>
requires(!IsOneOfIgnoringCVReference<U, SentinelOptional, Optional<T>, OptionalNone> && IsConstructible<T, U &&>)
constexpr SentinelOptional& operator=(U&& value)
{
m_value = T(forward<U>(value));
return *this;
}
constexpr void clear()
{
m_value = Traits::sentinel_value();
}
[[nodiscard]] constexpr bool has_value() const
{
return !Traits::is_sentinel(m_value);
}
template<typename Self>
[[nodiscard]] constexpr auto& value(this Self& self)
{
VERIFY(self.has_value());
return self.m_value;
}
[[nodiscard]] constexpr T value() &&
{
return release_value();
}
template<typename Self>
[[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

View File

@@ -217,7 +217,7 @@ public:
private:
friend class ::AK::FlyString;
friend class Optional<String>;
friend struct SentinelOptionalTraits<String>;
using ShortString = Detail::ShortString;
@@ -238,97 +238,15 @@ private:
};
template<>
class Optional<String> : public OptionalBase<String> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<String> {
static constexpr String sentinel_value() { return String(nullptr); }
static constexpr bool is_sentinel(String const& value) { return value.is_invalid(); }
};
template<>
class Optional<String> : public SentinelOptional<String> {
public:
using ValueType = String;
constexpr Optional() = default;
template<SameAs<OptionalNone> V>
constexpr Optional(V) { }
constexpr Optional(Optional<String> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
constexpr Optional(Optional&& other)
: m_value(move(other.m_value))
{
}
template<typename U = String>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, String>) constexpr Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<String>> && IsConstructible<String, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> 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<>

View File

@@ -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<Optional<Utf16FlyString>>, nullptr_t)
constexpr Utf16FlyString(Badge<SentinelOptionalTraits<Utf16FlyString>>, nullptr_t)
: m_data(Badge<Utf16FlyString> {}, nullptr)
{
}
[[nodiscard]] constexpr bool is_invalid(Badge<Optional<Utf16FlyString>>) const { return m_data.raw({}) == 0; }
[[nodiscard]] constexpr bool is_invalid(Badge<SentinelOptionalTraits<Utf16FlyString>>) 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<Utf16FlyString> : public OptionalBase<Utf16FlyString> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<Utf16FlyString> {
static constexpr Utf16FlyString sentinel_value() { return Utf16FlyString({}, nullptr); }
static constexpr bool is_sentinel(Utf16FlyString const& value) { return value.is_invalid({}); }
};
template<>
class Optional<Utf16FlyString> : public SentinelOptional<Utf16FlyString> {
public:
using ValueType = Utf16FlyString;
constexpr Optional() = default;
template<SameAs<OptionalNone> V>
constexpr Optional(V) { }
constexpr Optional(Optional<Utf16FlyString> const& other)
: m_value(other.m_value)
{
}
constexpr Optional(Optional&& other)
: m_value(move(other.m_value))
{
}
template<typename U = Utf16FlyString>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, Utf16FlyString>) constexpr Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<Utf16FlyString>> && IsConstructible<Utf16FlyString, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> 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<>

View File

@@ -41,99 +41,15 @@ private:
namespace AK {
template<>
class Optional<JS::Bytecode::IdentifierTableIndex> : public OptionalBase<JS::Bytecode::IdentifierTableIndex> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<JS::Bytecode::IdentifierTableIndex> {
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<JS::Bytecode::IdentifierTableIndex> : public SentinelOptional<JS::Bytecode::IdentifierTableIndex> {
public:
using ValueType = JS::Bytecode::IdentifierTableIndex;
Optional() = default;
template<SameAs<OptionalNone> V>
Optional(V) { }
Optional(Optional<JS::Bytecode::IdentifierTableIndex> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
Optional(Optional&& other)
: m_value(other.m_value)
{
}
template<typename U = JS::Bytecode::IdentifierTableIndex>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, JS::Bytecode::IdentifierTableIndex>) Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<JS::Bytecode::IdentifierTableIndex>> && IsConstructible<JS::Bytecode::IdentifierTableIndex, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> 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;
};
}

View File

@@ -62,99 +62,15 @@ private:
namespace AK {
template<>
class Optional<JS::Bytecode::Operand> : public OptionalBase<JS::Bytecode::Operand> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<JS::Bytecode::Operand> {
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<JS::Bytecode::Operand> : public SentinelOptional<JS::Bytecode::Operand> {
public:
using ValueType = JS::Bytecode::Operand;
Optional() = default;
template<SameAs<OptionalNone> V>
Optional(V) { }
Optional(Optional<JS::Bytecode::Operand> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
Optional(Optional&& other)
: m_value(other.m_value)
{
}
template<typename U = JS::Bytecode::Operand>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, JS::Bytecode::Operand>) Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<JS::Bytecode::Operand>> && IsConstructible<JS::Bytecode::Operand, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> 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;
};
}

View File

@@ -47,99 +47,15 @@ private:
namespace AK {
template<>
class Optional<JS::Bytecode::PropertyKeyTableIndex> : public OptionalBase<JS::Bytecode::PropertyKeyTableIndex> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<JS::Bytecode::PropertyKeyTableIndex> {
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<JS::Bytecode::PropertyKeyTableIndex> : public SentinelOptional<JS::Bytecode::PropertyKeyTableIndex> {
public:
using ValueType = JS::Bytecode::PropertyKeyTableIndex;
Optional() = default;
template<SameAs<OptionalNone> V>
Optional(V) { }
Optional(Optional<JS::Bytecode::PropertyKeyTableIndex> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
Optional(Optional&& other)
: m_value(other.m_value)
{
}
template<typename U = JS::Bytecode::PropertyKeyTableIndex>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, JS::Bytecode::PropertyKeyTableIndex>) Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<JS::Bytecode::PropertyKeyTableIndex>> && IsConstructible<JS::Bytecode::PropertyKeyTableIndex, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> 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;
};
}

View File

@@ -39,99 +39,15 @@ private:
namespace AK {
template<>
class Optional<JS::Bytecode::StringTableIndex> : public OptionalBase<JS::Bytecode::StringTableIndex> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<JS::Bytecode::StringTableIndex> {
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<JS::Bytecode::StringTableIndex> : public SentinelOptional<JS::Bytecode::StringTableIndex> {
public:
using ValueType = JS::Bytecode::StringTableIndex;
Optional() = default;
template<SameAs<OptionalNone> V>
Optional(V) { }
Optional(Optional<JS::Bytecode::StringTableIndex> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
Optional(Optional&& other)
: m_value(other.m_value)
{
}
template<typename U = JS::Bytecode::StringTableIndex>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, JS::Bytecode::StringTableIndex>) Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<JS::Bytecode::StringTableIndex>> && IsConstructible<JS::Bytecode::StringTableIndex, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> 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;
};
}

View File

@@ -113,7 +113,7 @@ public:
private:
class EmptyTag {
};
friend AK::Optional<Completion>;
friend struct AK::SentinelOptionalTraits<Completion>;
constexpr Completion(EmptyTag)
: m_type(Type::Empty)
@@ -135,88 +135,15 @@ private:
namespace AK {
template<>
class Optional<JS::Completion> : public OptionalBase<JS::Completion> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<JS::Completion> {
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<JS::Completion> : public SentinelOptional<JS::Completion> {
public:
using ValueType = JS::Completion;
constexpr Optional() = default;
constexpr Optional(Optional<JS::Completion> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
constexpr Optional(Optional&& other)
: m_value(move(other.m_value))
{
}
template<typename U = JS::Completion>
explicit(!IsConvertible<U&&, JS::Completion>) constexpr Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<JS::Completion>> && IsConstructible<JS::Completion, U &&>)
: m_value(forward<U>(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;
};
}

View File

@@ -179,7 +179,7 @@ public:
private:
friend Traits<PropertyKey>;
friend class Optional<PropertyKey>;
friend struct AK::SentinelOptionalTraits<PropertyKey>;
enum class ShouldMakeEmptyOptional {
Indeed,
@@ -242,99 +242,15 @@ struct Formatter<JS::PropertyKey> : Formatter<Utf16String> {
};
template<>
class Optional<JS::PropertyKey> : public OptionalBase<JS::PropertyKey> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<JS::PropertyKey> {
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<JS::PropertyKey> : public SentinelOptional<JS::PropertyKey> {
public:
using ValueType = JS::PropertyKey;
Optional() = default;
template<SameAs<OptionalNone> V>
Optional(V) { }
Optional(Optional<JS::PropertyKey> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
Optional(Optional&& other)
: m_value(other.m_value)
{
}
template<typename U = JS::PropertyKey>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, JS::PropertyKey>) Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<JS::PropertyKey>> && IsConstructible<JS::PropertyKey, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> 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;
};
}

View File

@@ -99,99 +99,15 @@ struct Traits<JS::Utf16String> : public DefaultTraits<JS::Utf16String> {
};
template<>
class Optional<JS::Utf16String> : public OptionalBase<JS::Utf16String> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<JS::Utf16String> {
static JS::Utf16String sentinel_value() { return JS::Utf16String::invalid(); }
static bool is_sentinel(JS::Utf16String const& value) { return !value.is_valid(); }
};
template<>
class Optional<JS::Utf16String> : public SentinelOptional<JS::Utf16String> {
public:
using ValueType = JS::Utf16String;
Optional() = default;
template<SameAs<OptionalNone> V>
Optional(V) { }
Optional(Optional<JS::Utf16String> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
Optional(Optional&& other)
: m_value(other.m_value)
{
}
template<typename U = JS::Utf16String>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, JS::Utf16String>) Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<JS::Utf16String>> && IsConstructible<JS::Utf16String, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> 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;
};
}

View File

@@ -608,99 +608,15 @@ namespace AK {
static_assert(sizeof(JS::Value) == sizeof(double));
template<>
class Optional<JS::Value> : public OptionalBase<JS::Value> {
template<typename U>
friend class Optional;
struct SentinelOptionalTraits<JS::Value> {
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<JS::Value> : public SentinelOptional<JS::Value> {
public:
using ValueType = JS::Value;
constexpr Optional() = default;
template<SameAs<OptionalNone> V>
constexpr Optional(V) { }
constexpr Optional(Optional<JS::Value> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
constexpr Optional(Optional&& other)
: m_value(other.m_value)
{
}
template<typename U = JS::Value>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, JS::Value>) constexpr Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<JS::Value>> && IsConstructible<JS::Value, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> 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;
};
}

View File

@@ -9,6 +9,7 @@
#include <AK/ByteString.h>
#include <AK/FlyString.h>
#include <AK/NumericLimits.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Vector.h>
@@ -662,3 +663,66 @@ TEST_CASE(flystring_specialization)
EXPECT_EQ((Optional<FlyString> {}).value_or("fallback_value"_fly_string), "fallback_value"sv);
}
}
struct SentinelInt {
u32 value { 0 };
static constexpr u32 invalid = NumericLimits<u32>::max();
bool is_valid() const { return value != invalid; }
};
template<>
struct AK::SentinelOptionalTraits<SentinelInt> {
static constexpr SentinelInt sentinel_value() { return { SentinelInt::invalid }; }
static constexpr bool is_sentinel(SentinelInt const& v) { return !v.is_valid(); }
};
template<>
class AK::Optional<SentinelInt> : public SentinelOptional<SentinelInt> {
public:
using SentinelOptional::SentinelOptional;
};
TEST_CASE(sentinel_optional)
{
static_assert(sizeof(Optional<SentinelInt>) == sizeof(SentinelInt));
static_assert(sizeof(Optional<FlyString>) == sizeof(FlyString));
static_assert(sizeof(Optional<String>) == sizeof(String));
{
Optional<SentinelInt> empty;
EXPECT(!empty.has_value());
Optional<SentinelInt> with_value { SentinelInt { 42 } };
EXPECT(with_value.has_value());
EXPECT_EQ(with_value->value, 42u);
with_value = {};
EXPECT(!with_value.has_value());
}
{
Optional<SentinelInt> a { SentinelInt { 7 } };
Optional<SentinelInt> b = a;
EXPECT(b.has_value());
EXPECT_EQ(b->value, 7u);
Optional<SentinelInt> c = move(a);
EXPECT(c.has_value());
EXPECT_EQ(c->value, 7u);
}
{
Optional<SentinelInt> opt { SentinelInt { 5 } };
auto released = opt.release_value();
EXPECT_EQ(released.value, 5u);
EXPECT(!opt.has_value());
}
{
Optional<SentinelInt> opt;
EXPECT_EQ(opt.value_or(SentinelInt { 99 }).value, 99u);
opt = SentinelInt { 10 };
EXPECT_EQ(opt.value_or(SentinelInt { 99 }).value, 10u);
}
}