#pragma once /** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include #include #include #include namespace Aws { namespace Crt { namespace VariantDetail { template constexpr const T &ConstExprMax(const T &a, const T &b) { return (a < b) ? b : a; } namespace ParameterPackSize { // Returns a max of sizeof(T) over all T in a template parameter pack template constexpr std::size_t GetMaxSizeOf(std::size_t curMax = 0) { return ConstExprMax(curMax, sizeof(Last)); } template constexpr std::size_t GetMaxSizeOf(std::size_t curMax = 0) { return ConstExprMax(curMax, GetMaxSizeOf(ConstExprMax(curMax, sizeof(First)))); } // some old gcc versions does not work with alignas(Ts..) template constexpr std::size_t AlignAsPack(std::size_t curMax = 0) { return ConstExprMax(curMax, alignof(Last)); } template constexpr std::size_t AlignAsPack(std::size_t curMax = 0) { return ConstExprMax(curMax, AlignAsPack(ConstExprMax(curMax, alignof(First)))); } } // namespace ParameterPackSize namespace Index { using VariantIndex = short; template constexpr VariantIndex GetIndexOf(VariantIndex curIndex = -1) { return std::is_same::value ? curIndex : -1; } template constexpr VariantIndex GetIndexOf(VariantIndex curIndex = 0) { return std::is_same::value ? curIndex : GetIndexOf(++curIndex); } } // namespace Index namespace Checker { // Returns True if the template parameter pack Ts has a type T, i.e. ContainsType() == true if T // is in the list of Ts template constexpr bool ContainsType() { return std::is_same::value; } template constexpr bool ContainsType() { return std::is_same::value || ContainsType(); } // a case when the template parameter pack is empty (i.e. Variant<>) template constexpr bool ContainsType() { return false; } template struct HasType { static const bool value = ContainsType(); }; } // namespace Checker #if defined(AWS_CRT_ENABLE_VARIANT_DEBUG) namespace VariantDebug { template class VariantDebugBrowser { public: VariantDebugBrowser(char *storage) { InitTuple<0, Ts...>(storage); } std::tuple::type...> as_tuple; private: template void InitTuple(char *storage) { First *value = reinterpret_cast(storage); std::get(as_tuple) = value; InitTuple(storage); } template void InitTuple(char *storage) { Last *value = reinterpret_cast(storage); std::get(as_tuple) = value; } }; } // namespace VariantDebug #endif /* defined(AWS_CRT_ENABLE_VARIANT_DEBUG) */ } // namespace VariantDetail template class VariantAlternative; /** * Custom implementation of a Variant type. std::variant requires C++17 * @tparam Ts types of the variant value */ template class Variant { private: template using ThisVariantAlternative = VariantAlternative; template using EnableIfOtherIsThisVariantAlternative = typename std:: enable_if::type, Ts...>::value, int>::type; public: using IndexT = VariantDetail::Index::VariantIndex; static constexpr std::size_t AlternativeCount = sizeof...(Ts); Variant() { using FirstAlternative = typename ThisVariantAlternative<0>::type; new (m_storage) FirstAlternative(); m_index = 0; } Variant(const Variant &other) { AWS_FATAL_ASSERT(other.m_index != -1); m_index = other.m_index; VisitorUtil<0, Ts...>::VisitBinary(this, other, CopyMoveConstructor()); } Variant(Variant &&other) { AWS_FATAL_ASSERT(other.m_index != -1); m_index = other.m_index; VisitorUtil<0, Ts...>::VisitBinary(this, std::move(other), CopyMoveConstructor()); } template = 1> Variant(const T &val) { static_assert( VariantDetail::Checker::HasType::type, Ts...>::value, "This variant does not have such alternative T."); static_assert( sizeof(T) <= STORAGE_SIZE, "Attempting to instantiate a Variant with a type bigger than all alternatives."); using PlainT = typename std::decay::type; new (m_storage) PlainT(val); m_index = VariantDetail::Index::GetIndexOf(); AWS_ASSERT(m_index != -1); } template = 1> Variant(T &&val) { static_assert( VariantDetail::Checker::HasType::type, Ts...>::value, "This variant does not have such alternative T."); static_assert( sizeof(T) <= STORAGE_SIZE, "Attempting to instantiate a Variant with a type bigger than all alternatives."); using PlainT = typename std::decay::type; new (m_storage) PlainT(std::forward(val)); m_index = VariantDetail::Index::GetIndexOf(); AWS_ASSERT(m_index != -1); } // An overload to initialize with an Alternative T in-place template explicit Variant(Aws::Crt::InPlaceTypeT, Args &&...args) { static_assert( VariantDetail::Checker::HasType::type, Ts...>::value, "This variant does not have such alternative T."); static_assert( sizeof(T) <= STORAGE_SIZE, "Attempting to instantiate a Variant with a type bigger than all alternatives."); using PlainT = typename std::decay::type; new (m_storage) PlainT(std::forward(args)...); m_index = VariantDetail::Index::GetIndexOf(); AWS_ASSERT(m_index != -1); } Variant &operator=(const Variant &other) { if (this != &other) { AWS_FATAL_ASSERT(other.m_index != -1); if (m_index != other.m_index) { Destroy(); m_index = other.m_index; VisitorUtil<0, Ts...>::VisitBinary(this, other, CopyMoveConstructor()); } else { VisitorUtil<0, Ts...>::VisitBinary(this, other, CopyMoveAssigner()); } } return *this; } Variant &operator=(Variant &&other) { if (this != &other) { AWS_FATAL_ASSERT(other.m_index != -1); if (m_index != other.m_index) { Destroy(); m_index = other.m_index; VisitorUtil<0, Ts...>::VisitBinary(this, std::move(other), CopyMoveConstructor()); } else { VisitorUtil<0, Ts...>::VisitBinary(this, std::move(other), CopyMoveAssigner()); }; } return *this; } /* emplace */ template = 1> T &emplace(Args &&...args) { static_assert( VariantDetail::Checker::HasType::type, Ts...>::value, "This variant does not have such alternative T."); static_assert( sizeof(T) <= STORAGE_SIZE, "Attempting to instantiate a Variant with a type bigger than all alternatives."); Destroy(); using PlainT = typename std::decay::type; new (m_storage) PlainT(std::forward(args)...); m_index = VariantDetail::Index::GetIndexOf(); AWS_ASSERT(m_index != -1); T *value = reinterpret_cast(m_storage); return *value; } template auto emplace(Args &&...args) -> typename ThisVariantAlternative::type & { static_assert(Index < AlternativeCount, "Unknown alternative index to emplace"); using AlternativeT = typename ThisVariantAlternative::type; return emplace(std::forward(args)...); } template = 1> bool holds_alternative() const { AWS_ASSERT(m_index != -1); return m_index == VariantDetail::Index::GetIndexOf(); } /* non-const get */ template = 1> T &get() { AWS_FATAL_ASSERT(holds_alternative()); T *value = reinterpret_cast(m_storage); return *value; } template = 1> T *get_if() { if (holds_alternative()) { T *value = reinterpret_cast(m_storage); return value; } else { return nullptr; } } template auto get() -> typename ThisVariantAlternative::type & { static_assert(Index < AlternativeCount, "Unknown alternative index to get"); AWS_FATAL_ASSERT(holds_alternative()); using AlternativeT = typename ThisVariantAlternative::type; AlternativeT *ret = reinterpret_cast(m_storage); return *ret; } /* const get */ template = 1> const T &get() const { AWS_FATAL_ASSERT(holds_alternative()); const T *value = reinterpret_cast(m_storage); return *value; } template = 1> const T *get_if() const { if (holds_alternative()) { T *value = reinterpret_cast(m_storage); return value; } else { return nullptr; } } template auto get() const -> const typename ThisVariantAlternative::type & { static_assert(Index < AlternativeCount, "Unknown alternative index to get"); AWS_ASSERT(Index == m_index); using AlternativeT = typename ThisVariantAlternative::type; const AlternativeT *ret = reinterpret_cast(m_storage); return *ret; } /* This is just a templated way to say * "int*" for * a VariantAlternative<0, Variant()>*/ template using RawAlternativePointerT = typename std::add_pointer::type>::type; template auto get_if() -> RawAlternativePointerT { static_assert(Index < AlternativeCount, "Unknown alternative index to get"); if (holds_alternative()) { using AlternativePtrT = RawAlternativePointerT; AlternativePtrT value = reinterpret_cast(m_storage); return value; } else { return nullptr; } } template using ConstRawAlternativePointerT = typename std::add_pointer< typename std::add_const::type>::type>::type; template auto get_if() const -> ConstRawAlternativePointerT { static_assert(Index < AlternativeCount, "Unknown alternative index to get"); if (holds_alternative()) { using AlternativePtrT = ConstRawAlternativePointerT; AlternativePtrT value = reinterpret_cast(m_storage); return value; } else { return nullptr; } } std::size_t index() const { return m_index; } ~Variant() { Destroy(); } template void Visit(VisitorT &&visitor) { return VisitorUtil<0, Ts...>::Visit(this, std::forward(visitor)); } private: static constexpr std::size_t STORAGE_SIZE = VariantDetail::ParameterPackSize::GetMaxSizeOf(); alignas(VariantDetail::ParameterPackSize::AlignAsPack()) char m_storage[STORAGE_SIZE]; IndexT m_index = -1; #if defined(AWS_CRT_ENABLE_VARIANT_DEBUG) VariantDetail::VariantDebug::VariantDebugBrowser browser = m_storage; #endif /* defined(AWS_CRT_ENABLE_VARIANT_DEBUG) */ template constexpr bool holds_alternative() const { return Index == m_index; } struct Destroyer { template void operator()(AlternativeT &&value) const { using PlaintT = typename std::remove_reference::type; value.~PlaintT(); } }; void Destroy() { AWS_FATAL_ASSERT(m_index != -1); Visit(Destroyer()); m_index = -1; } struct CopyMoveConstructor { template void operator()(AlternativeT &&value, AlternativeT &&other) const { using PlaintT = typename std::remove_reference::type; new (&value) PlaintT(std::move(other)); } template void operator()(AlternativeT &&value, ConstAlternativeT &other) const { using PlaintT = typename std::remove_reference::type; using PlaintOtherT = typename std::remove_const::type>::type; static_assert(std::is_same::value, "Incompatible types"); new (&value) PlaintT(other); } }; struct CopyMoveAssigner { template void operator()(AlternativeT &&value, AlternativeT &&other) const { value = std::move(other); } template void operator()(AlternativeT &&value, ConstAlternativeT &other) const { using PlaintT = typename std::remove_reference::type; using PlaintOtherT = typename std::remove_const::type>::type; static_assert(std::is_same::value, "Incompatible types"); value = other; } }; template struct VisitorUtil; template struct VisitorUtil { template static void Visit(Variant *pThis, VisitorStruct &&visitor) { static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); if (Index == pThis->m_index) { using AlternativeT = typename ThisVariantAlternative::type; AlternativeT *value = reinterpret_cast(pThis->m_storage); visitor(*value); } else { VisitorUtil::Visit(pThis, std::forward(visitor)); } } template static void VisitBinary(Variant *pThis, Variant &&other, VisitorStruct &&visitor) { static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); if (Index == pThis->m_index) { using AlternativeT = typename ThisVariantAlternative::type; AlternativeT *value = reinterpret_cast(pThis->m_storage); visitor(*value, other.get()); } else { VisitorUtil::VisitBinary( pThis, std::forward>(other), std::forward(visitor)); } } template static void VisitBinary(Variant *pThis, const Variant &other, VisitorStruct &&visitor) { static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); if (Index == pThis->m_index) { using AlternativeT = typename ThisVariantAlternative::type; AlternativeT *value = reinterpret_cast(pThis->m_storage); const AlternativeT &otherValue = other.get(); visitor(*value, otherValue); } else { VisitorUtil::VisitBinary( pThis, other, std::forward(visitor)); } } }; template struct VisitorUtil { template static void Visit(Variant *pThis, VisitorStruct &&visitor) { static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); if (Index == pThis->m_index) { using AlternativeT = typename ThisVariantAlternative::type; AlternativeT *value = reinterpret_cast(pThis->m_storage); visitor(*value); } else { AWS_FATAL_ASSERT(!"Unknown variant alternative to visit!"); } } template static void VisitBinary(Variant *pThis, Variant &&other, VisitorStruct &&visitor) { static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); if (Index == pThis->m_index) { using AlternativeT = typename ThisVariantAlternative::type; AlternativeT *value = reinterpret_cast(pThis->m_storage); visitor(*value, other.get()); } else { AWS_FATAL_ASSERT(!"Unknown variant alternative to visit!"); } } template static void VisitBinary(Variant *pThis, const Variant &other, VisitorStruct &&visitor) { static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); if (Index == pThis->m_index) { using AlternativeT = typename ThisVariantAlternative::type; AlternativeT *value = reinterpret_cast(pThis->m_storage); const AlternativeT &otherValue = other.get(); visitor(*value, otherValue); } else { AWS_FATAL_ASSERT(!"Unknown variant alternative to visit!"); } } }; }; /* Helper template to get an actual type from an Index */ template class VariantAlternative { public: // uses std::tuple as a helper struct to provide index-based access of a parameter pack using type = typename std::tuple_element>::type; VariantAlternative(const Variant &) {} VariantAlternative(const Variant *) {} }; template class VariantSize { constexpr static const std::size_t Value = T::AlternativeCount; }; } // namespace Crt } // namespace Aws