/* -*- 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 storing one of two optional value types that supports in-place lazy * construction. */ #ifndef mozilla_MaybeOneOf_h #define mozilla_MaybeOneOf_h #include // for size_t #include // for placement new #include #include "mozilla/Assertions.h" #include "mozilla/OperatorNewExtensions.h" #include "mozilla/TemplateLib.h" namespace mozilla { /* * MaybeOneOf is like Maybe, but it supports constructing either T1 * or T2. When a MaybeOneOf is constructed, it is |empty()|, i.e., * no value has been constructed and no destructor will be called when the * MaybeOneOf is destroyed. Upon calling |construct()| or * |construct()|, a T1 or T2 object will be constructed with the given * arguments and that object will be destroyed when the owning MaybeOneOf is * destroyed. * * Because MaybeOneOf must be aligned suitable to hold any value stored within * it, and because |alignas| requirements don't affect platform ABI with respect * to how parameters are laid out in memory, MaybeOneOf can't be used as the * type of a function parameter. Pass MaybeOneOf to functions by pointer or * reference instead. */ template class MOZ_NON_PARAM MaybeOneOf { static constexpr size_t StorageAlignment = tl::Max::value; static constexpr size_t StorageSize = tl::Max::value; alignas(StorageAlignment) unsigned char storage[StorageSize]; // GCC fails due to -Werror=strict-aliasing if |storage| is directly cast to // T*. Indirecting through these functions addresses the problem. void* data() { return storage; } const void* data() const { return storage; } enum State { None, SomeT1, SomeT2 } state; template struct Type2State {}; template T& as() { MOZ_ASSERT(state == Type2State::result); return *static_cast(data()); } template const T& as() const { MOZ_ASSERT(state == Type2State::result); return *static_cast(data()); } public: MaybeOneOf() : state(None) {} ~MaybeOneOf() { destroyIfConstructed(); } MaybeOneOf(MaybeOneOf&& rhs) : state(None) { if (!rhs.empty()) { if (rhs.constructed()) { construct(std::move(rhs.as())); rhs.as().~T1(); } else { construct(std::move(rhs.as())); rhs.as().~T2(); } rhs.state = None; } } MaybeOneOf& operator=(MaybeOneOf&& rhs) { MOZ_ASSERT(this != &rhs, "Self-move is prohibited"); this->~MaybeOneOf(); new (this) MaybeOneOf(std::move(rhs)); return *this; } bool empty() const { return state == None; } template bool constructed() const { return state == Type2State::result; } template void construct(Args&&... aArgs) { MOZ_ASSERT(state == None); state = Type2State::result; ::new (KnownNotNull, data()) T(std::forward(aArgs)...); } template T& ref() { return as(); } template const T& ref() const { return as(); } void destroy() { MOZ_ASSERT(state == SomeT1 || state == SomeT2); if (state == SomeT1) { as().~T1(); } else if (state == SomeT2) { as().~T2(); } state = None; } void destroyIfConstructed() { if (!empty()) { destroy(); } } template constexpr auto mapNonEmpty(Func&& aFunc) const { MOZ_ASSERT(!empty()); if (state == SomeT1) { return std::forward(aFunc)(as()); } return std::forward(aFunc)(as()); } template constexpr auto mapNonEmpty(Func&& aFunc) { MOZ_ASSERT(!empty()); if (state == SomeT1) { return std::forward(aFunc)(as()); } return std::forward(aFunc)(as()); } private: MaybeOneOf(const MaybeOneOf& aOther) = delete; const MaybeOneOf& operator=(const MaybeOneOf& aOther) = delete; }; template template struct MaybeOneOf::Type2State { typedef MaybeOneOf Enclosing; static const typename Enclosing::State result = Enclosing::SomeT1; }; template template struct MaybeOneOf::Type2State { typedef MaybeOneOf Enclosing; static const typename Enclosing::State result = Enclosing::SomeT2; }; } // namespace mozilla #endif /* mozilla_MaybeOneOf_h */