From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- mfbt/Maybe.h | 977 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 977 insertions(+) create mode 100644 mfbt/Maybe.h (limited to 'mfbt/Maybe.h') diff --git a/mfbt/Maybe.h b/mfbt/Maybe.h new file mode 100644 index 0000000000..100b139c79 --- /dev/null +++ b/mfbt/Maybe.h @@ -0,0 +1,977 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* A class for optional values and in-place lazy construction. */ + +#ifndef mozilla_Maybe_h +#define mozilla_Maybe_h + +#include // for placement new +#include +#include +#include + +#include "mozilla/Alignment.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/MaybeStorageBase.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/Poison.h" +#include "mozilla/ThreadSafety.h" + +class nsCycleCollectionTraversalCallback; + +template +inline void CycleCollectionNoteChild( + nsCycleCollectionTraversalCallback& aCallback, T* aChild, const char* aName, + uint32_t aFlags); + +namespace mozilla { + +struct Nothing {}; + +inline constexpr bool operator==(const Nothing&, const Nothing&) { + return true; +} + +template +class Maybe; + +namespace detail { + +// You would think that poisoning Maybe instances could just be a call +// to mozWritePoison. Unfortunately, using a simple call to +// mozWritePoison generates poor code on MSVC for small structures. The +// generated code contains (always not-taken) branches and does a bunch +// of setup for `rep stos{l,q}`, even though we know at compile time +// exactly how many words we're poisoning. Instead, we're going to +// force MSVC to generate the code we want via recursive templates. + +// Write the given poisonValue into p at offset*sizeof(uintptr_t). +template +inline void WritePoisonAtOffset(void* p, const uintptr_t poisonValue) { + memcpy(static_cast(p) + offset * sizeof(poisonValue), &poisonValue, + sizeof(poisonValue)); +} + +template +struct InlinePoisoner { + static void poison(void* p, const uintptr_t poisonValue) { + WritePoisonAtOffset(p, poisonValue); + InlinePoisoner::poison(p, poisonValue); + } +}; + +template +struct InlinePoisoner { + static void poison(void*, const uintptr_t) { + // All done! + } +}; + +// We can't generate inline code for large structures, though, because we'll +// blow out recursive template instantiation limits, and the code would be +// bloated to boot. So provide a fallback to the out-of-line poisoner. +template +struct OutOfLinePoisoner { + static MOZ_NEVER_INLINE void poison(void* p, const uintptr_t) { + mozWritePoison(p, ObjectSize); + } +}; + +template +inline void PoisonObject(T* p) { + const uintptr_t POISON = mozPoisonValue(); + std::conditional_t<(sizeof(T) <= 8 * sizeof(POISON)), + InlinePoisoner<0, sizeof(T) / sizeof(POISON)>, + OutOfLinePoisoner>::poison(p, POISON); +} + +template +struct MaybePoisoner { + static const size_t N = sizeof(T); + + static void poison(void* aPtr) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (N >= sizeof(uintptr_t)) { + PoisonObject(static_cast*>(aPtr)); + } +#endif + MOZ_MAKE_MEM_UNDEFINED(aPtr, N); + } +}; + +template , + bool Copyable = std::is_copy_constructible_v, + bool Movable = std::is_move_constructible_v> +class Maybe_CopyMove_Enabler; + +#define MOZ_MAYBE_COPY_OPS() \ + Maybe_CopyMove_Enabler(const Maybe_CopyMove_Enabler& aOther) { \ + if (downcast(aOther).isSome()) { \ + downcast(*this).emplace(*downcast(aOther)); \ + } \ + } \ + \ + Maybe_CopyMove_Enabler& operator=(const Maybe_CopyMove_Enabler& aOther) { \ + return downcast(*this).template operator= (downcast(aOther)); \ + } + +#define MOZ_MAYBE_MOVE_OPS() \ + constexpr Maybe_CopyMove_Enabler(Maybe_CopyMove_Enabler&& aOther) { \ + if (downcast(aOther).isSome()) { \ + downcast(*this).emplace(std::move(*downcast(aOther))); \ + downcast(aOther).reset(); \ + } \ + } \ + \ + constexpr Maybe_CopyMove_Enabler& operator=( \ + Maybe_CopyMove_Enabler&& aOther) { \ + downcast(*this).template operator= (std::move(downcast(aOther))); \ + \ + return *this; \ + } + +#define MOZ_MAYBE_DOWNCAST() \ + static constexpr Maybe& downcast(Maybe_CopyMove_Enabler& aObj) { \ + return static_cast&>(aObj); \ + } \ + static constexpr const Maybe& downcast( \ + const Maybe_CopyMove_Enabler& aObj) { \ + return static_cast&>(aObj); \ + } + +template +class Maybe_CopyMove_Enabler { + public: + Maybe_CopyMove_Enabler() = default; + + Maybe_CopyMove_Enabler(const Maybe_CopyMove_Enabler&) = default; + Maybe_CopyMove_Enabler& operator=(const Maybe_CopyMove_Enabler&) = default; + constexpr Maybe_CopyMove_Enabler(Maybe_CopyMove_Enabler&& aOther) { + downcast(aOther).reset(); + } + constexpr Maybe_CopyMove_Enabler& operator=(Maybe_CopyMove_Enabler&& aOther) { + downcast(aOther).reset(); + return *this; + } + + private: + MOZ_MAYBE_DOWNCAST() +}; + +template +class Maybe_CopyMove_Enabler { + public: + Maybe_CopyMove_Enabler() = default; + + Maybe_CopyMove_Enabler(const Maybe_CopyMove_Enabler&) = delete; + Maybe_CopyMove_Enabler& operator=(const Maybe_CopyMove_Enabler&) = delete; + constexpr Maybe_CopyMove_Enabler(Maybe_CopyMove_Enabler&& aOther) { + downcast(aOther).reset(); + } + constexpr Maybe_CopyMove_Enabler& operator=(Maybe_CopyMove_Enabler&& aOther) { + downcast(aOther).reset(); + return *this; + } + + private: + MOZ_MAYBE_DOWNCAST() +}; + +template +class Maybe_CopyMove_Enabler { + public: + Maybe_CopyMove_Enabler() = default; + + MOZ_MAYBE_COPY_OPS() + MOZ_MAYBE_MOVE_OPS() + + private: + MOZ_MAYBE_DOWNCAST() +}; + +template +class Maybe_CopyMove_Enabler { + public: + Maybe_CopyMove_Enabler() = default; + + MOZ_MAYBE_MOVE_OPS() + + private: + MOZ_MAYBE_DOWNCAST() +}; + +template +class Maybe_CopyMove_Enabler { + public: + Maybe_CopyMove_Enabler() = default; + + MOZ_MAYBE_COPY_OPS() + + private: + MOZ_MAYBE_DOWNCAST() +}; + +template +class Maybe_CopyMove_Enabler { + public: + Maybe_CopyMove_Enabler() = default; + + Maybe_CopyMove_Enabler(const Maybe_CopyMove_Enabler&) = delete; + Maybe_CopyMove_Enabler& operator=(const Maybe_CopyMove_Enabler&) = delete; + Maybe_CopyMove_Enabler(Maybe_CopyMove_Enabler&&) = delete; + Maybe_CopyMove_Enabler& operator=(Maybe_CopyMove_Enabler&&) = delete; +}; + +#undef MOZ_MAYBE_COPY_OPS +#undef MOZ_MAYBE_MOVE_OPS +#undef MOZ_MAYBE_DOWNCAST + +template > +struct MaybeStorage; + +template +struct MaybeStorage : MaybeStorageBase { + protected: + char mIsSome = false; // not bool -- guarantees minimal space consumption + + MaybeStorage() = default; + explicit MaybeStorage(const T& aVal) + : MaybeStorageBase{aVal}, mIsSome{true} {} + explicit MaybeStorage(T&& aVal) + : MaybeStorageBase{std::move(aVal)}, mIsSome{true} {} + + template + explicit MaybeStorage(std::in_place_t, Args&&... aArgs) + : MaybeStorageBase{std::in_place, std::forward(aArgs)...}, + mIsSome{true} {} + + public: + // Copy and move operations are no-ops, since copying is moving is implemented + // by Maybe_CopyMove_Enabler. + + MaybeStorage(const MaybeStorage&) : MaybeStorageBase{} {} + MaybeStorage& operator=(const MaybeStorage&) { return *this; } + MaybeStorage(MaybeStorage&&) : MaybeStorageBase{} {} + MaybeStorage& operator=(MaybeStorage&&) { return *this; } + + ~MaybeStorage() { + if (mIsSome) { + this->addr()->T::~T(); + } + } +}; + +template +struct MaybeStorage : MaybeStorageBase { + protected: + char mIsSome = false; // not bool -- guarantees minimal space consumption + + constexpr MaybeStorage() = default; + constexpr explicit MaybeStorage(const T& aVal) + : MaybeStorageBase{aVal}, mIsSome{true} {} + constexpr explicit MaybeStorage(T&& aVal) + : MaybeStorageBase{std::move(aVal)}, mIsSome{true} {} + + template + constexpr explicit MaybeStorage(std::in_place_t, Args&&... aArgs) + : MaybeStorageBase{std::in_place, std::forward(aArgs)...}, + mIsSome{true} {} +}; + +} // namespace detail + +template ::type>::type> +constexpr Maybe Some(T&& aValue); + +/* + * Maybe is a container class which contains either zero or one elements. It + * serves two roles. It can represent values which are *semantically* optional, + * augmenting a type with an explicit 'Nothing' value. In this role, it provides + * methods that make it easy to work with values that may be missing, along with + * equality and comparison operators so that Maybe values can be stored in + * containers. Maybe values can be constructed conveniently in expressions using + * type inference, as follows: + * + * void doSomething(Maybe aFoo) { + * if (aFoo) // Make sure that aFoo contains a value... + * aFoo->takeAction(); // and then use |aFoo->| to access it. + * } // |*aFoo| also works! + * + * doSomething(Nothing()); // Passes a Maybe containing no value. + * doSomething(Some(Foo(100))); // Passes a Maybe containing |Foo(100)|. + * + * You'll note that it's important to check whether a Maybe contains a value + * before using it, using conversion to bool, |isSome()|, or |isNothing()|. You + * can avoid these checks, and sometimes write more readable code, using + * |valueOr()|, |ptrOr()|, and |refOr()|, which allow you to retrieve the value + * in the Maybe and provide a default for the 'Nothing' case. You can also use + * |apply()| to call a function only if the Maybe holds a value, and |map()| to + * transform the value in the Maybe, returning another Maybe with a possibly + * different type. + * + * Maybe's other role is to support lazily constructing objects without using + * dynamic storage. A Maybe directly contains storage for a value, but it's + * empty by default. |emplace()|, as mentioned above, can be used to construct a + * value in Maybe's storage. The value a Maybe contains can be destroyed by + * calling |reset()|; this will happen automatically if a Maybe is destroyed + * while holding a value. + * + * It's a common idiom in C++ to use a pointer as a 'Maybe' type, with a null + * value meaning 'Nothing' and any other value meaning 'Some'. You can convert + * from such a pointer to a Maybe value using 'ToMaybe()'. + * + * Maybe is inspired by similar types in the standard library of many other + * languages (e.g. Haskell's Maybe and Rust's Option). In the C++ world it's + * very similar to std::optional, which was proposed for C++14 and originated in + * Boost. The most important differences between Maybe and std::optional are: + * + * - std::optional may be compared with T. We deliberately forbid that. + * - std::optional has |valueOr()|, equivalent to Maybe's |valueOr()|, but + * lacks corresponding methods for |refOr()| and |ptrOr()|. + * - std::optional lacks |map()| and |apply()|, making it less suitable for + * functional-style code. + * - std::optional lacks many convenience functions that Maybe has. Most + * unfortunately, it lacks equivalents of the type-inferred constructor + * functions |Some()| and |Nothing()|. + */ +template +class MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS Maybe + : private detail::MaybeStorage, + public detail::Maybe_CopyMove_Enabler { + template + friend class detail::Maybe_CopyMove_Enabler; + + template + friend constexpr Maybe Some(U&& aValue); + + struct SomeGuard {}; + + template + constexpr Maybe(U&& aValue, SomeGuard) + : detail::MaybeStorage{std::forward(aValue)} {} + + using detail::MaybeStorage::mIsSome; + using detail::MaybeStorage::mStorage; + + void poisonData() { detail::MaybePoisoner::poison(&mStorage.val); } + + public: + using ValueType = T; + + MOZ_ALLOW_TEMPORARY constexpr Maybe() = default; + + MOZ_ALLOW_TEMPORARY MOZ_IMPLICIT constexpr Maybe(Nothing) : Maybe{} {} + + template + constexpr explicit Maybe(std::in_place_t, Args&&... aArgs) + : detail::MaybeStorage{std::in_place, std::forward(aArgs)...} {} + + /** + * Maybe can be copy-constructed from a Maybe if T is constructible from + * a const U&. + */ + template >> + MOZ_IMPLICIT Maybe(const Maybe& aOther) { + if (aOther.isSome()) { + emplace(*aOther); + } + } + + /** + * Maybe can be move-constructed from a Maybe if T is constructible from + * a U&&. + */ + template >> + MOZ_IMPLICIT Maybe(Maybe&& aOther) { + if (aOther.isSome()) { + emplace(std::move(*aOther)); + aOther.reset(); + } + } + + template >> + Maybe& operator=(const Maybe& aOther) { + if (aOther.isSome()) { + if (mIsSome) { + ref() = aOther.ref(); + } else { + emplace(*aOther); + } + } else { + reset(); + } + return *this; + } + + template >> + Maybe& operator=(Maybe&& aOther) { + if (aOther.isSome()) { + if (mIsSome) { + ref() = std::move(aOther.ref()); + } else { + emplace(std::move(*aOther)); + } + aOther.reset(); + } else { + reset(); + } + + return *this; + } + + constexpr Maybe& operator=(Nothing) { + reset(); + return *this; + } + + /* Methods that check whether this Maybe contains a value */ + constexpr explicit operator bool() const { return isSome(); } + constexpr bool isSome() const { return mIsSome; } + constexpr bool isNothing() const { return !mIsSome; } + + /* Returns the contents of this Maybe by value. Unsafe unless |isSome()|. + */ + constexpr T value() const&; + constexpr T value() &&; + constexpr T value() const&&; + + /** + * Move the contents of this Maybe out of internal storage and return it + * without calling the destructor. The internal storage is also reset to + * avoid multiple calls. Unsafe unless |isSome()|. + */ + T extract() { + MOZ_RELEASE_ASSERT(isSome()); + T v = std::move(mStorage.val); + reset(); + return v; + } + + /** + * Returns the value (possibly |Nothing()|) by moving it out of this Maybe + * and leaving |Nothing()| in its place. + */ + Maybe take() { return std::exchange(*this, Nothing()); } + + /* + * Returns the contents of this Maybe by value. If |isNothing()|, returns + * the default value provided. + * + * Note: If the value passed to aDefault is not the result of a trivial + * expression, but expensive to evaluate, e.g. |valueOr(ExpensiveFunction())|, + * use |valueOrFrom| instead, e.g. + * |valueOrFrom([arg] { return ExpensiveFunction(arg); })|. This ensures + * that the expensive expression is only evaluated when its result will + * actually be used. + */ + template + constexpr T valueOr(V&& aDefault) const { + if (isSome()) { + return ref(); + } + return std::forward(aDefault); + } + + /* + * Returns the contents of this Maybe by value. If |isNothing()|, returns + * the value returned from the function or functor provided. + */ + template + constexpr T valueOrFrom(F&& aFunc) const { + if (isSome()) { + return ref(); + } + return aFunc(); + } + + /* Returns the contents of this Maybe by pointer. Unsafe unless |isSome()|. + */ + T* ptr(); + constexpr const T* ptr() const; + + /* + * Returns the contents of this Maybe by pointer. If |isNothing()|, + * returns the default value provided. + */ + T* ptrOr(T* aDefault) { + if (isSome()) { + return ptr(); + } + return aDefault; + } + + constexpr const T* ptrOr(const T* aDefault) const { + if (isSome()) { + return ptr(); + } + return aDefault; + } + + /* + * Returns the contents of this Maybe by pointer. If |isNothing()|, + * returns the value returned from the function or functor provided. + */ + template + T* ptrOrFrom(F&& aFunc) { + if (isSome()) { + return ptr(); + } + return aFunc(); + } + + template + const T* ptrOrFrom(F&& aFunc) const { + if (isSome()) { + return ptr(); + } + return aFunc(); + } + + constexpr T* operator->(); + constexpr const T* operator->() const; + + /* Returns the contents of this Maybe by ref. Unsafe unless |isSome()|. */ + constexpr T& ref() &; + constexpr const T& ref() const&; + constexpr T&& ref() &&; + constexpr const T&& ref() const&&; + + /* + * Returns the contents of this Maybe by ref. If |isNothing()|, returns + * the default value provided. + */ + constexpr T& refOr(T& aDefault) { + if (isSome()) { + return ref(); + } + return aDefault; + } + + constexpr const T& refOr(const T& aDefault) const { + if (isSome()) { + return ref(); + } + return aDefault; + } + + /* + * Returns the contents of this Maybe by ref. If |isNothing()|, returns the + * value returned from the function or functor provided. + */ + template + constexpr T& refOrFrom(F&& aFunc) { + if (isSome()) { + return ref(); + } + return aFunc(); + } + + template + constexpr const T& refOrFrom(F&& aFunc) const { + if (isSome()) { + return ref(); + } + return aFunc(); + } + + constexpr T& operator*() &; + constexpr const T& operator*() const&; + constexpr T&& operator*() &&; + constexpr const T&& operator*() const&&; + + /* If |isSome()|, runs the provided function or functor on the contents of + * this Maybe. */ + template + constexpr Maybe& apply(Func&& aFunc) { + if (isSome()) { + std::forward(aFunc)(ref()); + } + return *this; + } + + template + constexpr const Maybe& apply(Func&& aFunc) const { + if (isSome()) { + std::forward(aFunc)(ref()); + } + return *this; + } + + /* + * If |isSome()|, runs the provided function and returns the result wrapped + * in a Maybe. If |isNothing()|, returns an empty Maybe value with the same + * value type as what the provided function would have returned. + */ + template + constexpr auto map(Func&& aFunc) { + if (isSome()) { + return Some(std::forward(aFunc)(ref())); + } + return Maybe(aFunc)(ref()))>{}; + } + + template + constexpr auto map(Func&& aFunc) const { + if (isSome()) { + return Some(std::forward(aFunc)(ref())); + } + return Maybe(aFunc)(ref()))>{}; + } + + /* If |isSome()|, empties this Maybe and destroys its contents. */ + constexpr void reset() { + if (isSome()) { + if constexpr (!std::is_trivially_destructible_v) { + /* + * Static analyzer gets confused if we have Maybe, + * so we suppress thread-safety warnings here + */ + MOZ_PUSH_IGNORE_THREAD_SAFETY + ref().T::~T(); + MOZ_POP_THREAD_SAFETY + poisonData(); + } + mIsSome = false; + } + } + + /* + * Constructs a T value in-place in this empty Maybe's storage. The + * arguments to |emplace()| are the parameters to T's constructor. + */ + template + constexpr void emplace(Args&&... aArgs); + + template + constexpr std::enable_if_t && + std::is_copy_constructible_v && + !std::is_move_constructible_v> + emplace(U&& aArgs) { + emplace(aArgs); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const Maybe& aMaybe) { + if (aMaybe) { + aStream << aMaybe.ref(); + } else { + aStream << ""; + } + return aStream; + } +}; + +template +class Maybe { + public: + constexpr Maybe() = default; + constexpr MOZ_IMPLICIT Maybe(Nothing) {} + + void emplace(T& aRef) { mValue = &aRef; } + + /* Methods that check whether this Maybe contains a value */ + constexpr explicit operator bool() const { return isSome(); } + constexpr bool isSome() const { return mValue; } + constexpr bool isNothing() const { return !mValue; } + + T& ref() const { + MOZ_RELEASE_ASSERT(isSome()); + return *mValue; + } + + T* operator->() const { return &ref(); } + T& operator*() const { return ref(); } + + // Deliberately not defining value and ptr accessors, as these may be + // confusing on a reference-typed Maybe. + + // XXX Should we define refOr? + + void reset() { mValue = nullptr; } + + template + Maybe& apply(Func&& aFunc) { + if (isSome()) { + std::forward(aFunc)(ref()); + } + return *this; + } + + template + const Maybe& apply(Func&& aFunc) const { + if (isSome()) { + std::forward(aFunc)(ref()); + } + return *this; + } + + template + auto map(Func&& aFunc) { + Maybe(aFunc)(ref()))> val; + if (isSome()) { + val.emplace(std::forward(aFunc)(ref())); + } + return val; + } + + template + auto map(Func&& aFunc) const { + Maybe(aFunc)(ref()))> val; + if (isSome()) { + val.emplace(std::forward(aFunc)(ref())); + } + return val; + } + + bool refEquals(const Maybe& aOther) const { + return mValue == aOther.mValue; + } + + bool refEquals(const T& aOther) const { return mValue == &aOther; } + + private: + T* mValue = nullptr; +}; + +template +constexpr T Maybe::value() const& { + MOZ_RELEASE_ASSERT(isSome()); + return ref(); +} + +template +constexpr T Maybe::value() && { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(ref()); +} + +template +constexpr T Maybe::value() const&& { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(ref()); +} + +template +T* Maybe::ptr() { + MOZ_RELEASE_ASSERT(isSome()); + return &ref(); +} + +template +constexpr const T* Maybe::ptr() const { + MOZ_RELEASE_ASSERT(isSome()); + return &ref(); +} + +template +constexpr T* Maybe::operator->() { + MOZ_RELEASE_ASSERT(isSome()); + return ptr(); +} + +template +constexpr const T* Maybe::operator->() const { + MOZ_RELEASE_ASSERT(isSome()); + return ptr(); +} + +template +constexpr T& Maybe::ref() & { + MOZ_RELEASE_ASSERT(isSome()); + return mStorage.val; +} + +template +constexpr const T& Maybe::ref() const& { + MOZ_RELEASE_ASSERT(isSome()); + return mStorage.val; +} + +template +constexpr T&& Maybe::ref() && { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(mStorage.val); +} + +template +constexpr const T&& Maybe::ref() const&& { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(mStorage.val); +} + +template +constexpr T& Maybe::operator*() & { + MOZ_RELEASE_ASSERT(isSome()); + return ref(); +} + +template +constexpr const T& Maybe::operator*() const& { + MOZ_RELEASE_ASSERT(isSome()); + return ref(); +} + +template +constexpr T&& Maybe::operator*() && { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(ref()); +} + +template +constexpr const T&& Maybe::operator*() const&& { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(ref()); +} + +template +template +constexpr void Maybe::emplace(Args&&... aArgs) { + MOZ_RELEASE_ASSERT(!isSome()); + ::new (KnownNotNull, &mStorage.val) T(std::forward(aArgs)...); + mIsSome = true; +} + +/* + * Some() creates a Maybe value containing the provided T value. If T has a + * move constructor, it's used to make this as efficient as possible. + * + * Some() selects the type of Maybe it returns by removing any const, volatile, + * or reference qualifiers from the type of the value you pass to it. This gives + * it more intuitive behavior when used in expressions, but it also means that + * if you need to construct a Maybe value that holds a const, volatile, or + * reference value, you need to use emplace() instead. + */ +template +constexpr Maybe Some(T&& aValue) { + return {std::forward(aValue), typename Maybe::SomeGuard{}}; +} + +template +constexpr Maybe SomeRef(T& aValue) { + Maybe value; + value.emplace(aValue); + return value; +} + +template +constexpr Maybe ToMaybeRef(T* const aPtr) { + return aPtr ? SomeRef(*aPtr) : Nothing{}; +} + +template +Maybe>> ToMaybe(T* aPtr) { + if (aPtr) { + return Some(*aPtr); + } + return Nothing(); +} + +/* + * Two Maybe values are equal if + * - both are Nothing, or + * - both are Some, and the values they contain are equal. + */ +template +constexpr bool operator==(const Maybe& aLHS, const Maybe& aRHS) { + static_assert(!std::is_reference_v, + "operator== is not defined for Maybe, compare values or " + "addresses explicitly instead"); + if (aLHS.isNothing() != aRHS.isNothing()) { + return false; + } + return aLHS.isNothing() || *aLHS == *aRHS; +} + +template +constexpr bool operator!=(const Maybe& aLHS, const Maybe& aRHS) { + return !(aLHS == aRHS); +} + +/* + * We support comparison to Nothing to allow reasonable expressions like: + * if (maybeValue == Nothing()) { ... } + */ +template +constexpr bool operator==(const Maybe& aLHS, const Nothing& aRHS) { + return aLHS.isNothing(); +} + +template +constexpr bool operator!=(const Maybe& aLHS, const Nothing& aRHS) { + return !(aLHS == aRHS); +} + +template +constexpr bool operator==(const Nothing& aLHS, const Maybe& aRHS) { + return aRHS.isNothing(); +} + +template +constexpr bool operator!=(const Nothing& aLHS, const Maybe& aRHS) { + return !(aLHS == aRHS); +} + +/* + * Maybe values are ordered in the same way T values are ordered, except that + * Nothing comes before anything else. + */ +template +constexpr bool operator<(const Maybe& aLHS, const Maybe& aRHS) { + if (aLHS.isNothing()) { + return aRHS.isSome(); + } + if (aRHS.isNothing()) { + return false; + } + return *aLHS < *aRHS; +} + +template +constexpr bool operator>(const Maybe& aLHS, const Maybe& aRHS) { + return !(aLHS < aRHS || aLHS == aRHS); +} + +template +constexpr bool operator<=(const Maybe& aLHS, const Maybe& aRHS) { + return aLHS < aRHS || aLHS == aRHS; +} + +template +constexpr bool operator>=(const Maybe& aLHS, const Maybe& aRHS) { + return !(aLHS < aRHS); +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, mozilla::Maybe& aField, + const char* aName, uint32_t aFlags = 0) { + if (aField) { + ImplCycleCollectionTraverse(aCallback, aField.ref(), aName, aFlags); + } +} + +template +inline void ImplCycleCollectionUnlink(mozilla::Maybe& aField) { + if (aField) { + ImplCycleCollectionUnlink(aField.ref()); + } +} + +} // namespace mozilla + +#endif /* mozilla_Maybe_h */ -- cgit v1.2.3