Skip to content

Commit 01cc7cb

Browse files
committed
Add an initial implementation of std::optional<T>.
This initial implementation supports all the metaprogramming around the enabling/disabling and explicit'ness of constructors and assignment operators, but it does not support the metaprogramming around the triviality of constructors, assignment operators, and destructors; nor does it support the proper constexpr'ness of the various constructors.
1 parent 02871e9 commit 01cc7cb

File tree

7 files changed

+380
-0
lines changed

7 files changed

+380
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#pragma once
2+
3+
#include "scratch/bits/type-traits/decay.h"
4+
#include "scratch/bits/type-traits/is-convertible.h"
5+
#include "scratch/bits/type-traits/is-fooible.h"
6+
7+
namespace scratch {
8+
9+
template<class T> class optional; // forward declaration
10+
11+
} // namespace scratch
12+
13+
namespace scratch::detail {
14+
15+
// This type-trait is "true" if T is somehow convertible-from optional<U>,
16+
// which means that if optional<T> and optional<U> are on the left and
17+
// right-hand sides of an assignment expression, we should prefer to
18+
// interpret that as `optT.emplace(optU)` instead of
19+
// `optT = (optU ? *optU : nullopt)`.
20+
21+
template<class T, class U>
22+
inline constexpr bool enable_assignment_of_optional_from_forwarded_v =
23+
!is_same_v<decay_t<U>, optional<T>> && (
24+
!is_scalar_v<T> ||
25+
!is_same_v<decay_t<U>, T>
26+
);
27+
28+
template<class T, class U>
29+
inline constexpr bool enable_assignment_of_optional_from_optional_v = !(
30+
is_constructible_v<T, optional<U>&> ||
31+
is_constructible_v<T, const optional<U>&> ||
32+
is_constructible_v<T, optional<U>&&> ||
33+
is_constructible_v<T, const optional<U>&&> ||
34+
is_convertible_v<optional<U>&, T> ||
35+
is_convertible_v<const optional<U>&, T> ||
36+
is_convertible_v<optional<U>&&, T> ||
37+
is_convertible_v<const optional<U>&&, T> ||
38+
is_assignable_v<T&, optional<U>&> ||
39+
is_assignable_v<T&, const optional<U>&> ||
40+
is_assignable_v<T&, optional<U>&&> ||
41+
is_assignable_v<T&, const optional<U>&&>
42+
);
43+
44+
} // namespace scratch::detail
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
namespace scratch {
4+
5+
struct in_place_t {
6+
constexpr explicit in_place_t() = default;
7+
};
8+
9+
inline constexpr in_place_t in_place{};
10+
11+
} // namespace scratch
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
namespace scratch {
4+
5+
struct nullopt_t {
6+
constexpr explicit nullopt_t(int) {}
7+
};
8+
9+
inline constexpr nullopt_t nullopt(42);
10+
11+
} // namespace scratch
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
#pragma once
2+
3+
#include "scratch/bits/stdexcept/bad-optional-access.h"
4+
#include "scratch/bits/optional/enable-optional-smfs.h"
5+
#include "scratch/bits/optional/in-place.h"
6+
#include "scratch/bits/optional/nullopt.h"
7+
#include "scratch/bits/type-traits/enable-if.h"
8+
#include "scratch/bits/type-traits/is-convertible.h"
9+
#include "scratch/bits/type-traits/is-foo.h"
10+
#include "scratch/bits/type-traits/is-fooible.h"
11+
#include "scratch/bits/type-traits/is-swappable.h"
12+
13+
#include <initializer_list>
14+
#include <new>
15+
#include <utility>
16+
17+
namespace scratch {
18+
19+
template<class T>
20+
class optional {
21+
22+
static_assert(is_object_v<T>, "optional<T> works only with object types");
23+
24+
union {
25+
char m_dummy;
26+
T m_value;
27+
};
28+
bool m_has_value;
29+
30+
public:
31+
using value_type = T;
32+
33+
constexpr optional() noexcept : m_has_value(false) {}
34+
constexpr optional(nullopt_t) noexcept : m_has_value(false) {}
35+
36+
template<bool_if_t<is_copy_constructible_v<T>> = true>
37+
optional(const optional& rhs)
38+
noexcept(is_nothrow_copy_constructible_v<T>)
39+
{
40+
m_has_value = rhs.has_value();
41+
if (m_has_value) {
42+
::new (&m_value) T(*rhs);
43+
}
44+
}
45+
46+
template<bool_if_t<is_move_constructible_v<T>> = true>
47+
optional(optional&& rhs)
48+
noexcept(is_nothrow_move_constructible_v<T>)
49+
{
50+
m_has_value = rhs.has_value();
51+
if (m_has_value) {
52+
::new (&m_value) T(std::move(*rhs));
53+
}
54+
}
55+
56+
template<class U, bool_if_t<is_constructible_v<T, const U&> && !is_convertible_v<const U&, T>> = true>
57+
explicit optional(const optional<U>& rhs)
58+
noexcept(is_nothrow_constructible_v<T, const U&>)
59+
{
60+
m_has_value = rhs.has_value();
61+
if (m_has_value) {
62+
::new (&m_value) T(*rhs);
63+
}
64+
}
65+
66+
template<class U, bool_if_t<is_constructible_v<T, const U&> && is_convertible_v<const U&, T>> = true>
67+
optional(const optional<U>& rhs)
68+
noexcept(is_nothrow_constructible_v<T, const U&>)
69+
{
70+
m_has_value = rhs.has_value();
71+
if (m_has_value) {
72+
::new (&m_value) T(*rhs);
73+
}
74+
}
75+
76+
template<class U, bool_if_t<is_constructible_v<T, U&&> && !is_convertible_v<U&&, T>> = true>
77+
explicit optional(optional<U>&& rhs)
78+
noexcept(is_nothrow_constructible_v<T, U&&>)
79+
{
80+
m_has_value = rhs.has_value();
81+
if (m_has_value) {
82+
::new (&m_value) T(std::move(*rhs));
83+
}
84+
}
85+
86+
template<class U, bool_if_t<is_constructible_v<T, U&&> && is_convertible_v<U&&, T>> = true>
87+
optional(optional<U>&& rhs)
88+
noexcept(is_nothrow_constructible_v<T, U&&>)
89+
{
90+
m_has_value = rhs.has_value();
91+
if (m_has_value) {
92+
::new (&m_value) T(std::move(*rhs));
93+
}
94+
}
95+
96+
template<class U = T, bool_if_t<is_constructible_v<T, U&&> && !is_convertible_v<U&&, T>> = true>
97+
explicit optional(U&& value)
98+
noexcept(is_nothrow_constructible_v<T, U&&>)
99+
{
100+
m_has_value = true;
101+
::new (&m_value) T(std::forward<U>(value));
102+
}
103+
104+
template<class U = T, bool_if_t<is_constructible_v<T, U&&> && is_convertible_v<U&&, T>> = true>
105+
optional(U&& value)
106+
noexcept(is_nothrow_constructible_v<T, U&&>)
107+
{
108+
m_has_value = true;
109+
::new (&m_value) T(std::forward<U>(value));
110+
}
111+
112+
template<class... Args, bool_if_t<is_constructible_v<T, Args&&...>> = true>
113+
explicit optional(in_place_t, Args&&... args)
114+
noexcept(is_nothrow_constructible_v<T, Args&&...>)
115+
{
116+
m_has_value = true;
117+
::new (&m_value) T(std::forward<Args>(args)...);
118+
}
119+
120+
template<class U, class... Args, bool_if_t<is_constructible_v<T, std::initializer_list<U>&, Args&&...>> = true>
121+
explicit optional(in_place_t, std::initializer_list<U> il, Args&&... args)
122+
noexcept(is_nothrow_constructible_v<T, std::initializer_list<U>&, Args&&...>)
123+
{
124+
m_has_value = true;
125+
::new (&m_value) T(il, std::forward<Args>(args)...);
126+
}
127+
128+
optional& operator=(nullopt_t) noexcept
129+
{
130+
reset();
131+
return *this;
132+
}
133+
134+
template<class U, bool_if_t<is_constructible_v<T, const U&> && is_assignable_v<T&, const U&> && detail::enable_assignment_of_optional_from_optional_v<T, U>> = true>
135+
optional& operator=(const optional<U>& rhs)
136+
noexcept(is_nothrow_constructible_v<T, const U&> && is_nothrow_assignable_v<T&, const U&>)
137+
{
138+
if (rhs.has_value()) {
139+
if (this->has_value()) {
140+
m_value = *rhs;
141+
} else {
142+
m_has_value = true;
143+
::new (&m_value) T(*rhs);
144+
}
145+
} else {
146+
reset();
147+
}
148+
return *this;
149+
}
150+
151+
template<class U, bool_if_t<is_constructible_v<T, U&&> && is_assignable_v<T&, U&&> && detail::enable_assignment_of_optional_from_optional_v<T, U>> = true>
152+
optional& operator=(optional<U>&& rhs)
153+
noexcept(is_nothrow_constructible_v<T, U&&> && is_nothrow_assignable_v<T&, U&&>)
154+
{
155+
if (rhs.has_value()) {
156+
if (this->has_value()) {
157+
m_value = std::move(*rhs);
158+
} else {
159+
m_has_value = true;
160+
::new (&m_value) T(std::move(*rhs));
161+
}
162+
} else {
163+
reset();
164+
}
165+
return *this;
166+
}
167+
168+
template<class U = T, bool_if_t<is_constructible_v<T, U&&> && is_assignable_v<T&, U&&> && detail::enable_assignment_of_optional_from_forwarded_v<T, U>> = true>
169+
optional& operator=(U&& rhs)
170+
noexcept(is_nothrow_constructible_v<T, U&&> && is_nothrow_assignable_v<T&, U&&>)
171+
{
172+
173+
if (this->has_value()) {
174+
m_value = std::forward<U>(rhs);
175+
} else {
176+
m_has_value = true;
177+
::new (&m_value) T(std::forward<U>(rhs));
178+
}
179+
return *this;
180+
}
181+
182+
~optional() {
183+
reset();
184+
}
185+
186+
constexpr const T* operator->() const { return &m_value; }
187+
constexpr T* operator->() { return &m_value; }
188+
constexpr const T& operator*() const& { return m_value; }
189+
constexpr T& operator*() & { return m_value; }
190+
constexpr const T&& operator*() const&& { return std::move(m_value); }
191+
constexpr T&& operator*() && { return std::move(m_value); }
192+
193+
constexpr explicit operator bool() const noexcept { return has_value(); }
194+
constexpr bool has_value() const noexcept { return m_has_value; }
195+
196+
constexpr T& value() & { if (!has_value()) throw bad_optional_access(); return *(*this); }
197+
constexpr const T& value() const & { if (!has_value()) throw bad_optional_access(); return *(*this); }
198+
constexpr T&& value() && { if (!has_value()) throw bad_optional_access(); return std::move(*(*this)); }
199+
constexpr const T&& value() const && { if (!has_value()) throw bad_optional_access(); return std::move(*(*this)); }
200+
201+
template<class U = T> constexpr T value_or(U&& u) const & { return has_value() ? *(*this) : static_cast<T>(std::forward<U>(u)); }
202+
template<class U = T> constexpr T value_or(U&& u) && { return has_value() ? std::move(*(*this)) : static_cast<T>(std::forward<U>(u)); }
203+
204+
void swap(optional& rhs)
205+
noexcept(is_nothrow_move_constructible_v<T> && is_nothrow_swappable_v<T>)
206+
{
207+
using std::swap;
208+
if (has_value()) {
209+
if (rhs.has_value()) {
210+
swap(**this, *rhs);
211+
} else {
212+
::new (&rhs.m_value) T(std::move(m_value));
213+
rhs.m_has_value = true;
214+
m_value.~T();
215+
m_has_value = false;
216+
}
217+
} else {
218+
if (rhs.has_value()) {
219+
::new (&m_value) T(std::move(rhs.m_value));
220+
m_has_value = true;
221+
rhs.m_value.~T();
222+
rhs.m_has_value = false;
223+
} else {
224+
// do nothing
225+
}
226+
}
227+
}
228+
229+
void reset() noexcept {
230+
if (m_has_value) {
231+
m_value.~T();
232+
m_has_value = false;
233+
}
234+
}
235+
236+
template<class... Args, bool_if_t<is_constructible_v<T, Args&&...>> = true>
237+
T& emplace(Args&&... args)
238+
noexcept(is_nothrow_constructible_v<T, Args&&...>)
239+
{
240+
reset();
241+
::new (&m_value) T(std::forward<Args>(args)...);
242+
m_has_value = true;
243+
return m_value;
244+
}
245+
246+
template<class U, class... Args, bool_if_t<is_constructible_v<T, std::initializer_list<U>&, Args&&...>> = true>
247+
T& emplace(std::initializer_list<U> il, Args&&... args)
248+
noexcept(is_nothrow_constructible_v<T, std::initializer_list<U>&, Args&&...>)
249+
{
250+
reset();
251+
::new (&m_value) T(il, std::forward<Args>(args)...);
252+
m_has_value = true;
253+
return m_value;
254+
}
255+
};
256+
257+
template<class T>
258+
void swap(optional<T>& a, optional<T>& b)
259+
noexcept(noexcept(a.swap(b)))
260+
{
261+
a.swap(b);
262+
}
263+
264+
template<class T, class U> constexpr bool operator==(const optional<T>& a, const optional<U>& b) { return bool(a) ? (bool(b) ? (*a == *b) : false) : !bool(b); }
265+
template<class T, class U> constexpr bool operator!=(const optional<T>& a, const optional<U>& b) { return bool(a) ? (bool(b) ? (*a != *b) : true) : bool(b); }
266+
template<class T, class U> constexpr bool operator<(const optional<T>& a, const optional<U>& b) { return bool(a) ? (bool(b) ? (*a < *b) : false) : bool(b); }
267+
template<class T, class U> constexpr bool operator<=(const optional<T>& a, const optional<U>& b) { return bool(a) ? (bool(b) ? (*a <= *b) : false) : true; }
268+
template<class T, class U> constexpr bool operator>(const optional<T>& a, const optional<U>& b) { return bool(a) ? (bool(b) ? (*a > *b) : true) : false; }
269+
template<class T, class U> constexpr bool operator>=(const optional<T>& a, const optional<U>& b) { return bool(a) ? (bool(b) ? (*a >= *b) : true) : !bool(b); }
270+
271+
template<class T, class U> constexpr bool operator==(const optional<T>& a, const U& b) { return bool(a) ? (*a == b) : false; }
272+
template<class T, class U> constexpr bool operator!=(const optional<T>& a, const U& b) { return bool(a) ? (*a != b) : true; }
273+
template<class T, class U> constexpr bool operator<(const optional<T>& a, const U& b) { return bool(a) ? (*a < b) : true; }
274+
template<class T, class U> constexpr bool operator<=(const optional<T>& a, const U& b) { return bool(a) ? (*a <= b) : true; }
275+
template<class T, class U> constexpr bool operator>(const optional<T>& a, const U& b) { return bool(a) ? (*a > b) : false; }
276+
template<class T, class U> constexpr bool operator>=(const optional<T>& a, const U& b) { return bool(a) ? (*a >= b) : false; }
277+
278+
template<class T, class U> constexpr bool operator==(const U& b, const optional<T>& a) { return bool(a) ? (b == *a) : false; }
279+
template<class T, class U> constexpr bool operator!=(const U& b, const optional<T>& a) { return bool(a) ? (b != *a) : true; }
280+
template<class T, class U> constexpr bool operator<(const U& b, const optional<T>& a) { return bool(a) ? (b < *a) : false; }
281+
template<class T, class U> constexpr bool operator<=(const U& b, const optional<T>& a) { return bool(a) ? (b <= *a) : false; }
282+
template<class T, class U> constexpr bool operator>(const U& b, const optional<T>& a) { return bool(a) ? (b > *a) : true; }
283+
template<class T, class U> constexpr bool operator>=(const U& b, const optional<T>& a) { return bool(a) ? (b >= *a) : true; }
284+
285+
template<class T> constexpr bool operator==(const optional<T>& a, nullopt_t) { return !bool(a); }
286+
template<class T> constexpr bool operator!=(const optional<T>& a, nullopt_t) { return bool(a); }
287+
template<class T> constexpr bool operator<(const optional<T>&, nullopt_t) { return false; }
288+
template<class T> constexpr bool operator<=(const optional<T>& a, nullopt_t) { return !bool(a); }
289+
template<class T> constexpr bool operator>(const optional<T>& a, nullopt_t) { return bool(a); }
290+
template<class T> constexpr bool operator>=(const optional<T>&, nullopt_t) { return true; }
291+
292+
template<class T> constexpr bool operator==(nullopt_t, const optional<T>& a) { return !bool(a); }
293+
template<class T> constexpr bool operator!=(nullopt_t, const optional<T>& a) { return bool(a); }
294+
template<class T> constexpr bool operator<(nullopt_t, const optional<T>& a) { return bool(a); }
295+
template<class T> constexpr bool operator<=(nullopt_t, const optional<T>&) { return true; }
296+
template<class T> constexpr bool operator>(nullopt_t, const optional<T>&) { return false; }
297+
template<class T> constexpr bool operator>=(nullopt_t, const optional<T>& a) { return !bool(a); }
298+
299+
} // namespace scratch
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
#include "scratch/bits/stdexcept/exception.h"
4+
5+
namespace scratch {
6+
7+
class bad_optional_access : public exception {};
8+
9+
} // namespace scratch

include/scratch/optional

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
#pragma once
2+
3+
#include "bits/optional/in-place.h"
4+
#include "bits/optional/nullopt.h"
5+
#include "bits/optional/optional.h"
6+
#include "bits/stdexcept/bad-optional-access.h"

include/scratch/utility

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#pragma once
22

3+
#include "bits/optional/in-place.h"
34
#include "bits/utility/declval.h"
45
#include "bits/utility/integer-sequence.h"

0 commit comments

Comments
 (0)