// // Copyright © 2017 Arm Ltd. All rights reserved. // SPDX-License-Identifier: MIT // #pragma once #include "Exceptions.hpp" #include #include /// 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 armnn { /// 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 CONSTRUCT_IN_PLACE armnn::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 == Optional" 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 class OptionalReferenceSwitch : public OptionalBase { public: using Base = OptionalBase; OptionalReferenceSwitch() noexcept : Base{} {} OptionalReferenceSwitch(EmptyOptional) noexcept : Base{} {} OptionalReferenceSwitch(const T& value) : Base{} { Construct(value); } template OptionalReferenceSwitch(ConstructInPlace, Args&&... args) : Base{} { Construct(CONSTRUCT_IN_PLACE, std::forward(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(m_Storage); return *valuePtr; } T& value() { if (!Base::has_value()) { throw BadOptionalAccessException("Optional has no value"); } auto valuePtr = reinterpret_cast(m_Storage); return *valuePtr; } private: void Construct(const T& value) { new (m_Storage) T(value); m_HasValue = true; } template void Construct(ConstructInPlace, Args&&... args) { new (m_Storage) T(std::forward(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 class OptionalReferenceSwitch : public OptionalBase { public: using Base = OptionalBase; using NonRefT = typename std::remove_reference::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 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 class Optional final : public OptionalReferenceSwitch::value, T> { public: using BaseSwitch = OptionalReferenceSwitch::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 explicit Optional(ConstructInPlace, Args&&... args) : BaseSwitch(CONSTRUCT_IN_PLACE, std::forward(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& 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 object template Optional MakeOptional(Args&&... args) { return Optional(CONSTRUCT_IN_PLACE, std::forward(args)...); } }