blob: 116466d013f91be4ef32ef3976335c6fab1be2c7 [file] [log] [blame]
//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//
#pragma once
#include "Exceptions.hpp"
#include <type_traits>
#include <cstring>
// 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;
}
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(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(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(CONSTRUCT_IN_PLACE, std::forward<Args>(args)...) {}
};
// 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>(CONSTRUCT_IN_PLACE, std::forward<Args>(args)...);
}
}