Use iterator adapters in decl_class_parents
[hiphop-php.git] / hphp / util / optional.h
blob90f750c288e27a5678aa410fd091f61cc8f26a17
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #pragma once
19 #include <initializer_list>
20 #include <optional>
21 #include <type_traits>
23 #include "hphp/util/assertions.h"
26 * Drop in replacement for std::optional
28 * This is meant to be used instead of std::optional or
29 * folly::Optional. It replicates the interface of std::optional.
31 * Both std::optional and folly::Optional have undesirable behavior
32 * for accessing the value when the option is disengaged.
33 * folly::Optional throws an exception, while it is merely undefined
34 * for std::optional. Throwing an exception is particularly
35 * problematic, as it makes it hard to debug (by the time you catch
36 * it, the stack is unwind), and causes issues with noexcept
37 * functions.
39 * Instead we choose to always_assert if you attempt to access the
40 * value when disengaged. This is no more expensive than
41 * folly::Optional, but makes it much easier to debug.
43 * HPHP::Optional just wraps a std::optional, but adds the proper
44 * asserts around the right functions.
47 namespace HPHP {
48 ///////////////////////////////////////////////////////////////////////////////
50 template <typename T>
51 struct Optional {
52 // These all intentionally match the interface for std::optional:
54 constexpr const T& operator*() const& {
55 always_assert(m_opt.has_value());
56 return *m_opt;
58 constexpr T& operator*() & {
59 always_assert(m_opt.has_value());
60 return *m_opt;
62 constexpr const T&& operator*() const&& {
63 always_assert(m_opt.has_value());
64 return *std::move(m_opt);
66 constexpr T&& operator*() && {
67 always_assert(m_opt.has_value());
68 return *std::move(m_opt);
71 constexpr const T* operator->() const {
72 always_assert(m_opt.has_value());
73 return m_opt.operator->();
75 constexpr T* operator->() {
76 always_assert(m_opt.has_value());
77 return m_opt.operator->();
80 constexpr explicit operator bool() const noexcept { return m_opt.has_value(); }
81 constexpr bool has_value() const noexcept { return m_opt.has_value(); }
83 // value() throws an exception even in std::optional. We don't want
84 // exceptions, so treat it as a synonym for operator*.
85 constexpr T& value() & {
86 always_assert(m_opt.has_value());
87 return *m_opt;
89 constexpr const T& value() const & {
90 always_assert(m_opt.has_value());
91 return *m_opt;
93 constexpr T&& value() && {
94 always_assert(m_opt.has_value());
95 return *std::move(m_opt);
97 constexpr const T&& value() const && {
98 always_assert(m_opt.has_value());
99 return *std::move(m_opt);
102 template <typename U>
103 constexpr T value_or(U&& default_value) const& {
104 return m_opt.value_or(std::move(default_value));
106 template <typename U>
107 constexpr T value_or(U&& default_value) && {
108 return std::move(m_opt).value_or(std::move(default_value));
111 // std::optional doesn't have this, but folly::Optional
112 // does. Support it for compatibility.
113 T* get_pointer() {
114 return m_opt.has_value() ? &*m_opt : nullptr;
116 const T* get_pointer() const {
117 return m_opt.has_value() ? &*m_opt : nullptr;
120 void swap(Optional& o)
121 noexcept(noexcept(std::declval<decltype(o.m_opt)>().swap(o.m_opt))) {
122 m_opt.swap(o.m_opt);
125 void reset() noexcept { m_opt.reset(); }
127 template<typename... Args> T& emplace(Args&&... args) {
128 return m_opt.emplace(std::forward<Args>(args)...);
130 template<typename U, typename... Args>
131 T& emplace(std::initializer_list<U> ilist, Args&&... args) {
132 return m_opt.emplace(ilist, std::forward<Args>(args)...);
135 private:
136 // SFINAE stuff to support the required ctor and assignment
137 // overloads.
138 template<typename... Cond>
139 using Requires = std::enable_if_t<std::conjunction_v<Cond...>, bool>;
141 template<typename U>
142 using remove_cvref_t =
143 typename std::remove_cv<typename std::remove_reference<U>::type>::type;
145 template<typename U>
146 using not_self = std::negation<std::is_same<Optional, remove_cvref_t<U>>>;
147 template<typename U>
148 using not_tag =
149 std::negation<std::is_same<std::in_place_t, remove_cvref_t<U>>>;
151 template<typename U, typename V>
152 using converts_from_optional =
153 std::disjunction<std::is_constructible<U, const Optional<V>&>,
154 std::is_constructible<U, Optional<V>&>,
155 std::is_constructible<U, const Optional<V>&&>,
156 std::is_constructible<U, Optional<V>&&>,
157 std::is_convertible<const Optional<V>&, U>,
158 std::is_convertible<Optional<V>&, U>,
159 std::is_convertible<const Optional<V>&&, U>,
160 std::is_convertible<Optional<V>&&, U>>;
162 template<typename U, typename V>
163 using assigns_from_optional =
164 std::disjunction<std::is_assignable<U&, const Optional<V>&>,
165 std::is_assignable<U&, Optional<V>&>,
166 std::is_assignable<U&, const Optional<V>&&>,
167 std::is_assignable<U&, Optional<V>&&>>;
169 public:
171 // These ctor and assignment operator overloads are a
172 // mess. Unfortunately we cannot just forward arguments to
173 // std::optional and have to replicate all the overloads with the
174 // same SFINAE logic.
176 // NB: folly::Optional resets when moved-from. std::optional remains
177 // engaged with the contained value in a moved-from state. We rely
178 // on folly::Optional's behavior, so we emulate it here.
180 constexpr Optional() noexcept = default;
181 constexpr Optional(std::nullopt_t) noexcept
182 : m_opt(std::nullopt) {}
184 constexpr Optional(const Optional& o)
185 : m_opt(o.m_opt) {}
187 constexpr Optional(Optional&& o)
188 noexcept(noexcept(decltype(o.m_opt)(std::move(o.m_opt))))
189 : m_opt(std::move(o.m_opt)) { o.m_opt.reset(); }
191 template <typename U = T,
192 Requires<not_self<U>,
193 not_tag<U>,
194 std::is_constructible<T, U>,
195 std::is_convertible<U&&, T>> = true>
196 constexpr Optional(U&& v)
197 : m_opt(std::in_place, std::forward<U>(v)) {}
199 template <typename U = T,
200 Requires<not_self<U>,
201 not_tag<U>,
202 std::is_constructible<T, U>,
203 std::negation<std::is_convertible<U&&, T>>> = true>
204 explicit constexpr Optional(U&& v)
205 : m_opt(std::in_place, std::forward<U>(v)) {}
207 template <typename U,
208 Requires<std::negation<std::is_same<T, U>>,
209 std::is_constructible<T, const U&>,
210 std::is_convertible<const U&, T>,
211 std::negation<converts_from_optional<T, U>>> = true>
212 constexpr Optional(const Optional<U>& o)
213 : m_opt(o.m_opt) {}
215 template <typename U,
216 Requires<std::negation<std::is_same<T, U>>,
217 std::is_constructible<T, const U&>,
218 std::negation<std::is_convertible<const U&, T>>,
219 std::negation<converts_from_optional<T, U>>> = true>
220 explicit constexpr Optional(const Optional<U>& o)
221 : m_opt(o.m_opt) {}
223 template <typename U,
224 Requires<std::negation<std::is_same<T, U>>,
225 std::is_constructible<T, U&&>,
226 std::is_convertible<U&&, T>,
227 std::negation<converts_from_optional<T, U>>> = true>
228 constexpr Optional(Optional<U>&& o)
229 : m_opt(std::move(o.m_opt)) { o.m_opt.reset(); }
231 template <typename U,
232 Requires<std::negation<std::is_same<T, U>>,
233 std::is_constructible<T, U&&>,
234 std::negation<std::is_convertible<U&&, T>>,
235 std::negation<converts_from_optional<T, U>>> = true>
236 explicit constexpr Optional(Optional<U>&& o)
237 : m_opt(std::move(o.m_opt)) { o.m_opt.reset(); }
239 template<typename... Args,
240 Requires<std::is_constructible<T, Args&&...>> = true>
241 explicit constexpr Optional(std::in_place_t, Args&&... args)
242 : m_opt(std::in_place, std::forward<Args>(args)...) {}
244 template<typename U, typename... Args,
245 Requires<std::is_constructible<T,
246 std::initializer_list<U>&,
247 Args&&...>> = true>
248 explicit constexpr Optional(std::in_place_t,
249 std::initializer_list<U> il,
250 Args&&... args)
251 : m_opt(std::in_place, il, std::forward<Args>(args)...) {}
253 Optional& operator=(const Optional& o) {
254 m_opt = o.m_opt;
255 return *this;
258 Optional& operator=(Optional&& o)
259 noexcept(
260 noexcept(std::declval<decltype(o.m_opt)>().operator=(std::move(o.m_opt)))
262 m_opt = std::move(o.m_opt);
263 o.m_opt.reset();
264 return *this;
267 Optional& operator=(std::nullopt_t) noexcept {
268 m_opt = std::nullopt;
269 return *this;
272 template<typename U = T>
273 std::enable_if_t<
274 std::conjunction_v<
275 not_self<U>,
276 std::negation<
277 std::conjunction<
278 std::is_scalar<T>,
279 std::is_same<T, std::decay_t<U>>
282 std::is_constructible<T, U>,
283 std::is_assignable<T&, U>
285 Optional&
287 operator=(U&& u) {
288 m_opt = std::forward<U>(u);
289 return *this;
292 template<typename U>
293 std::enable_if_t<
294 std::conjunction_v<
295 std::negation<std::is_same<T, U>>,
296 std::is_constructible<T, const U&>,
297 std::is_assignable<T&, U>,
298 std::negation<converts_from_optional<T, U>>,
299 std::negation<assigns_from_optional<T, U>>
301 Optional&
303 operator=(const Optional<U>& u) {
304 m_opt = u.m_opt;
305 return *this;
308 template<typename U>
309 std::enable_if_t<
310 std::conjunction_v<
311 std::negation<std::is_same<T, U>>,
312 std::is_constructible<T, U>,
313 std::is_assignable<T&, U>,
314 std::negation<converts_from_optional<T, U>>,
315 std::negation<assigns_from_optional<T, U>>
317 Optional&
319 operator=(Optional<U>&& u) {
320 m_opt = std::move(u.m_opt);
321 u.m_opt.reset();
322 return *this;
325 private:
326 std::optional<T> m_opt;
328 friend std::hash<Optional>;
330 #define OPERATORS \
331 O(==) \
332 O(!=) \
333 O(<) \
334 O(<=) \
335 O(>) \
336 O(>=)
338 #define O(op) \
339 template <typename U, typename V> \
340 friend constexpr bool operator op(const Optional<U>&, const V&); \
341 template <typename U, typename V> \
342 friend constexpr bool operator op(const U&, const Optional<V>&); \
343 template <typename T1, typename T2> \
344 friend constexpr bool operator op(const Optional<T1>&, const Optional<T2>&);
345 OPERATORS
346 #undef O
349 #define O(op) \
350 template <typename T, typename V> \
351 constexpr bool operator op(const Optional<T>& t, const V& v) { \
352 return t.m_opt op v; \
354 template <typename T, typename U> \
355 constexpr bool operator op(const U& u, const Optional<T>& t) { \
356 return u op t.m_opt; \
358 template <typename T1, typename T2> \
359 constexpr bool operator op(const Optional<T1>& t1, \
360 const Optional<T2>& t2) { \
361 return t1.m_opt op t2.m_opt; \
363 OPERATORS
364 #undef O
366 #undef OPERATORS
368 template <typename T1, typename T2>
369 void swap(Optional<T1>& t1, Optional<T2>& t2) noexcept(noexcept(t1.swap(t2))) {
370 return t1.swap(t2);
373 template<typename T>
374 constexpr Optional<std::decay_t<T>>
375 make_optional(T&& t) {
376 return Optional<std::decay_t<T>>{ std::forward<T>(t) };
378 template<typename T, typename ...Args>
379 constexpr Optional<T>
380 make_optional(Args&&... args) {
381 return Optional<T>{ std::in_place, std::forward<Args>(args)... };
383 template<typename T, typename U, typename ...Args>
384 constexpr Optional<T>
385 make_optional(std::initializer_list<U> il, Args&&... args) {
386 return Optional<T>{ std::in_place, il, std::forward<Args>(args)... };
389 ///////////////////////////////////////////////////////////////////////////////
392 namespace std {
393 ///////////////////////////////////////////////////////////////////////////////
395 template <typename T>
396 struct hash<HPHP::Optional<T>> {
397 size_t operator()(const HPHP::Optional<T>& o) const {
398 return m_hash(o.m_opt);
400 private:
401 hash<std::optional<T>> m_hash;
404 ///////////////////////////////////////////////////////////////////////////////