diff options
Diffstat (limited to '')
-rw-r--r-- | mfbt/Maybe.h | 977 |
1 files changed, 977 insertions, 0 deletions
diff --git a/mfbt/Maybe.h b/mfbt/Maybe.h new file mode 100644 index 0000000000..3298370f49 --- /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 <new> // for placement new +#include <ostream> +#include <type_traits> +#include <utility> + +#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 <typename T> +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 T> +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 <size_t offset> +inline void WritePoisonAtOffset(void* p, const uintptr_t poisonValue) { + memcpy(static_cast<char*>(p) + offset * sizeof(poisonValue), &poisonValue, + sizeof(poisonValue)); +} + +template <size_t Offset, size_t NOffsets> +struct InlinePoisoner { + static void poison(void* p, const uintptr_t poisonValue) { + WritePoisonAtOffset<Offset>(p, poisonValue); + InlinePoisoner<Offset + 1, NOffsets>::poison(p, poisonValue); + } +}; + +template <size_t N> +struct InlinePoisoner<N, N> { + 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 <size_t ObjectSize> +struct OutOfLinePoisoner { + static MOZ_NEVER_INLINE void poison(void* p, const uintptr_t) { + mozWritePoison(p, ObjectSize); + } +}; + +template <typename T> +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<sizeof(T)>>::poison(p, POISON); +} + +template <typename T> +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<std::remove_cv_t<T>*>(aPtr)); + } +#endif + MOZ_MAKE_MEM_UNDEFINED(aPtr, N); + } +}; + +template <typename T, + bool TriviallyDestructibleAndCopyable = + IsTriviallyDestructibleAndCopyable<T>, + bool Copyable = std::is_copy_constructible_v<T>, + bool Movable = std::is_move_constructible_v<T>> +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=<T>(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=<T>(std::move(downcast(aOther))); \ + \ + return *this; \ + } + +#define MOZ_MAYBE_DOWNCAST() \ + static constexpr Maybe<T>& downcast(Maybe_CopyMove_Enabler& aObj) { \ + return static_cast<Maybe<T>&>(aObj); \ + } \ + static constexpr const Maybe<T>& downcast( \ + const Maybe_CopyMove_Enabler& aObj) { \ + return static_cast<const Maybe<T>&>(aObj); \ + } + +template <typename T> +class Maybe_CopyMove_Enabler<T, true, true, true> { + 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 <typename T> +class Maybe_CopyMove_Enabler<T, true, false, true> { + 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 <typename T> +class Maybe_CopyMove_Enabler<T, false, true, true> { + public: + Maybe_CopyMove_Enabler() = default; + + MOZ_MAYBE_COPY_OPS() + MOZ_MAYBE_MOVE_OPS() + + private: + MOZ_MAYBE_DOWNCAST() +}; + +template <typename T> +class Maybe_CopyMove_Enabler<T, false, false, true> { + public: + Maybe_CopyMove_Enabler() = default; + + MOZ_MAYBE_MOVE_OPS() + + private: + MOZ_MAYBE_DOWNCAST() +}; + +template <typename T> +class Maybe_CopyMove_Enabler<T, false, true, false> { + public: + Maybe_CopyMove_Enabler() = default; + + MOZ_MAYBE_COPY_OPS() + + private: + MOZ_MAYBE_DOWNCAST() +}; + +template <typename T, bool TriviallyDestructibleAndCopyable> +class Maybe_CopyMove_Enabler<T, TriviallyDestructibleAndCopyable, false, + false> { + 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 <typename T, bool TriviallyDestructibleAndCopyable = + IsTriviallyDestructibleAndCopyable<T>> +struct MaybeStorage; + +template <typename T> +struct MaybeStorage<T, false> : MaybeStorageBase<T> { + protected: + char mIsSome = false; // not bool -- guarantees minimal space consumption + + MaybeStorage() = default; + explicit MaybeStorage(const T& aVal) + : MaybeStorageBase<T>{aVal}, mIsSome{true} {} + explicit MaybeStorage(T&& aVal) + : MaybeStorageBase<T>{std::move(aVal)}, mIsSome{true} {} + + template <typename... Args> + explicit MaybeStorage(std::in_place_t, Args&&... aArgs) + : MaybeStorageBase<T>{std::in_place, std::forward<Args>(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<T>{} {} + MaybeStorage& operator=(const MaybeStorage&) { return *this; } + MaybeStorage(MaybeStorage&&) : MaybeStorageBase<T>{} {} + MaybeStorage& operator=(MaybeStorage&&) { return *this; } + + ~MaybeStorage() { + if (mIsSome) { + this->addr()->T::~T(); + } + } +}; + +template <typename T> +struct MaybeStorage<T, true> : MaybeStorageBase<T> { + protected: + char mIsSome = false; // not bool -- guarantees minimal space consumption + + constexpr MaybeStorage() = default; + constexpr explicit MaybeStorage(const T& aVal) + : MaybeStorageBase<T>{aVal}, mIsSome{true} {} + constexpr explicit MaybeStorage(T&& aVal) + : MaybeStorageBase<T>{std::move(aVal)}, mIsSome{true} {} + + template <typename... Args> + constexpr explicit MaybeStorage(std::in_place_t, Args&&... aArgs) + : MaybeStorageBase<T>{std::in_place, std::forward<Args>(aArgs)...}, + mIsSome{true} {} +}; + +} // namespace detail + +template <typename T, typename U = typename std::remove_cv< + typename std::remove_reference<T>::type>::type> +constexpr Maybe<U> 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<Foo> 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<Foo> containing no value. + * doSomething(Some(Foo(100))); // Passes a Maybe<Foo> 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<T> 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 T> +class MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS Maybe + : private detail::MaybeStorage<T>, + public detail::Maybe_CopyMove_Enabler<T> { + template <typename, bool, bool, bool> + friend class detail::Maybe_CopyMove_Enabler; + + template <typename U, typename V> + friend constexpr Maybe<V> Some(U&& aValue); + + struct SomeGuard {}; + + template <typename U> + constexpr Maybe(U&& aValue, SomeGuard) + : detail::MaybeStorage<T>{std::forward<U>(aValue)} {} + + using detail::MaybeStorage<T>::mIsSome; + using detail::MaybeStorage<T>::mStorage; + + void poisonData() { detail::MaybePoisoner<T>::poison(&mStorage.val); } + + public: + using ValueType = T; + + MOZ_ALLOW_TEMPORARY constexpr Maybe() = default; + + MOZ_ALLOW_TEMPORARY MOZ_IMPLICIT constexpr Maybe(Nothing) : Maybe{} {} + + template <typename... Args> + constexpr explicit Maybe(std::in_place_t, Args&&... aArgs) + : detail::MaybeStorage<T>{std::in_place, std::forward<Args>(aArgs)...} {} + + /** + * Maybe<T> can be copy-constructed from a Maybe<U> if T is constructible from + * a const U&. + */ + template <typename U, + typename = std::enable_if_t<std::is_constructible_v<T, const U&>>> + MOZ_IMPLICIT Maybe(const Maybe<U>& aOther) { + if (aOther.isSome()) { + emplace(*aOther); + } + } + + /** + * Maybe<T> can be move-constructed from a Maybe<U> if T is constructible from + * a U&&. + */ + template <typename U, + typename = std::enable_if_t<std::is_constructible_v<T, U&&>>> + MOZ_IMPLICIT Maybe(Maybe<U>&& aOther) { + if (aOther.isSome()) { + emplace(std::move(*aOther)); + aOther.reset(); + } + } + + template <typename U, + typename = std::enable_if_t<std::is_constructible_v<T, const U&>>> + Maybe& operator=(const Maybe<U>& aOther) { + if (aOther.isSome()) { + if (mIsSome) { + ref() = aOther.ref(); + } else { + emplace(*aOther); + } + } else { + reset(); + } + return *this; + } + + template <typename U, + typename = std::enable_if_t<std::is_constructible_v<T, U&&>>> + Maybe& operator=(Maybe<U>&& 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<T> by value. Unsafe unless |isSome()|. + */ + constexpr T value() const&; + constexpr T value() &&; + constexpr T value() const&&; + + /** + * Move the contents of this Maybe<T> 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<T> + * and leaving |Nothing()| in its place. + */ + Maybe<T> take() { return std::exchange(*this, Nothing()); } + + /* + * Returns the contents of this Maybe<T> 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 <typename V> + constexpr T valueOr(V&& aDefault) const { + if (isSome()) { + return ref(); + } + return std::forward<V>(aDefault); + } + + /* + * Returns the contents of this Maybe<T> by value. If |isNothing()|, returns + * the value returned from the function or functor provided. + */ + template <typename F> + constexpr T valueOrFrom(F&& aFunc) const { + if (isSome()) { + return ref(); + } + return aFunc(); + } + + /* Returns the contents of this Maybe<T> by pointer. Unsafe unless |isSome()|. + */ + T* ptr(); + constexpr const T* ptr() const; + + /* + * Returns the contents of this Maybe<T> 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<T> by pointer. If |isNothing()|, + * returns the value returned from the function or functor provided. + */ + template <typename F> + T* ptrOrFrom(F&& aFunc) { + if (isSome()) { + return ptr(); + } + return aFunc(); + } + + template <typename F> + 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<T> 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<T> 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<T> by ref. If |isNothing()|, returns the + * value returned from the function or functor provided. + */ + template <typename F> + constexpr T& refOrFrom(F&& aFunc) { + if (isSome()) { + return ref(); + } + return aFunc(); + } + + template <typename F> + 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 <typename Func> + constexpr Maybe& apply(Func&& aFunc) { + if (isSome()) { + std::forward<Func>(aFunc)(ref()); + } + return *this; + } + + template <typename Func> + constexpr const Maybe& apply(Func&& aFunc) const { + if (isSome()) { + std::forward<Func>(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 <typename Func> + constexpr auto map(Func&& aFunc) { + if (isSome()) { + return Some(std::forward<Func>(aFunc)(ref())); + } + return Maybe<decltype(std::forward<Func>(aFunc)(ref()))>{}; + } + + template <typename Func> + constexpr auto map(Func&& aFunc) const { + if (isSome()) { + return Some(std::forward<Func>(aFunc)(ref())); + } + return Maybe<decltype(std::forward<Func>(aFunc)(ref()))>{}; + } + + /* If |isSome()|, empties this Maybe and destroys its contents. */ + constexpr void reset() { + if (isSome()) { + if constexpr (!std::is_trivially_destructible_v<T>) { + /* + * Static analyzer gets confused if we have Maybe<MutexAutoLock>, + * 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<T>'s storage. The + * arguments to |emplace()| are the parameters to T's constructor. + */ + template <typename... Args> + constexpr void emplace(Args&&... aArgs); + + template <typename U> + constexpr std::enable_if_t<std::is_same_v<T, U> && + std::is_copy_constructible_v<U> && + !std::is_move_constructible_v<U>> + emplace(U&& aArgs) { + emplace(aArgs); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const Maybe<T>& aMaybe) { + if (aMaybe) { + aStream << aMaybe.ref(); + } else { + aStream << "<Nothing>"; + } + return aStream; + } +}; + +template <typename T> +class Maybe<T&> { + 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 <typename Func> + Maybe& apply(Func&& aFunc) { + if (isSome()) { + std::forward<Func>(aFunc)(ref()); + } + return *this; + } + + template <typename Func> + const Maybe& apply(Func&& aFunc) const { + if (isSome()) { + std::forward<Func>(aFunc)(ref()); + } + return *this; + } + + template <typename Func> + auto map(Func&& aFunc) { + Maybe<decltype(std::forward<Func>(aFunc)(ref()))> val; + if (isSome()) { + val.emplace(std::forward<Func>(aFunc)(ref())); + } + return val; + } + + template <typename Func> + auto map(Func&& aFunc) const { + Maybe<decltype(std::forward<Func>(aFunc)(ref()))> val; + if (isSome()) { + val.emplace(std::forward<Func>(aFunc)(ref())); + } + return val; + } + + bool refEquals(const Maybe<T&>& aOther) const { + return mValue == aOther.mValue; + } + + bool refEquals(const T& aOther) const { return mValue == &aOther; } + + private: + T* mValue = nullptr; +}; + +template <typename T> +constexpr T Maybe<T>::value() const& { + MOZ_RELEASE_ASSERT(isSome()); + return ref(); +} + +template <typename T> +constexpr T Maybe<T>::value() && { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(ref()); +} + +template <typename T> +constexpr T Maybe<T>::value() const&& { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(ref()); +} + +template <typename T> +T* Maybe<T>::ptr() { + MOZ_RELEASE_ASSERT(isSome()); + return &ref(); +} + +template <typename T> +constexpr const T* Maybe<T>::ptr() const { + MOZ_RELEASE_ASSERT(isSome()); + return &ref(); +} + +template <typename T> +constexpr T* Maybe<T>::operator->() { + MOZ_RELEASE_ASSERT(isSome()); + return ptr(); +} + +template <typename T> +constexpr const T* Maybe<T>::operator->() const { + MOZ_RELEASE_ASSERT(isSome()); + return ptr(); +} + +template <typename T> +constexpr T& Maybe<T>::ref() & { + MOZ_RELEASE_ASSERT(isSome()); + return mStorage.val; +} + +template <typename T> +constexpr const T& Maybe<T>::ref() const& { + MOZ_RELEASE_ASSERT(isSome()); + return mStorage.val; +} + +template <typename T> +constexpr T&& Maybe<T>::ref() && { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(mStorage.val); +} + +template <typename T> +constexpr const T&& Maybe<T>::ref() const&& { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(mStorage.val); +} + +template <typename T> +constexpr T& Maybe<T>::operator*() & { + MOZ_RELEASE_ASSERT(isSome()); + return ref(); +} + +template <typename T> +constexpr const T& Maybe<T>::operator*() const& { + MOZ_RELEASE_ASSERT(isSome()); + return ref(); +} + +template <typename T> +constexpr T&& Maybe<T>::operator*() && { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(ref()); +} + +template <typename T> +constexpr const T&& Maybe<T>::operator*() const&& { + MOZ_RELEASE_ASSERT(isSome()); + return std::move(ref()); +} + +template <typename T> +template <typename... Args> +constexpr void Maybe<T>::emplace(Args&&... aArgs) { + MOZ_RELEASE_ASSERT(!isSome()); + ::new (KnownNotNull, &mStorage.val) T(std::forward<Args>(aArgs)...); + mIsSome = true; +} + +/* + * Some() creates a Maybe<T> 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 <typename T, typename U> +constexpr Maybe<U> Some(T&& aValue) { + return {std::forward<T>(aValue), typename Maybe<U>::SomeGuard{}}; +} + +template <typename T> +constexpr Maybe<T&> SomeRef(T& aValue) { + Maybe<T&> value; + value.emplace(aValue); + return value; +} + +template <typename T> +constexpr Maybe<T&> ToMaybeRef(T* const aPtr) { + return aPtr ? SomeRef(*aPtr) : Nothing{}; +} + +template <typename T> +Maybe<std::remove_cv_t<std::remove_reference_t<T>>> ToMaybe(T* aPtr) { + if (aPtr) { + return Some(*aPtr); + } + return Nothing(); +} + +/* + * Two Maybe<T> values are equal if + * - both are Nothing, or + * - both are Some, and the values they contain are equal. + */ +template <typename T> +constexpr bool operator==(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { + static_assert(!std::is_reference_v<T>, + "operator== is not defined for Maybe<T&>, compare values or " + "addresses explicitly instead"); + if (aLHS.isNothing() != aRHS.isNothing()) { + return false; + } + return aLHS.isNothing() || *aLHS == *aRHS; +} + +template <typename T> +constexpr bool operator!=(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { + return !(aLHS == aRHS); +} + +/* + * We support comparison to Nothing to allow reasonable expressions like: + * if (maybeValue == Nothing()) { ... } + */ +template <typename T> +constexpr bool operator==(const Maybe<T>& aLHS, const Nothing& aRHS) { + return aLHS.isNothing(); +} + +template <typename T> +constexpr bool operator!=(const Maybe<T>& aLHS, const Nothing& aRHS) { + return !(aLHS == aRHS); +} + +template <typename T> +constexpr bool operator==(const Nothing& aLHS, const Maybe<T>& aRHS) { + return aRHS.isNothing(); +} + +template <typename T> +constexpr bool operator!=(const Nothing& aLHS, const Maybe<T>& aRHS) { + return !(aLHS == aRHS); +} + +/* + * Maybe<T> values are ordered in the same way T values are ordered, except that + * Nothing comes before anything else. + */ +template <typename T> +constexpr bool operator<(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { + if (aLHS.isNothing()) { + return aRHS.isSome(); + } + if (aRHS.isNothing()) { + return false; + } + return *aLHS < *aRHS; +} + +template <typename T> +constexpr bool operator>(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { + return !(aLHS < aRHS || aLHS == aRHS); +} + +template <typename T> +constexpr bool operator<=(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { + return aLHS < aRHS || aLHS == aRHS; +} + +template <typename T> +constexpr bool operator>=(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { + return !(aLHS < aRHS); +} + +template <typename T> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, mozilla::Maybe<T>& aField, + const char* aName, uint32_t aFlags = 0) { + if (aField) { + ImplCycleCollectionTraverse(aCallback, aField.ref(), aName, aFlags); + } +} + +template <typename T> +inline void ImplCycleCollectionUnlink(mozilla::Maybe<T>& aField) { + if (aField) { + ImplCycleCollectionUnlink(aField.ref()); + } +} + +} // namespace mozilla + +#endif /* mozilla_Maybe_h */ |