From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- mfbt/Maybe.h | 551 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 551 insertions(+) create mode 100644 mfbt/Maybe.h (limited to 'mfbt/Maybe.h') 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 // for placement new +#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 Maybe +{ + bool mIsSome; + AlignedStorage2 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 can be copy-constructed from a Maybe if U* and T* are + * compatible, or from Maybe. + */ + template::value && + (std::is_same::value || + (std::is_pointer::value && + std::is_base_of::type, + typename std::remove_pointer::type>::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* and T* are + * compatible, or from Maybe. + */ + template::value && + (std::is_same::value || + (std::is_pointer::value && + std::is_base_of::type, + typename std::remove_pointer::type>::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) { + // 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 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 *mStorage.addr(); + } + + const T& ref() const + { + MOZ_ASSERT(mIsSome); + return *mStorage.addr(); + } + + /* + * 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 (mStorage.addr()) T(Forward(aArgs)...); + mIsSome = true; + } +}; + +/* + * 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 +Maybe::Type>::Type> +Some(T&& aValue) +{ + typedef typename RemoveCV::Type>::Type U; + 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 */ -- cgit v1.2.3