AK: Add OptionalBase class to reduce code duplication

Using CRTP and `static_cast`s because "deducing this" is
still not fully supported yet.
This commit is contained in:
Jonne Ransijn 2024-10-29 11:35:36 +01:00 committed by Andreas Kling
parent f80fca2dee
commit a70ed6a2ad
Notes: github-actions[bot] 2024-10-31 22:27:34 +00:00
3 changed files with 122 additions and 135 deletions

View file

@ -48,8 +48,127 @@ struct OptionalNone {
explicit OptionalNone() = default; explicit OptionalNone() = default;
}; };
template<typename T, typename Self = Optional<T>>
requires(!IsLvalueReference<Self>) class [[nodiscard]] OptionalBase {
public:
using ValueType = T;
template<SameAs<OptionalNone> V>
Self& operator=(V)
{
static_cast<Self&>(*this).clear();
return static_cast<Self&>(*this);
}
[[nodiscard]] ALWAYS_INLINE T* ptr() &
{
return static_cast<Self&>(*this).has_value() ? __builtin_launder(reinterpret_cast<T*>(&static_cast<Self&>(*this).value())) : nullptr;
}
[[nodiscard]] ALWAYS_INLINE T const* ptr() const&
{
return static_cast<Self const&>(*this).has_value() ? __builtin_launder(reinterpret_cast<T const*>(&static_cast<Self const&>(*this).value())) : nullptr;
}
[[nodiscard]] ALWAYS_INLINE T value_or(T const& fallback) const&
{
if (static_cast<Self const&>(*this).has_value())
return static_cast<Self const&>(*this).value();
return fallback;
}
[[nodiscard]] ALWAYS_INLINE T value_or(T&& fallback) &&
{
if (static_cast<Self&>(*this).has_value())
return move(static_cast<Self&>(*this).value());
return move(fallback);
}
template<typename Callback, typename O = T>
[[nodiscard]] ALWAYS_INLINE O value_or_lazy_evaluated(Callback callback) const
{
if (static_cast<Self const&>(*this).has_value())
return static_cast<Self const&>(*this).value();
return callback();
}
template<typename Callback, typename O = T>
[[nodiscard]] ALWAYS_INLINE Optional<O> value_or_lazy_evaluated_optional(Callback callback) const
{
if (static_cast<Self const&>(*this).has_value())
return static_cast<Self const&>(*this).value();
return callback();
}
template<typename Callback, typename O = T>
[[nodiscard]] ALWAYS_INLINE ErrorOr<O> try_value_or_lazy_evaluated(Callback callback) const
{
if (static_cast<Self const&>(*this).has_value())
return static_cast<Self const&>(*this).value();
return TRY(callback());
}
template<typename Callback, typename O = T>
[[nodiscard]] ALWAYS_INLINE ErrorOr<Optional<O>> try_value_or_lazy_evaluated_optional(Callback callback) const
{
if (static_cast<Self const&>(*this).has_value())
return static_cast<Self const&>(*this).value();
return TRY(callback());
}
template<typename O>
ALWAYS_INLINE bool operator==(Optional<O> const& other) const
{
return static_cast<Self const&>(*this).has_value() == (other).has_value()
&& (!static_cast<Self const&>(*this).has_value() || static_cast<Self const&>(*this).value() == (other).value());
}
template<typename O>
requires(!Detail::IsBaseOf<OptionalBase<T, Self>, O>)
ALWAYS_INLINE bool operator==(O const& other) const
{
return static_cast<Self const&>(*this).has_value() && static_cast<Self const&>(*this).value() == other;
}
[[nodiscard]] ALWAYS_INLINE T const& operator*() const { return static_cast<Self const&>(*this).value(); }
[[nodiscard]] ALWAYS_INLINE T& operator*() { return static_cast<Self&>(*this).value(); }
ALWAYS_INLINE T const* operator->() const { return &static_cast<Self const&>(*this).value(); }
ALWAYS_INLINE T* operator->() { return &static_cast<Self&>(*this).value(); }
template<typename F, typename MappedType = decltype(declval<F>()(declval<T&>())), auto IsErrorOr = IsSpecializationOf<MappedType, ErrorOr>, typename OptionalType = Optional<ConditionallyResultType<IsErrorOr, MappedType>>>
ALWAYS_INLINE Conditional<IsErrorOr, ErrorOr<OptionalType>, OptionalType> map(F&& mapper)
{
if constexpr (IsErrorOr) {
if (static_cast<Self&>(*this).has_value())
return OptionalType { TRY(mapper(static_cast<Self&>(*this).value())) };
return OptionalType {};
} else {
if (static_cast<Self&>(*this).has_value())
return OptionalType { mapper(static_cast<Self&>(*this).value()) };
return OptionalType {};
}
}
template<typename F, typename MappedType = decltype(declval<F>()(declval<T&>())), auto IsErrorOr = IsSpecializationOf<MappedType, ErrorOr>, typename OptionalType = Optional<ConditionallyResultType<IsErrorOr, MappedType>>>
ALWAYS_INLINE Conditional<IsErrorOr, ErrorOr<OptionalType>, OptionalType> map(F&& mapper) const
{
if constexpr (IsErrorOr) {
if (static_cast<Self const&>(*this).has_value())
return OptionalType { TRY(mapper(static_cast<Self const&>(*this).value())) };
return OptionalType {};
} else {
if (static_cast<Self const&>(*this).has_value())
return OptionalType { mapper(static_cast<Self const&>(*this).value()) };
return OptionalType {};
}
}
};
template<typename T> template<typename T>
requires(!IsLvalueReference<T>) class [[nodiscard]] Optional<T> { requires(!IsLvalueReference<T>) class [[nodiscard]] Optional<T> : public OptionalBase<T, Optional<T>> {
template<typename U> template<typename U>
friend class Optional; friend class Optional;
@ -202,16 +321,6 @@ public:
[[nodiscard]] ALWAYS_INLINE bool has_value() const { return m_has_value; } [[nodiscard]] ALWAYS_INLINE bool has_value() const { return m_has_value; }
[[nodiscard]] ALWAYS_INLINE T* ptr() &
{
return m_has_value ? __builtin_launder(reinterpret_cast<T*>(&m_storage)) : nullptr;
}
[[nodiscard]] ALWAYS_INLINE T const* ptr() const&
{
return m_has_value ? __builtin_launder(reinterpret_cast<T const*>(&m_storage)) : nullptr;
}
[[nodiscard]] ALWAYS_INLINE T& value() & [[nodiscard]] ALWAYS_INLINE T& value() &
{ {
VERIFY(m_has_value); VERIFY(m_has_value);
@ -238,88 +347,6 @@ public:
return released_value; return released_value;
} }
[[nodiscard]] ALWAYS_INLINE T value_or(T const& fallback) const&
{
if (m_has_value)
return value();
return fallback;
}
[[nodiscard]] ALWAYS_INLINE T value_or(T&& fallback) &&
{
if (m_has_value)
return move(value());
return move(fallback);
}
template<typename Callback>
[[nodiscard]] ALWAYS_INLINE T value_or_lazy_evaluated(Callback callback) const
{
if (m_has_value)
return value();
return callback();
}
template<typename Callback>
[[nodiscard]] ALWAYS_INLINE Optional<T> value_or_lazy_evaluated_optional(Callback callback) const
{
if (m_has_value)
return value();
return callback();
}
template<typename Callback>
[[nodiscard]] ALWAYS_INLINE ErrorOr<T> try_value_or_lazy_evaluated(Callback callback) const
{
if (m_has_value)
return value();
return TRY(callback());
}
template<typename Callback>
[[nodiscard]] ALWAYS_INLINE ErrorOr<Optional<T>> try_value_or_lazy_evaluated_optional(Callback callback) const
{
if (m_has_value)
return value();
return TRY(callback());
}
[[nodiscard]] ALWAYS_INLINE T const& operator*() const { return value(); }
[[nodiscard]] ALWAYS_INLINE T& operator*() { return value(); }
ALWAYS_INLINE T const* operator->() const { return &value(); }
ALWAYS_INLINE T* operator->() { return &value(); }
template<typename F, typename MappedType = decltype(declval<F>()(declval<T&>())), auto IsErrorOr = IsSpecializationOf<MappedType, ErrorOr>, typename OptionalType = Optional<ConditionallyResultType<IsErrorOr, MappedType>>>
ALWAYS_INLINE Conditional<IsErrorOr, ErrorOr<OptionalType>, OptionalType> map(F&& mapper)
{
if constexpr (IsErrorOr) {
if (m_has_value)
return OptionalType { TRY(mapper(value())) };
return OptionalType {};
} else {
if (m_has_value)
return OptionalType { mapper(value()) };
return OptionalType {};
}
}
template<typename F, typename MappedType = decltype(declval<F>()(declval<T&>())), auto IsErrorOr = IsSpecializationOf<MappedType, ErrorOr>, typename OptionalType = Optional<ConditionallyResultType<IsErrorOr, MappedType>>>
ALWAYS_INLINE Conditional<IsErrorOr, ErrorOr<OptionalType>, OptionalType> map(F&& mapper) const
{
if constexpr (IsErrorOr) {
if (m_has_value)
return OptionalType { TRY(mapper(value())) };
return OptionalType {};
} else {
if (m_has_value)
return OptionalType { mapper(value()) };
return OptionalType {};
}
}
private: private:
alignas(T) u8 m_storage[sizeof(T)]; alignas(T) u8 m_storage[sizeof(T)];
bool m_has_value { false }; bool m_has_value { false };

View file

@ -157,7 +157,7 @@ private:
namespace AK { namespace AK {
template<> template<>
class Optional<JS::Completion> { class Optional<JS::Completion> : public OptionalBase<JS::Completion> {
template<typename U> template<typename U>
friend class Optional; friend class Optional;
@ -237,26 +237,6 @@ public:
return released_value; return released_value;
} }
JS::Completion value_or(JS::Completion const& fallback) const&
{
if (has_value())
return value();
return fallback;
}
[[nodiscard]] JS::Completion value_or(JS::Completion&& fallback) &&
{
if (has_value())
return value();
return fallback;
}
JS::Completion const& operator*() const { return value(); }
JS::Completion& operator*() { return value(); }
JS::Completion const* operator->() const { return &value(); }
JS::Completion* operator->() { return &value(); }
private: private:
JS::Completion m_value { JS::Completion::EmptyTag {} }; JS::Completion m_value { JS::Completion::EmptyTag {} };
}; };

View file

@ -599,7 +599,7 @@ namespace AK {
static_assert(sizeof(JS::Value) == sizeof(double)); static_assert(sizeof(JS::Value) == sizeof(double));
template<> template<>
class Optional<JS::Value> { class Optional<JS::Value> : public OptionalBase<JS::Value> {
template<typename U> template<typename U>
friend class Optional; friend class Optional;
@ -702,26 +702,6 @@ public:
return released_value; return released_value;
} }
JS::Value value_or(JS::Value const& fallback) const&
{
if (has_value())
return value();
return fallback;
}
[[nodiscard]] JS::Value value_or(JS::Value&& fallback) &&
{
if (has_value())
return value();
return fallback;
}
JS::Value const& operator*() const { return value(); }
JS::Value& operator*() { return value(); }
JS::Value const* operator->() const { return &value(); }
JS::Value* operator->() { return &value(); }
private: private:
JS::Value m_value; JS::Value m_value;
}; };