/* -*- Mode: C++; tab-width: 8; 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 "mozilla/Alignment.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/Move.h" #include "mozilla/OperatorNewExtensions.h" #include "mozilla/TypeTraits.h" #include // for placement new #include #include namespace mozilla { struct Nothing { }; /* * 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 allows in-place construction without a separate call to * |emplace()| by using a dummy |in_place_t| value to tag the appropriate * constructor. * - 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()|. * * N.B. GCC has missed optimizations with Maybe in the past and may generate * extra branches/loads/stores. Use with caution on hot paths; it's not known * whether or not this is still a problem. */ template class MOZ_NON_PARAM MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS Maybe { MOZ_ALIGNAS_IN_STRUCT(T) unsigned char mStorage[sizeof(T)]; char mIsSome; // not bool -- guarantees minimal space consumption // GCC fails due to -Werror=strict-aliasing if |mStorage| is directly cast to // T*. Indirecting through these functions addresses the problem. void* data() { return mStorage; } const void* data() const { return mStorage; } public: using ValueType = T; Maybe() : mIsSome(false) { } ~Maybe() { reset(); } MOZ_IMPLICIT Maybe(Nothing) : mIsSome(false) { } Maybe(const Maybe& aOther) : mIsSome(false) { if (aOther.mIsSome) { emplace(*aOther); } } /** * Maybe can be copy-constructed from a Maybe if U is convertible to T. */ template::value>::type> MOZ_IMPLICIT Maybe(const Maybe& aOther) : mIsSome(false) { if (aOther.isSome()) { emplace(*aOther); } } Maybe(Maybe&& aOther) : mIsSome(false) { if (aOther.mIsSome) { emplace(Move(*aOther)); aOther.reset(); } } /** * Maybe can be move-constructed from a Maybe if U is convertible to T. */ template::value>::type> MOZ_IMPLICIT Maybe(Maybe&& aOther) : mIsSome(false) { if (aOther.isSome()) { emplace(Move(*aOther)); aOther.reset(); } } Maybe& operator=(const Maybe& aOther) { if (&aOther != this) { if (aOther.mIsSome) { if (mIsSome) { ref() = aOther.ref(); } else { emplace(*aOther); } } else { reset(); } } return *this; } template::value>::type> Maybe& operator=(const Maybe& aOther) { if (aOther.isSome()) { if (mIsSome) { ref() = aOther.ref(); } else { emplace(*aOther); } } else { reset(); } return *this; } Maybe& operator=(Maybe&& aOther) { MOZ_ASSERT(this != &aOther, "Self-moves are prohibited"); if (aOther.mIsSome) { if (mIsSome) { ref() = Move(aOther.ref()); } else { emplace(Move(*aOther)); } aOther.reset(); } else { reset(); } return *this; } template::value>::type> Maybe& operator=(Maybe&& aOther) { if (aOther.isSome()) { if (mIsSome) { ref() = Move(aOther.ref()); } else { emplace(Move(*aOther)); } aOther.reset(); } else { reset(); } return *this; } /* Methods that check whether this Maybe contains a value */ explicit operator bool() const { return isSome(); } bool isSome() const { return mIsSome; } bool isNothing() const { return !mIsSome; } /* Returns the contents of this Maybe by value. Unsafe unless |isSome()|. */ T value() const { MOZ_ASSERT(mIsSome); return ref(); } /* * Returns the contents of this Maybe by value. If |isNothing()|, returns * the default value provided. */ template T valueOr(V&& aDefault) const { if (isSome()) { return ref(); } return Forward(aDefault); } /* * Returns the contents of this Maybe by value. If |isNothing()|, returns * the value returned from the function or functor provided. */ template T valueOrFrom(F&& aFunc) const { if (isSome()) { return ref(); } return aFunc(); } /* Returns the contents of this Maybe by pointer. Unsafe unless |isSome()|. */ T* ptr() { MOZ_ASSERT(mIsSome); return &ref(); } const T* ptr() const { MOZ_ASSERT(mIsSome); return &ref(); } /* * 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; } 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(); } T* operator->() { MOZ_ASSERT(mIsSome); return ptr(); } const T* operator->() const { MOZ_ASSERT(mIsSome); return ptr(); } /* Returns the contents of this Maybe by ref. Unsafe unless |isSome()|. */ T& ref() { MOZ_ASSERT(mIsSome); return *static_cast(data()); } const T& ref() const { MOZ_ASSERT(mIsSome); return *static_cast(data()); } /* * Returns the contents of this Maybe by ref. If |isNothing()|, returns * the default value provided. */ T& refOr(T& aDefault) { if (isSome()) { return ref(); } return aDefault; } 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 T& refOrFrom(F&& aFunc) { if (isSome()) { return ref(); } return aFunc(); } template const T& refOrFrom(F&& aFunc) const { if (isSome()) { return ref(); } return aFunc(); } T& operator*() { MOZ_ASSERT(mIsSome); return ref(); } const T& operator*() const { MOZ_ASSERT(mIsSome); return ref(); } /* If |isSome()|, runs the provided function or functor on the contents of * this Maybe. */ template Maybe& apply(Func aFunc) { if (isSome()) { aFunc(ref()); } return *this; } template const Maybe& apply(Func aFunc) const { if (isSome()) { 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. */ template auto map(Func aFunc) -> Maybe>().ref()))> { using ReturnType = decltype(aFunc(ref())); if (isSome()) { Maybe val; val.emplace(aFunc(ref())); return val; } return Maybe(); } template auto map(Func aFunc) const -> Maybe>().ref()))> { using ReturnType = decltype(aFunc(ref())); if (isSome()) { Maybe val; val.emplace(aFunc(ref())); return val; } return Maybe(); } /* If |isSome()|, empties this Maybe and destroys its contents. */ void reset() { if (isSome()) { ref().T::~T(); 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 void emplace(Args&&... aArgs) { MOZ_ASSERT(!mIsSome); ::new (KnownNotNull, data()) T(Forward(aArgs)...); mIsSome = true; } friend std::ostream& operator<<(std::ostream& aStream, const Maybe& aMaybe) { if (aMaybe) { aStream << aMaybe.ref(); } else { aStream << ""; } return aStream; } }; /* * 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::type>::type> Maybe Some(T&& aValue) { Maybe value; value.emplace(Forward(aValue)); return value; } template Maybe::Type>::Type> 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 bool operator==(const Maybe& aLHS, const Maybe& aRHS) { if (aLHS.isNothing() != aRHS.isNothing()) { return false; } return aLHS.isNothing() || *aLHS == *aRHS; } template 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 bool operator==(const Maybe& aLHS, const Nothing& aRHS) { return aLHS.isNothing(); } template bool operator!=(const Maybe& aLHS, const Nothing& aRHS) { return !(aLHS == aRHS); } template bool operator==(const Nothing& aLHS, const Maybe& aRHS) { return aRHS.isNothing(); } template 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 bool operator<(const Maybe& aLHS, const Maybe& aRHS) { if (aLHS.isNothing()) { return aRHS.isSome(); } if (aRHS.isNothing()) { return false; } return *aLHS < *aRHS; } template bool operator>(const Maybe& aLHS, const Maybe& aRHS) { return !(aLHS < aRHS || aLHS == aRHS); } template bool operator<=(const Maybe& aLHS, const Maybe& aRHS) { return aLHS < aRHS || aLHS == aRHS; } template bool operator>=(const Maybe& aLHS, const Maybe& aRHS) { return !(aLHS < aRHS); } } // namespace mozilla #endif /* mozilla_Maybe_h */