/* -*- 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/TypeTraits.h" #include <new> // for placement new #include <type_traits> 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<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 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 T> class Maybe { bool mIsSome; AlignedStorage2<T> mStorage; public: typedef T ValueType; Maybe() : mIsSome(false) { } ~Maybe() { reset(); } MOZ_IMPLICIT Maybe(Nothing) : mIsSome(false) { } Maybe(const Maybe& aOther) : mIsSome(false) { if (aOther.mIsSome) { emplace(*aOther); } } /** * Maybe<T*> can be copy-constructed from a Maybe<U*> if U* and T* are * compatible, or from Maybe<decltype(nullptr)>. */ template<typename U, typename = typename std::enable_if<std::is_pointer<T>::value && (std::is_same<U, decltype(nullptr)>::value || (std::is_pointer<U>::value && std::is_base_of<typename std::remove_pointer<T>::type, typename std::remove_pointer<U>::type>::value))>::type> MOZ_IMPLICIT Maybe(const Maybe<U>& aOther) : mIsSome(false) { if (aOther.isSome()) { emplace(*aOther); } } Maybe(Maybe&& aOther) : mIsSome(false) { if (aOther.mIsSome) { emplace(Move(*aOther)); aOther.reset(); } } /** * Maybe<T*> can be move-constructed from a Maybe<U*> if U* and T* are * compatible, or from Maybe<decltype(nullptr)>. */ template<typename U, typename = typename std::enable_if<std::is_pointer<T>::value && (std::is_same<U, decltype(nullptr)>::value || (std::is_pointer<U>::value && std::is_base_of<typename std::remove_pointer<T>::type, typename std::remove_pointer<U>::type>::value))>::type> MOZ_IMPLICIT Maybe(Maybe<U>&& aOther) : mIsSome(false) { if (aOther.isSome()) { emplace(Move(*aOther)); aOther.reset(); } } Maybe& operator=(const Maybe& aOther) { if (&aOther != this) { if (aOther.mIsSome) { if (mIsSome) { // XXX(seth): The correct code for this branch, below, can't be used // due to a bug in Visual Studio 2010. See bug 1052940. /* ref() = aOther.ref(); */ reset(); emplace(*aOther); } 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; } /* 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<T> by value. Unsafe unless |isSome()|. */ T value() const { MOZ_ASSERT(mIsSome); return ref(); } /* * Returns the contents of this Maybe<T> by value. If |isNothing()|, returns * the default value provided. */ template<typename V> T valueOr(V&& aDefault) const { if (isSome()) { return ref(); } return 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> 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() { MOZ_ASSERT(mIsSome); return &ref(); } const T* ptr() const { MOZ_ASSERT(mIsSome); return &ref(); } /* * 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; } 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(); } T* operator->() { MOZ_ASSERT(mIsSome); return ptr(); } const T* operator->() const { MOZ_ASSERT(mIsSome); return ptr(); } /* Returns the contents of this Maybe<T> by ref. Unsafe unless |isSome()|. */ T& ref() { MOZ_ASSERT(mIsSome); return *mStorage.addr(); } const T& ref() const { MOZ_ASSERT(mIsSome); return *mStorage.addr(); } /* * Returns the contents of this Maybe<T> 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<T> by ref. If |isNothing()|, returns the * value returned from the function or functor provided. */ template<typename F> T& refOrFrom(F&& aFunc) { if (isSome()) { return ref(); } return aFunc(); } template<typename F> 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<typename Func> Maybe& apply(Func aFunc) { if (isSome()) { aFunc(ref()); } return *this; } template<typename Func> 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<typename Func> auto map(Func aFunc) -> Maybe<decltype(aFunc(DeclVal<Maybe<T>>().ref()))> { using ReturnType = decltype(aFunc(ref())); if (isSome()) { Maybe<ReturnType> val; val.emplace(aFunc(ref())); return val; } return Maybe<ReturnType>(); } template<typename Func> auto map(Func aFunc) const -> Maybe<decltype(aFunc(DeclVal<Maybe<T>>().ref()))> { using ReturnType = decltype(aFunc(ref())); if (isSome()) { Maybe<ReturnType> val; val.emplace(aFunc(ref())); return val; } return Maybe<ReturnType>(); } /* 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<T>'s storage. The * arguments to |emplace()| are the parameters to T's constructor. */ template<typename... Args> void emplace(Args&&... aArgs) { MOZ_ASSERT(!mIsSome); ::new (mStorage.addr()) T(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> Maybe<typename RemoveCV<typename RemoveReference<T>::Type>::Type> Some(T&& aValue) { typedef typename RemoveCV<typename RemoveReference<T>::Type>::Type U; Maybe<U> value; value.emplace(Forward<T>(aValue)); return value; } template<typename T> Maybe<typename RemoveCV<typename RemoveReference<T>::Type>::Type> 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> bool operator==(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { if (aLHS.isNothing() != aRHS.isNothing()) { return false; } return aLHS.isNothing() || *aLHS == *aRHS; } template<typename T> 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> bool operator==(const Maybe<T>& aLHS, const Nothing& aRHS) { return aLHS.isNothing(); } template<typename T> bool operator!=(const Maybe<T>& aLHS, const Nothing& aRHS) { return !(aLHS == aRHS); } template<typename T> bool operator==(const Nothing& aLHS, const Maybe<T>& aRHS) { return aRHS.isNothing(); } template<typename T> 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> 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> bool operator>(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { return !(aLHS < aRHS || aLHS == aRHS); } template<typename T> bool operator<=(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { return aLHS < aRHS || aLHS == aRHS; } template<typename T> bool operator>=(const Maybe<T>& aLHS, const Maybe<T>& aRHS) { return !(aLHS < aRHS); } } // namespace mozilla #endif /* mozilla_Maybe_h */