summaryrefslogtreecommitdiff
path: root/mfbt/Maybe.h
diff options
context:
space:
mode:
Diffstat (limited to 'mfbt/Maybe.h')
-rw-r--r--mfbt/Maybe.h551
1 files changed, 551 insertions, 0 deletions
diff --git a/mfbt/Maybe.h b/mfbt/Maybe.h
new file mode 100644
index 0000000000..2a601ac494
--- /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 */