123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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 */
|