1 //
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
27 namespace arm
28 {
29
30 namespace 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.
35 struct EmptyOptional {};
36
37 /// Disambiguation tag that can be passed to the constructor to indicate that
38 /// the contained object should be constructed in-place
39 struct 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.
48 class OptionalBase
49 {
50 public:
OptionalBase()51 OptionalBase() noexcept
52 : m_HasValue{false}
53 {
54 }
55
has_value() const56 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.
operator bool() const64 explicit operator bool() const noexcept
65 {
66 return has_value();
67 }
68
69 protected:
OptionalBase(bool hasValue)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 ///
83 template <bool IsReference, typename T>
84 class OptionalReferenceSwitch : public OptionalBase
85 {
86 public:
87 using Base = OptionalBase;
88
OptionalReferenceSwitch()89 OptionalReferenceSwitch() noexcept : Base{} {}
OptionalReferenceSwitch(EmptyOptional)90 OptionalReferenceSwitch(EmptyOptional) noexcept : Base{} {}
91
OptionalReferenceSwitch(const T & value)92 OptionalReferenceSwitch(const T& value)
93 : Base{}
94 {
95 Construct(value);
96 }
97
98 template<class... Args>
OptionalReferenceSwitch(ConstructInPlace,Args &&...args)99 OptionalReferenceSwitch(ConstructInPlace, Args&&... args)
100 : Base{}
101 {
102 Construct(ARM_PIPE_CONSTRUCT_IN_PLACE, std::forward<Args>(args)...);
103 }
104
OptionalReferenceSwitch(const OptionalReferenceSwitch & other)105 OptionalReferenceSwitch(const OptionalReferenceSwitch& other)
106 : Base{}
107 {
108 *this = other;
109 }
110
operator =(const T & value)111 OptionalReferenceSwitch& operator=(const T& value)
112 {
113 reset();
114 Construct(value);
115 return *this;
116 }
117
operator =(const OptionalReferenceSwitch & other)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
operator =(EmptyOptional)129 OptionalReferenceSwitch& operator=(EmptyOptional)
130 {
131 reset();
132 return *this;
133 }
134
~OptionalReferenceSwitch()135 ~OptionalReferenceSwitch()
136 {
137 reset();
138 }
139
reset()140 void reset()
141 {
142 if (Base::has_value())
143 {
144 value().T::~T();
145 Base::m_HasValue = false;
146 }
147 }
148
value() const149 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
value()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
171 private:
Construct(const T & value)172 void Construct(const T& value)
173 {
174 new (m_Storage) T(value);
175 m_HasValue = true;
176 }
177
178 template<class... Args>
Construct(ConstructInPlace,Args &&...args)179 void Construct(ConstructInPlace, Args&&... args)
180 {
181 new (m_Storage) T(std::forward<Args>(args)...);
182 m_HasValue = true;
183 }
184
185 alignas(alignof(T)) unsigned char m_Storage[sizeof(T)];
186 };
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 ///
193 template <typename T>
194 class OptionalReferenceSwitch<true, T> : public OptionalBase
195 {
196 public:
197 using Base = OptionalBase;
198 using NonRefT = typename std::remove_reference<T>::type;
199
OptionalReferenceSwitch()200 OptionalReferenceSwitch() noexcept : Base{}, m_Storage{nullptr} {}
OptionalReferenceSwitch(EmptyOptional)201 OptionalReferenceSwitch(EmptyOptional) noexcept : Base{}, m_Storage{nullptr} {}
202
OptionalReferenceSwitch(const OptionalReferenceSwitch & other)203 OptionalReferenceSwitch(const OptionalReferenceSwitch& other) : Base{}
204 {
205 *this = other;
206 }
207
OptionalReferenceSwitch(T value)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
operator =(const T value)217 OptionalReferenceSwitch& operator=(const T value)
218 {
219 m_Storage = &value;
220 Base::m_HasValue = true;
221 return *this;
222 }
223
operator =(const OptionalReferenceSwitch & other)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
operator =(EmptyOptional)231 OptionalReferenceSwitch& operator=(EmptyOptional)
232 {
233 reset();
234 return *this;
235 }
236
~OptionalReferenceSwitch()237 ~OptionalReferenceSwitch()
238 {
239 reset();
240 }
241
reset()242 void reset()
243 {
244 Base::m_HasValue = false;
245 m_Storage = nullptr;
246 }
247
value() const248 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
value()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
268 private:
269 NonRefT* m_Storage;
270 };
271
272 template <typename T>
273 class Optional final : public OptionalReferenceSwitch<std::is_reference<T>::value, T>
274 {
275 public:
276 using BaseSwitch = OptionalReferenceSwitch<std::is_reference<T>::value, T>;
277
Optional()278 Optional() noexcept : BaseSwitch{} {}
Optional(const T & value)279 Optional(const T& value) : BaseSwitch{value} {}
280 Optional& operator=(const Optional& other) = default;
Optional(EmptyOptional empty)281 Optional(EmptyOptional empty) : BaseSwitch{empty} {}
Optional(const Optional & other)282 Optional(const Optional& other) : BaseSwitch{other} {}
Optional(const BaseSwitch & other)283 Optional(const BaseSwitch& other) : BaseSwitch{other} {}
284
285 template<class... Args>
Optional(ConstructInPlace,Args &&...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).
operator ==(const Optional<T> & rhs) const291 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
307 template<typename T, class... Args>
MakeOptional(Args &&...args)308 Optional<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
315