xref: /aosp_15_r20/external/armnn/profiling/common/include/Optional.hpp (revision 89c4ff92f2867872bb9e2354d150bf0c8c502810)
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