diff options
Diffstat (limited to 'mfbt/Maybe.h')
-rw-r--r-- | mfbt/Maybe.h | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/mfbt/Maybe.h b/mfbt/Maybe.h new file mode 100644 index 000000000..2a601ac49 --- /dev/null +++ b/mfbt/Maybe.h @@ -0,0 +1,551 @@ +/* -*- 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 */ |