blob: 8fdbbfe21df223b29df7ece1ca670eebf4bb5498 [file] [log] [blame]
Jim Flynndecd08b2022-03-13 22:35:46 +00001//
2// Copyright © 2022 Arm Ltd. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5#pragma once
6
7#include "ProfilingException.hpp"
8
9#include <cstring>
10#include <type_traits>
11
12/// Optional is a drop in replacement for std::optional until we migrate
13/// to c++-17. Only a subset of the optional features are implemented that
14/// we intend to use in ArmNN.
15
16/// There are two distinct implementations here:
17///
18/// 1, for normal constructable/destructable types and reference types
19/// 2, for reference types
20
21/// The std::optional features we support are:
22///
23/// - has_value() and operator bool() to tell if the optional has a value
24/// - value() returns a reference to the held object
25///
26
27namespace arm
28{
29
30namespace pipe
31{
32
33/// EmptyOptional is used to initialize the Optional class in case we want
34/// to have default value for an Optional in a function declaration.
35struct EmptyOptional {};
36
37/// Disambiguation tag that can be passed to the constructor to indicate that
38/// the contained object should be constructed in-place
39struct ConstructInPlace
40{
41 explicit ConstructInPlace() = default;
42};
43
44#define ARM_PIPE_CONSTRUCT_IN_PLACE arm::pipe::ConstructInPlace{}
45
46/// OptionalBase is the common functionality between reference and non-reference
47/// optional types.
48class OptionalBase
49{
50public:
51 OptionalBase() noexcept
52 : m_HasValue{false}
53 {
54 }
55
56 bool has_value() const noexcept
57 {
58 return m_HasValue;
59 }
60
61 /// Conversion to bool, so can be used in if-statements and similar contexts expecting a bool.
62 /// Note this is explicit so that it doesn't get implicitly converted to a bool in unwanted cases,
63 /// for example "Optional<TypeA> == Optional<TypeB>" should not compile.
64 explicit operator bool() const noexcept
65 {
66 return has_value();
67 }
68
69protected:
70 OptionalBase(bool hasValue) noexcept
71 : m_HasValue{hasValue}
72 {
73 }
74
75 bool m_HasValue;
76};
77
78///
79/// The default implementation is the non-reference case. This
80/// has an unsigned char array for storing the optional value which
81/// is in-place constructed there.
82///
83template <bool IsReference, typename T>
84class OptionalReferenceSwitch : public OptionalBase
85{
86public:
87 using Base = OptionalBase;
88
89 OptionalReferenceSwitch() noexcept : Base{} {}
90 OptionalReferenceSwitch(EmptyOptional) noexcept : Base{} {}
91
92 OptionalReferenceSwitch(const T& value)
93 : Base{}
94 {
95 Construct(value);
96 }
97
98 template<class... Args>
99 OptionalReferenceSwitch(ConstructInPlace, Args&&... args)
100 : Base{}
101 {
102 Construct(ARM_PIPE_CONSTRUCT_IN_PLACE, std::forward<Args>(args)...);
103 }
104
105 OptionalReferenceSwitch(const OptionalReferenceSwitch& other)
106 : Base{}
107 {
108 *this = other;
109 }
110
111 OptionalReferenceSwitch& operator=(const T& value)
112 {
113 reset();
114 Construct(value);
115 return *this;
116 }
117
118 OptionalReferenceSwitch& operator=(const OptionalReferenceSwitch& other)
119 {
120 reset();
121 if (other.has_value())
122 {
123 Construct(other.value());
124 }
125
126 return *this;
127 }
128
129 OptionalReferenceSwitch& operator=(EmptyOptional)
130 {
131 reset();
132 return *this;
133 }
134
135 ~OptionalReferenceSwitch()
136 {
137 reset();
138 }
139
140 void reset()
141 {
142 if (Base::has_value())
143 {
144 value().T::~T();
145 Base::m_HasValue = false;
146 }
147 }
148
149 const T& value() const
150 {
151 if (!Base::has_value())
152 {
153 throw BadOptionalAccessException("Optional has no value");
154 }
155
156 auto valuePtr = reinterpret_cast<const T*>(m_Storage);
157 return *valuePtr;
158 }
159
160 T& value()
161 {
162 if (!Base::has_value())
163 {
164 throw BadOptionalAccessException("Optional has no value");
165 }
166
167 auto valuePtr = reinterpret_cast<T*>(m_Storage);
168 return *valuePtr;
169 }
170
171private:
172 void Construct(const T& value)
173 {
174 new (m_Storage) T(value);
175 m_HasValue = true;
176 }
177
178 template<class... Args>
179 void Construct(ConstructInPlace, Args&&... args)
180 {
181 new (m_Storage) T(std::forward<Args>(args)...);
182 m_HasValue = true;
183 }
184
Mike Kellybd207032023-08-09 18:39:42 +0100185 alignas(alignof(T)) unsigned char m_Storage[sizeof(T)] = {};
Jim Flynndecd08b2022-03-13 22:35:46 +0000186};
187
188///
189/// This is the special case for reference types. This holds a pointer
190/// to the referenced type. This doesn't own the referenced memory and
191/// it never calls delete on the pointer.
192///
193template <typename T>
194class OptionalReferenceSwitch<true, T> : public OptionalBase
195{
196public:
197 using Base = OptionalBase;
198 using NonRefT = typename std::remove_reference<T>::type;
199
200 OptionalReferenceSwitch() noexcept : Base{}, m_Storage{nullptr} {}
201 OptionalReferenceSwitch(EmptyOptional) noexcept : Base{}, m_Storage{nullptr} {}
202
203 OptionalReferenceSwitch(const OptionalReferenceSwitch& other) : Base{}
204 {
205 *this = other;
206 }
207
208 OptionalReferenceSwitch(T value)
209 : Base{true}
210 , m_Storage{&value}
211 {
212 }
213
214 template<class... Args>
215 OptionalReferenceSwitch(ConstructInPlace, Args&&... args) = delete;
216
217 OptionalReferenceSwitch& operator=(const T value)
218 {
219 m_Storage = &value;
220 Base::m_HasValue = true;
221 return *this;
222 }
223
224 OptionalReferenceSwitch& operator=(const OptionalReferenceSwitch& other)
225 {
226 m_Storage = other.m_Storage;
227 Base::m_HasValue = other.has_value();
228 return *this;
229 }
230
231 OptionalReferenceSwitch& operator=(EmptyOptional)
232 {
233 reset();
234 return *this;
235 }
236
237 ~OptionalReferenceSwitch()
238 {
239 reset();
240 }
241
242 void reset()
243 {
244 Base::m_HasValue = false;
245 m_Storage = nullptr;
246 }
247
248 const T value() const
249 {
250 if (!Base::has_value())
251 {
252 throw BadOptionalAccessException("Optional has no value");
253 }
254
255 return *m_Storage;
256 }
257
258 T value()
259 {
260 if (!Base::has_value())
261 {
262 throw BadOptionalAccessException("Optional has no value");
263 }
264
265 return *m_Storage;
266 }
267
268private:
269 NonRefT* m_Storage;
270};
271
272template <typename T>
273class Optional final : public OptionalReferenceSwitch<std::is_reference<T>::value, T>
274{
275public:
276 using BaseSwitch = OptionalReferenceSwitch<std::is_reference<T>::value, T>;
277
278 Optional() noexcept : BaseSwitch{} {}
279 Optional(const T& value) : BaseSwitch{value} {}
280 Optional& operator=(const Optional& other) = default;
281 Optional(EmptyOptional empty) : BaseSwitch{empty} {}
282 Optional(const Optional& other) : BaseSwitch{other} {}
283 Optional(const BaseSwitch& other) : BaseSwitch{other} {}
284
285 template<class... Args>
286 explicit Optional(ConstructInPlace, Args&&... args) :
287 BaseSwitch(ARM_PIPE_CONSTRUCT_IN_PLACE, std::forward<Args>(args)...) {}
288
289 /// Two optionals are considered equal if they are both empty or both contain values which
290 /// themselves are considered equal (via their own == operator).
291 bool operator==(const Optional<T>& rhs) const
292 {
293 if (!this->has_value() && !rhs.has_value())
294 {
295 return true;
296 }
297 if (this->has_value() && rhs.has_value() && this->value() == rhs.value())
298 {
299 return true;
300 }
301 return false;
302 }
303};
304
305/// Utility template that constructs an object of type T in-place and wraps
306/// it inside an Optional<T> object
307template<typename T, class... Args>
308Optional<T> MakeOptional(Args&&... args)
309{
310 return Optional<T>(ARM_PIPE_CONSTRUCT_IN_PLACE, std::forward<Args>(args)...);
311}
312
313} // namespace pipe
314} // namespace arm