2 +----------------------------------------------------------------------+
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 +----------------------------------------------------------------------+
19 #include <initializer_list>
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
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.
48 ///////////////////////////////////////////////////////////////////////////////
52 // These all intentionally match the interface for std::optional:
54 constexpr const T
& operator*() const& {
55 always_assert(m_opt
.has_value());
58 constexpr T
& operator*() & {
59 always_assert(m_opt
.has_value());
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());
89 constexpr const T
& value() const & {
90 always_assert(m_opt
.has_value());
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.
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
))) {
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
)...);
136 // SFINAE stuff to support the required ctor and assignment
138 template<typename
... Cond
>
139 using Requires
= std::enable_if_t
<std::conjunction_v
<Cond
...>, bool>;
142 using remove_cvref_t
=
143 typename
std::remove_cv
<typename
std::remove_reference
<U
>::type
>::type
;
146 using not_self
= std::negation
<std::is_same
<Optional
, remove_cvref_t
<U
>>>;
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
>&&>>;
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
)
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
>,
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
>,
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
)
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
)
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
>&,
248 explicit constexpr Optional(std::in_place_t
,
249 std::initializer_list
<U
> il
,
251 : m_opt(std::in_place
, il
, std::forward
<Args
>(args
)...) {}
253 Optional
& operator=(const Optional
& o
) {
258 Optional
& operator=(Optional
&& o
)
260 noexcept(std::declval
<decltype(o
.m_opt
)>().operator=(std::move(o
.m_opt
)))
262 m_opt
= std::move(o
.m_opt
);
267 Optional
& operator=(std::nullopt_t
) noexcept
{
268 m_opt
= std::nullopt
;
272 template<typename U
= T
>
279 std::is_same
<T
, std::decay_t
<U
>>
282 std::is_constructible
<T
, U
>,
283 std::is_assignable
<T
&, U
>
288 m_opt
= std::forward
<U
>(u
);
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
>>
303 operator=(const Optional
<U
>& u
) {
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
>>
319 operator=(Optional
<U
>&& u
) {
320 m_opt
= std::move(u
.m_opt
);
326 std::optional
<T
> m_opt
;
328 friend std::hash
<Optional
>;
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>&);
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; \
368 template <typename T1
, typename T2
>
369 void swap(Optional
<T1
>& t1
, Optional
<T2
>& t2
) noexcept(noexcept(t1
.swap(t2
))) {
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 ///////////////////////////////////////////////////////////////////////////////
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
);
401 hash
<std::optional
<T
>> m_hash
;
404 ///////////////////////////////////////////////////////////////////////////////