MaybeOneOf.h (4868B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* 8 * A class storing one of two optional value types that supports in-place lazy 9 * construction. 10 */ 11 12 #ifndef mozilla_MaybeOneOf_h 13 #define mozilla_MaybeOneOf_h 14 15 #include <stddef.h> // for size_t 16 17 #include <new> // for placement new 18 #include <utility> 19 #include <algorithm> 20 21 #include "mozilla/Assertions.h" 22 #include "mozilla/OperatorNewExtensions.h" 23 24 namespace mozilla { 25 26 /* 27 * MaybeOneOf<T1, T2> is like Maybe, but it supports constructing either T1 28 * or T2. When a MaybeOneOf<T1, T2> is constructed, it is |empty()|, i.e., 29 * no value has been constructed and no destructor will be called when the 30 * MaybeOneOf<T1, T2> is destroyed. Upon calling |construct<T1>()| or 31 * |construct<T2>()|, a T1 or T2 object will be constructed with the given 32 * arguments and that object will be destroyed when the owning MaybeOneOf is 33 * destroyed. 34 * 35 * Because MaybeOneOf must be aligned suitable to hold any value stored within 36 * it, and because |alignas| requirements don't affect platform ABI with respect 37 * to how parameters are laid out in memory, MaybeOneOf can't be used as the 38 * type of a function parameter. Pass MaybeOneOf to functions by pointer or 39 * reference instead. 40 */ 41 template <class T1, class T2> 42 class MOZ_NON_PARAM MaybeOneOf { 43 static constexpr size_t StorageAlignment = std::max(alignof(T1), alignof(T2)); 44 static constexpr size_t StorageSize = std::max(sizeof(T1), sizeof(T2)); 45 46 alignas(StorageAlignment) unsigned char storage[StorageSize]; 47 48 // GCC fails due to -Werror=strict-aliasing if |storage| is directly cast to 49 // T*. Indirecting through these functions addresses the problem. 50 void* data() { return storage; } 51 const void* data() const { return storage; } 52 53 enum State { None, SomeT1, SomeT2 } state; 54 template <class T, class Ignored = void> 55 struct Type2State {}; 56 57 template <class T> 58 T& as() { 59 MOZ_ASSERT(state == Type2State<T>::result); 60 return *static_cast<T*>(data()); 61 } 62 63 template <class T> 64 const T& as() const { 65 MOZ_ASSERT(state == Type2State<T>::result); 66 return *static_cast<const T*>(data()); 67 } 68 69 public: 70 MaybeOneOf() : state(None) {} 71 ~MaybeOneOf() { destroyIfConstructed(); } 72 73 MaybeOneOf(MaybeOneOf&& rhs) : state(None) { 74 if (!rhs.empty()) { 75 if (rhs.constructed<T1>()) { 76 construct<T1>(std::move(rhs.as<T1>())); 77 rhs.as<T1>().~T1(); 78 } else { 79 construct<T2>(std::move(rhs.as<T2>())); 80 rhs.as<T2>().~T2(); 81 } 82 rhs.state = None; 83 } 84 } 85 86 MaybeOneOf& operator=(MaybeOneOf&& rhs) { 87 MOZ_ASSERT(this != &rhs, "Self-move is prohibited"); 88 this->~MaybeOneOf(); 89 new (this) MaybeOneOf(std::move(rhs)); 90 return *this; 91 } 92 93 bool empty() const { return state == None; } 94 95 template <class T> 96 bool constructed() const { 97 return state == Type2State<T>::result; 98 } 99 100 template <class T, class... Args> 101 void construct(Args&&... aArgs) { 102 MOZ_ASSERT(state == None); 103 state = Type2State<T>::result; 104 ::new (KnownNotNull, data()) T(std::forward<Args>(aArgs)...); 105 } 106 107 template <class T> 108 T& ref() { 109 return as<T>(); 110 } 111 112 template <class T> 113 const T& ref() const { 114 return as<T>(); 115 } 116 117 void destroy() { 118 MOZ_ASSERT(state == SomeT1 || state == SomeT2); 119 if (state == SomeT1) { 120 as<T1>().~T1(); 121 } else if (state == SomeT2) { 122 as<T2>().~T2(); 123 } 124 state = None; 125 } 126 127 void destroyIfConstructed() { 128 if (!empty()) { 129 destroy(); 130 } 131 } 132 133 template <typename Func> 134 constexpr auto mapNonEmpty(Func&& aFunc) const { 135 MOZ_ASSERT(!empty()); 136 if (state == SomeT1) { 137 return std::forward<Func>(aFunc)(as<T1>()); 138 } 139 return std::forward<Func>(aFunc)(as<T2>()); 140 } 141 template <typename Func> 142 constexpr auto mapNonEmpty(Func&& aFunc) { 143 MOZ_ASSERT(!empty()); 144 if (state == SomeT1) { 145 return std::forward<Func>(aFunc)(as<T1>()); 146 } 147 return std::forward<Func>(aFunc)(as<T2>()); 148 } 149 150 private: 151 MaybeOneOf(const MaybeOneOf& aOther) = delete; 152 const MaybeOneOf& operator=(const MaybeOneOf& aOther) = delete; 153 }; 154 155 template <class T1, class T2> 156 template <class Ignored> 157 struct MaybeOneOf<T1, T2>::Type2State<T1, Ignored> { 158 typedef MaybeOneOf<T1, T2> Enclosing; 159 static const typename Enclosing::State result = Enclosing::SomeT1; 160 }; 161 162 template <class T1, class T2> 163 template <class Ignored> 164 struct MaybeOneOf<T1, T2>::Type2State<T2, Ignored> { 165 typedef MaybeOneOf<T1, T2> Enclosing; 166 static const typename Enclosing::State result = Enclosing::SomeT2; 167 }; 168 169 } // namespace mozilla 170 171 #endif /* mozilla_MaybeOneOf_h */