1 #ifndef FISH_MAYBE_H 2 #define FISH_MAYBE_H 3 4 #include <cassert> 5 #include <type_traits> 6 #include <utility> 7 8 namespace maybe_detail { 9 // Template magic to make maybe_t<T> copyable iff T is copyable. 10 // maybe_impl_t is the "too aggressive" implementation: it is always copyable. 11 template <typename T> 12 struct maybe_impl_t { 13 alignas(T) char storage[sizeof(T)]; 14 bool filled = false; 15 valuemaybe_impl_t16 T &value() { 17 assert(filled && "maybe_t does not have a value"); 18 return *reinterpret_cast<T *>(storage); 19 } 20 valuemaybe_impl_t21 const T &value() const { 22 assert(filled && "maybe_t does not have a value"); 23 return *reinterpret_cast<const T *>(storage); 24 } 25 resetmaybe_impl_t26 void reset() { 27 if (this->filled) { 28 value().~T(); 29 this->filled = false; 30 } 31 } 32 acquiremaybe_impl_t33 T acquire() { 34 assert(filled && "maybe_t does not have a value"); 35 T res = std::move(value()); 36 reset(); 37 return res; 38 } 39 40 maybe_impl_t() = default; 41 42 // Move construction/assignment from a T. maybe_impl_tmaybe_impl_t43 explicit maybe_impl_t(T &&v) : filled(true) { new (storage) T(std::forward<T>(v)); } 44 maybe_impl_t &operator=(T &&v) { 45 if (filled) { 46 value() = std::move(v); 47 } else { 48 new (storage) T(std::move(v)); 49 filled = true; 50 } 51 return *this; 52 } 53 54 // Copy construction/assignment from a T. maybe_impl_tmaybe_impl_t55 explicit maybe_impl_t(const T &v) : filled(true) { new (storage) T(v); } 56 maybe_impl_t &operator=(const T &v) { 57 if (filled) { 58 value() = v; 59 } else { 60 new (storage) T(v); 61 filled = true; 62 } 63 return *this; 64 } 65 66 // Move construction/assignment from a maybe_impl. maybe_impl_tmaybe_impl_t67 maybe_impl_t(maybe_impl_t &&v) : filled(v.filled) { 68 if (filled) { 69 new (storage) T(std::move(v.value())); 70 } 71 } 72 maybe_impl_t &operator=(maybe_impl_t &&v) { 73 if (!v.filled) { 74 reset(); 75 } else { 76 *this = std::move(v.value()); 77 } 78 return *this; 79 } 80 81 // Copy construction/assignment from a maybe_impl. maybe_impl_tmaybe_impl_t82 maybe_impl_t(const maybe_impl_t &v) : filled(v.filled) { 83 if (v.filled) { 84 new (storage) T(v.value()); 85 } 86 } 87 maybe_impl_t &operator=(const maybe_impl_t &v) { 88 if (&v == this) return *this; 89 if (!v.filled) { 90 reset(); 91 } else { 92 *this = v.value(); 93 } 94 return *this; 95 } 96 ~maybe_impl_tmaybe_impl_t97 ~maybe_impl_t() { reset(); } 98 }; 99 100 struct copyable_t {}; 101 struct noncopyable_t { 102 noncopyable_t() = default; 103 noncopyable_t(noncopyable_t &&) = default; 104 noncopyable_t &operator=(noncopyable_t &&) = default; 105 noncopyable_t(const noncopyable_t &) = delete; 106 noncopyable_t &operator=(const noncopyable_t &) = delete; 107 }; 108 109 // conditionally_copyable_t is copyable iff T is copyable. 110 // This enables conditionally copyable wrapper types by inheriting from it. 111 template <typename T> 112 using conditionally_copyable_t = typename std::conditional<std::is_copy_constructible<T>::value, 113 copyable_t, noncopyable_t>::type; 114 115 }; // namespace maybe_detail 116 117 // A none_t is a helper type used to implicitly initialize maybe_t. 118 // Example usage: 119 // maybe_t<int> sqrt(int x) { 120 // if (x < 0) return none(); 121 // return (int)sqrt(x); 122 // } 123 enum class none_t { none = 1 }; none()124inline constexpr none_t none() { return none_t::none; } 125 126 // Support for a maybe, also known as Optional. 127 // This is a value-type class that stores a value of T in aligned storage. 128 template <typename T> 129 class maybe_t : private maybe_detail::conditionally_copyable_t<T> { 130 maybe_detail::maybe_impl_t<T> impl_; 131 132 public: 133 // return whether the receiver contains a value. has_value()134 bool has_value() const { return impl_.filled; } 135 136 // bool conversion indicates whether the receiver contains a value. 137 explicit operator bool() const { return impl_.filled; } 138 139 // The default constructor constructs a maybe with no value. 140 maybe_t() = default; 141 142 // Construct a maybe_t from a none_t maybe_t(none_t)143 /* implicit */ maybe_t(none_t) {} 144 145 // Construct a maybe_t from a value T. maybe_t(T && v)146 /* implicit */ maybe_t(T &&v) : impl_(std::move(v)) {} 147 148 // Construct a maybe_t from a value T. maybe_t(const T & v)149 /* implicit */ maybe_t(const T &v) : impl_(v) {} 150 151 // Copy and move constructors. 152 maybe_t(const maybe_t &) = default; 153 maybe_t(maybe_t &&) = default; 154 155 // Construct a value in-place. 156 template <class... Args> emplace(Args &&...args)157 void emplace(Args &&...args) { 158 reset(); 159 impl_.filled = true; 160 new (impl_.storage) T(std::forward<Args>(args)...); 161 } 162 163 // Access the value. value()164 T &value() { return impl_.value(); } 165 value()166 const T &value() const { return impl_.value(); } 167 168 // Transfer the value to the caller. acquire()169 T acquire() { return impl_.acquire(); } 170 171 // Clear the value. reset()172 void reset() { impl_.reset(); } 173 174 // Assign a new value. 175 maybe_t &operator=(T &&v) { 176 impl_ = std::move(v); 177 return *this; 178 } 179 180 // Note that defaulting these allows these to be conditionally deleted via 181 // conditionally_copyable_t(). 182 maybe_t &operator=(const maybe_t &) = default; 183 maybe_t &operator=(maybe_t &&) = default; 184 185 // Dereference support. 186 const T *operator->() const { return &value(); } 187 T *operator->() { return &value(); } 188 const T &operator*() const { return value(); } 189 T &operator*() { return value(); } 190 191 // Helper to replace missing_or_empty() on env_var_t. 192 // Uses SFINAE to only introduce this function if T has an empty() type. 193 template <typename S = T> S()194 decltype(S().empty(), bool()) missing_or_empty() const { 195 return !has_value() || value().empty(); 196 } 197 198 // Compare values for equality. 199 bool operator==(const maybe_t &rhs) const { 200 if (this->has_value() && rhs.has_value()) return this->value() == rhs.value(); 201 return this->has_value() == rhs.has_value(); 202 } 203 204 bool operator!=(const maybe_t &rhs) const { return !(*this == rhs); } 205 206 bool operator==(const T &rhs) const { return this->has_value() && this->value() == rhs; } 207 208 bool operator!=(const T &rhs) const { return !(*this == rhs); } 209 ~maybe_t()210 ~maybe_t() { reset(); } 211 }; 212 213 #endif 214