| // |
| // Copyright © 2022 Arm Ltd. All rights reserved. |
| // SPDX-License-Identifier: MIT |
| // |
| #pragma once |
| |
| #include "ProfilingException.hpp" |
| |
| #include <cstring> |
| #include <type_traits> |
| |
| /// Optional is a drop in replacement for std::optional until we migrate |
| /// to c++-17. Only a subset of the optional features are implemented that |
| /// we intend to use in ArmNN. |
| |
| /// There are two distinct implementations here: |
| /// |
| /// 1, for normal constructable/destructable types and reference types |
| /// 2, for reference types |
| |
| /// The std::optional features we support are: |
| /// |
| /// - has_value() and operator bool() to tell if the optional has a value |
| /// - value() returns a reference to the held object |
| /// |
| |
| namespace arm |
| { |
| |
| namespace pipe |
| { |
| |
| /// EmptyOptional is used to initialize the Optional class in case we want |
| /// to have default value for an Optional in a function declaration. |
| struct EmptyOptional {}; |
| |
| /// Disambiguation tag that can be passed to the constructor to indicate that |
| /// the contained object should be constructed in-place |
| struct ConstructInPlace |
| { |
| explicit ConstructInPlace() = default; |
| }; |
| |
| #define ARM_PIPE_CONSTRUCT_IN_PLACE arm::pipe::ConstructInPlace{} |
| |
| /// OptionalBase is the common functionality between reference and non-reference |
| /// optional types. |
| class OptionalBase |
| { |
| public: |
| OptionalBase() noexcept |
| : m_HasValue{false} |
| { |
| } |
| |
| bool has_value() const noexcept |
| { |
| return m_HasValue; |
| } |
| |
| /// Conversion to bool, so can be used in if-statements and similar contexts expecting a bool. |
| /// Note this is explicit so that it doesn't get implicitly converted to a bool in unwanted cases, |
| /// for example "Optional<TypeA> == Optional<TypeB>" should not compile. |
| explicit operator bool() const noexcept |
| { |
| return has_value(); |
| } |
| |
| protected: |
| OptionalBase(bool hasValue) noexcept |
| : m_HasValue{hasValue} |
| { |
| } |
| |
| bool m_HasValue; |
| }; |
| |
| /// |
| /// The default implementation is the non-reference case. This |
| /// has an unsigned char array for storing the optional value which |
| /// is in-place constructed there. |
| /// |
| template <bool IsReference, typename T> |
| class OptionalReferenceSwitch : public OptionalBase |
| { |
| public: |
| using Base = OptionalBase; |
| |
| OptionalReferenceSwitch() noexcept : Base{} {} |
| OptionalReferenceSwitch(EmptyOptional) noexcept : Base{} {} |
| |
| OptionalReferenceSwitch(const T& value) |
| : Base{} |
| { |
| Construct(value); |
| } |
| |
| template<class... Args> |
| OptionalReferenceSwitch(ConstructInPlace, Args&&... args) |
| : Base{} |
| { |
| Construct(ARM_PIPE_CONSTRUCT_IN_PLACE, std::forward<Args>(args)...); |
| } |
| |
| OptionalReferenceSwitch(const OptionalReferenceSwitch& other) |
| : Base{} |
| { |
| *this = other; |
| } |
| |
| OptionalReferenceSwitch& operator=(const T& value) |
| { |
| reset(); |
| Construct(value); |
| return *this; |
| } |
| |
| OptionalReferenceSwitch& operator=(const OptionalReferenceSwitch& other) |
| { |
| reset(); |
| if (other.has_value()) |
| { |
| Construct(other.value()); |
| } |
| |
| return *this; |
| } |
| |
| OptionalReferenceSwitch& operator=(EmptyOptional) |
| { |
| reset(); |
| return *this; |
| } |
| |
| ~OptionalReferenceSwitch() |
| { |
| reset(); |
| } |
| |
| void reset() |
| { |
| if (Base::has_value()) |
| { |
| value().T::~T(); |
| Base::m_HasValue = false; |
| } |
| } |
| |
| const T& value() const |
| { |
| if (!Base::has_value()) |
| { |
| throw BadOptionalAccessException("Optional has no value"); |
| } |
| |
| auto valuePtr = reinterpret_cast<const T*>(m_Storage); |
| return *valuePtr; |
| } |
| |
| T& value() |
| { |
| if (!Base::has_value()) |
| { |
| throw BadOptionalAccessException("Optional has no value"); |
| } |
| |
| auto valuePtr = reinterpret_cast<T*>(m_Storage); |
| return *valuePtr; |
| } |
| |
| private: |
| void Construct(const T& value) |
| { |
| new (m_Storage) T(value); |
| m_HasValue = true; |
| } |
| |
| template<class... Args> |
| void Construct(ConstructInPlace, Args&&... args) |
| { |
| new (m_Storage) T(std::forward<Args>(args)...); |
| m_HasValue = true; |
| } |
| |
| alignas(alignof(T)) unsigned char m_Storage[sizeof(T)]; |
| }; |
| |
| /// |
| /// This is the special case for reference types. This holds a pointer |
| /// to the referenced type. This doesn't own the referenced memory and |
| /// it never calls delete on the pointer. |
| /// |
| template <typename T> |
| class OptionalReferenceSwitch<true, T> : public OptionalBase |
| { |
| public: |
| using Base = OptionalBase; |
| using NonRefT = typename std::remove_reference<T>::type; |
| |
| OptionalReferenceSwitch() noexcept : Base{}, m_Storage{nullptr} {} |
| OptionalReferenceSwitch(EmptyOptional) noexcept : Base{}, m_Storage{nullptr} {} |
| |
| OptionalReferenceSwitch(const OptionalReferenceSwitch& other) : Base{} |
| { |
| *this = other; |
| } |
| |
| OptionalReferenceSwitch(T value) |
| : Base{true} |
| , m_Storage{&value} |
| { |
| } |
| |
| template<class... Args> |
| OptionalReferenceSwitch(ConstructInPlace, Args&&... args) = delete; |
| |
| OptionalReferenceSwitch& operator=(const T value) |
| { |
| m_Storage = &value; |
| Base::m_HasValue = true; |
| return *this; |
| } |
| |
| OptionalReferenceSwitch& operator=(const OptionalReferenceSwitch& other) |
| { |
| m_Storage = other.m_Storage; |
| Base::m_HasValue = other.has_value(); |
| return *this; |
| } |
| |
| OptionalReferenceSwitch& operator=(EmptyOptional) |
| { |
| reset(); |
| return *this; |
| } |
| |
| ~OptionalReferenceSwitch() |
| { |
| reset(); |
| } |
| |
| void reset() |
| { |
| Base::m_HasValue = false; |
| m_Storage = nullptr; |
| } |
| |
| const T value() const |
| { |
| if (!Base::has_value()) |
| { |
| throw BadOptionalAccessException("Optional has no value"); |
| } |
| |
| return *m_Storage; |
| } |
| |
| T value() |
| { |
| if (!Base::has_value()) |
| { |
| throw BadOptionalAccessException("Optional has no value"); |
| } |
| |
| return *m_Storage; |
| } |
| |
| private: |
| NonRefT* m_Storage; |
| }; |
| |
| template <typename T> |
| class Optional final : public OptionalReferenceSwitch<std::is_reference<T>::value, T> |
| { |
| public: |
| using BaseSwitch = OptionalReferenceSwitch<std::is_reference<T>::value, T>; |
| |
| Optional() noexcept : BaseSwitch{} {} |
| Optional(const T& value) : BaseSwitch{value} {} |
| Optional& operator=(const Optional& other) = default; |
| Optional(EmptyOptional empty) : BaseSwitch{empty} {} |
| Optional(const Optional& other) : BaseSwitch{other} {} |
| Optional(const BaseSwitch& other) : BaseSwitch{other} {} |
| |
| template<class... Args> |
| explicit Optional(ConstructInPlace, Args&&... args) : |
| BaseSwitch(ARM_PIPE_CONSTRUCT_IN_PLACE, std::forward<Args>(args)...) {} |
| |
| /// Two optionals are considered equal if they are both empty or both contain values which |
| /// themselves are considered equal (via their own == operator). |
| bool operator==(const Optional<T>& rhs) const |
| { |
| if (!this->has_value() && !rhs.has_value()) |
| { |
| return true; |
| } |
| if (this->has_value() && rhs.has_value() && this->value() == rhs.value()) |
| { |
| return true; |
| } |
| return false; |
| } |
| }; |
| |
| /// Utility template that constructs an object of type T in-place and wraps |
| /// it inside an Optional<T> object |
| template<typename T, class... Args> |
| Optional<T> MakeOptional(Args&&... args) |
| { |
| return Optional<T>(ARM_PIPE_CONSTRUCT_IN_PLACE, std::forward<Args>(args)...); |
| } |
| |
| } // namespace pipe |
| } // namespace arm |