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()124 inline 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