diff --git a/include/aws/crt/TypeTraits.h b/include/aws/crt/TypeTraits.h index 72ed9d9a6..ffc0c192f 100644 --- a/include/aws/crt/TypeTraits.h +++ b/include/aws/crt/TypeTraits.h @@ -26,5 +26,19 @@ namespace Aws struct IsSpecializationOf, Primary> : std::true_type { }; + + /** + * Forms the logical conjunction of the type traits Args..., effectively performing a logical AND on the + * sequence of traits. + */ + template struct Conjunction : std::true_type + { + }; + + template + struct Conjunction : std::conditional, std::false_type>::type + { + }; + } // namespace Crt } // namespace Aws diff --git a/include/aws/crt/Variant.h b/include/aws/crt/Variant.h index 1ac603fb8..0a7fd6db6 100644 --- a/include/aws/crt/Variant.h +++ b/include/aws/crt/Variant.h @@ -5,6 +5,7 @@ */ #include +#include #include #include @@ -17,6 +18,7 @@ namespace Aws { namespace VariantDetail { + // TODO Pass values instead of refs. template constexpr const T &ConstExprMax(const T &a, const T &b) { return (a < b) ? b : a; @@ -79,7 +81,7 @@ namespace Aws return std::is_same::value || ContainsType(); } - // a case when the template parameter pack is empty (i.e. Variant<>) + // a case when the template parameter pack is empty (i.e. VariantImpl<>) template constexpr bool ContainsType() { return false; @@ -116,477 +118,681 @@ namespace Aws }; } // 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; + /* Depending on the Variant types, this struct either deletes special move members or defaults them. */ + template class MovableVariant; - public: - using IndexT = VariantDetail::Index::VariantIndex; - static constexpr std::size_t AlternativeCount = sizeof...(Ts); - - Variant() + template <> class MovableVariant { - using FirstAlternative = typename ThisVariantAlternative<0>::type; - new (m_storage) FirstAlternative(); - m_index = 0; - } - - Variant(const Variant &other) + public: + MovableVariant() = default; + MovableVariant(const MovableVariant &) = default; + MovableVariant &operator=(const MovableVariant &) = default; + MovableVariant(MovableVariant &&other) = default; + MovableVariant &operator=(MovableVariant &&other) = default; + }; + template <> class MovableVariant { - AWS_FATAL_ASSERT(other.m_index != -1); - m_index = other.m_index; - VisitorUtil<0, Ts...>::VisitBinary(this, other, CopyMoveConstructor()); - } + public: + MovableVariant() = default; + MovableVariant(const MovableVariant &) = default; + MovableVariant &operator=(const MovableVariant &) = default; + MovableVariant(MovableVariant &&) = delete; + MovableVariant &operator=(MovableVariant &&) = delete; + }; - Variant(Variant &&other) - { - AWS_FATAL_ASSERT(other.m_index != -1); - m_index = other.m_index; - VisitorUtil<0, Ts...>::VisitBinary(this, std::move(other), CopyMoveConstructor()); - } + /* Depending on the Variant types, this struct either deletes special copy members or defaults them. */ + template class CopyableVariant; - template = 1> Variant(const T &val) + template <> class CopyableVariant { - 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); - } + public: + CopyableVariant() = default; + CopyableVariant(const CopyableVariant &other) = default; + CopyableVariant &operator=(const CopyableVariant &other) = default; + CopyableVariant(CopyableVariant &&) = default; + CopyableVariant &operator=(CopyableVariant &&) = default; + }; - template = 1> Variant(T &&val) + template <> class CopyableVariant { - 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); - } + public: + CopyableVariant() = default; - // 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); - } + CopyableVariant(const CopyableVariant &) = delete; + CopyableVariant &operator=(const CopyableVariant &) = delete; - 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; - } + CopyableVariant(CopyableVariant &&) = default; + CopyableVariant &operator=(CopyableVariant &&) = default; + }; - 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; - } + template class VariantAlternative; - /* emplace */ - template = 1> - T &emplace(Args &&...args) + template class VariantImpl { - 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; - } + private: + template using ThisVariantAlternative = VariantAlternative; - template - auto emplace(Args &&...args) -> typename ThisVariantAlternative::type & - { - static_assert(Index < AlternativeCount, "Unknown alternative index to emplace"); - using AlternativeT = typename ThisVariantAlternative::type; + template + using EnableIfOtherIsThisVariantAlternative = typename std::enable_if< + VariantDetail::Checker::HasType::type, Ts...>::value, + int>::type; - return emplace(std::forward(args)...); - } + public: + using FirstAlternative = typename ThisVariantAlternative<0>::type; - template = 1> bool holds_alternative() const - { - AWS_ASSERT(m_index != -1); - return m_index == VariantDetail::Index::GetIndexOf(); - } + static constexpr bool isFirstAlternativeNothrowDefaultConstructible = + std::is_nothrow_default_constructible::value; - /* non-const get */ - template = 1> T &get() - { - AWS_FATAL_ASSERT(holds_alternative()); - T *value = reinterpret_cast(m_storage); - return *value; - } + static constexpr bool isFirstAlternativeDefaultConstructible = + std::is_nothrow_constructible::value; - template = 1> T *get_if() - { - if (holds_alternative()) - { - T *value = reinterpret_cast(m_storage); - return value; - } - else + using IndexT = VariantDetail::Index::VariantIndex; + static constexpr std::size_t AlternativeCount = sizeof...(Ts); + + VariantImpl() noexcept(isFirstAlternativeNothrowDefaultConstructible) { - return nullptr; + new (m_storage) FirstAlternative(); + m_index = 0; } - } - 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()) + VariantImpl(const VariantImpl &other) { - T *value = reinterpret_cast(m_storage); - return value; + AWS_FATAL_ASSERT(other.m_index != -1); + m_index = other.m_index; + VisitorUtil<0, Ts...>::VisitBinary(this, other, CopyMoveConstructor()); } - else + + VariantImpl(VariantImpl &&other) { - return nullptr; + AWS_FATAL_ASSERT(other.m_index != -1); + m_index = other.m_index; + VisitorUtil<0, Ts...>::VisitBinary(this, std::move(other), CopyMoveConstructor()); } - } - - 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 = 1> + VariantImpl(const T &val) noexcept( + std::is_nothrow_constructible::type, decltype(val)>::value) + { + 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 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; + template = 1> + VariantImpl(T &&val) noexcept( + std::is_nothrow_constructible::type, decltype(val)>::value) + { + 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); } - else + + // An overload to initialize with an Alternative T in-place + template explicit VariantImpl(Aws::Crt::InPlaceTypeT, Args &&...args) { - return nullptr; - } - } + 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."); - template - using ConstRawAlternativePointerT = typename std::add_pointer< - typename std::add_const::type>::type>::type; + using PlainT = typename std::decay::type; + new (m_storage) PlainT(std::forward(args)...); + m_index = VariantDetail::Index::GetIndexOf(); + AWS_ASSERT(m_index != -1); + } - template auto get_if() const -> ConstRawAlternativePointerT - { - static_assert(Index < AlternativeCount, "Unknown alternative index to get"); - if (holds_alternative()) + VariantImpl &operator=(const VariantImpl &other) { - using AlternativePtrT = ConstRawAlternativePointerT; - AlternativePtrT value = reinterpret_cast(m_storage); - return value; + 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; } - else + + VariantImpl &operator=(VariantImpl &&other) { - return nullptr; + 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; } - } - std::size_t index() const { return m_index; } + /* 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."); - ~Variant() { Destroy(); } + Destroy(); - template void Visit(VisitorT &&visitor) - { - return VisitorUtil<0, Ts...>::Visit(this, std::forward(visitor)); - } + using PlainT = typename std::decay::type; + new (m_storage) PlainT(std::forward(args)...); + m_index = VariantDetail::Index::GetIndexOf(); + AWS_ASSERT(m_index != -1); - private: - static constexpr std::size_t STORAGE_SIZE = VariantDetail::ParameterPackSize::GetMaxSizeOf(); + T *value = reinterpret_cast(m_storage); + return *value; + } - 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 + auto emplace(Args &&...args) -> typename ThisVariantAlternative::type & + { + static_assert(Index < AlternativeCount, "Unknown alternative index to emplace"); + using AlternativeT = typename ThisVariantAlternative::type; - template constexpr bool holds_alternative() const { return Index == m_index; } + return emplace(std::forward(args)...); + } - struct Destroyer - { - template void operator()(AlternativeT &&value) const + template = 1> bool holds_alternative() const { - (void)value; - using PlaintT = typename std::remove_reference::type; - value.~PlaintT(); + AWS_ASSERT(m_index != -1); + return m_index == VariantDetail::Index::GetIndexOf(); } - }; - void Destroy() - { - AWS_FATAL_ASSERT(m_index != -1); - Visit(Destroyer()); - - m_index = -1; - } - - struct CopyMoveConstructor - { - template void operator()(AlternativeT &&value, AlternativeT &&other) const + /* non-const get */ + template = 1> T &get() { - using PlaintT = typename std::remove_reference::type; - new (&value) PlaintT(std::move(other)); + AWS_FATAL_ASSERT(holds_alternative()); + T *value = reinterpret_cast(m_storage); + return *value; } - template - void operator()(AlternativeT &&value, ConstAlternativeT &other) const + template = 1> T *get_if() { - 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); + if (holds_alternative()) + { + T *value = reinterpret_cast(m_storage); + return value; + } + else + { + return nullptr; + } } - }; - struct CopyMoveAssigner - { - template void operator()(AlternativeT &&value, AlternativeT &&other) const + template auto get() -> typename ThisVariantAlternative::type & { - value = std::move(other); + 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; } - template - void operator()(AlternativeT &&value, ConstAlternativeT &other) const + /* const get */ + template = 1> const T &get() 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; + AWS_FATAL_ASSERT(holds_alternative()); + const T *value = reinterpret_cast(m_storage); + return *value; } - }; - - template struct VisitorUtil; - template - struct VisitorUtil - { - template static void Visit(Variant *pThis, VisitorStruct &&visitor) + template = 1> const T *get_if() const { - static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); - - if (Index == pThis->m_index) + if (holds_alternative()) { - using AlternativeT = typename ThisVariantAlternative::type; - AlternativeT *value = reinterpret_cast(pThis->m_storage); - visitor(*value); + T *value = reinterpret_cast(m_storage); + return value; } else { - VisitorUtil(Index + 1), Second, Rest...>::Visit( - pThis, std::forward(visitor)); + return nullptr; } } - template - static void VisitBinary(Variant *pThis, Variant &&other, VisitorStruct &&visitor) + template auto get() const -> const typename ThisVariantAlternative::type & { - static_assert(Index < AlternativeCount, "Attempting to visit unknown Index 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, VariantImpl()>*/ + template + using RawAlternativePointerT = + typename std::add_pointer::type>::type; - if (Index == pThis->m_index) + template auto get_if() -> RawAlternativePointerT + { + static_assert(Index < AlternativeCount, "Unknown alternative index to get"); + if (holds_alternative()) { - using AlternativeT = typename ThisVariantAlternative::type; - AlternativeT *value = reinterpret_cast(pThis->m_storage); - visitor(*value, other.get()); + using AlternativePtrT = RawAlternativePointerT; + AlternativePtrT value = reinterpret_cast(m_storage); + return value; } else { - VisitorUtil(Index + 1), Second, Rest...>::VisitBinary( - pThis, std::forward>(other), std::forward(visitor)); + return nullptr; } } - template - static void VisitBinary(Variant *pThis, const Variant &other, VisitorStruct &&visitor) - { - static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); + template + using ConstRawAlternativePointerT = typename std::add_pointer< + typename std::add_const::type>::type>::type; - if (Index == pThis->m_index) + template auto get_if() const -> ConstRawAlternativePointerT + { + static_assert(Index < AlternativeCount, "Unknown alternative index to get"); + if (holds_alternative()) { - using AlternativeT = typename ThisVariantAlternative::type; - AlternativeT *value = reinterpret_cast(pThis->m_storage); - const AlternativeT &otherValue = other.get(); - visitor(*value, otherValue); + using AlternativePtrT = ConstRawAlternativePointerT; + AlternativePtrT value = reinterpret_cast(m_storage); + return value; } else { - VisitorUtil(Index + 1), Second, Rest...>::VisitBinary( - pThis, other, std::forward(visitor)); + return nullptr; } } - }; - template struct VisitorUtil - { - template static void Visit(Variant *pThis, VisitorStruct &&visitor) + std::size_t index() const { return m_index; } + + ~VariantImpl() { Destroy(); } + + template void Visit(VisitorT &&visitor) { - static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); + 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) */ - if (Index == pThis->m_index) + template constexpr bool holds_alternative() const { return Index == m_index; } + + struct Destroyer + { + template void operator()(AlternativeT &&value) const { - using AlternativeT = typename ThisVariantAlternative::type; - AlternativeT *value = reinterpret_cast(pThis->m_storage); - visitor(*value); + (void)value; + using PlaintT = typename std::remove_reference::type; + value.~PlaintT(); } - else + }; + + void Destroy() + { + AWS_FATAL_ASSERT(m_index != -1); + Visit(Destroyer()); + + m_index = -1; + } + + struct CopyMoveConstructor + { + template void operator()(AlternativeT &&value, AlternativeT &&other) const { - AWS_FATAL_ASSERT(!"Unknown variant alternative to visit!"); + using PlaintT = typename std::remove_reference::type; + new (&value) PlaintT(std::move(other)); } - } - template - static void VisitBinary(Variant *pThis, Variant &&other, VisitorStruct &&visitor) + 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 { - static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); + template void operator()(AlternativeT &&value, AlternativeT &&other) const + { + value = std::move(other); + } - if (Index == pThis->m_index) + template + void operator()(AlternativeT &&value, ConstAlternativeT &other) const { - using AlternativeT = typename ThisVariantAlternative::type; - AlternativeT *value = reinterpret_cast(pThis->m_storage); - visitor(*value, other.get()); + 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; } - else + }; + + template struct VisitorUtil; + + template + struct VisitorUtil + { + template static void Visit(VariantImpl *pThis, VisitorStruct &&visitor) { - AWS_FATAL_ASSERT(!"Unknown variant alternative to visit!"); + 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(Index + 1), Second, Rest...>::Visit( + pThis, std::forward(visitor)); + } } - } - template - static void VisitBinary(Variant *pThis, const Variant &other, VisitorStruct &&visitor) + template + static void VisitBinary( + VariantImpl *pThis, + VariantImpl &&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(Index + 1), Second, Rest...>::VisitBinary( + pThis, std::forward>(other), std::forward(visitor)); + } + } + + template + static void VisitBinary( + VariantImpl *pThis, + const VariantImpl &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(Index + 1), Second, Rest...>::VisitBinary( + pThis, other, std::forward(visitor)); + } + } + }; + + template struct VisitorUtil { - static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); + template static void Visit(VariantImpl *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!"); + } + } - if (Index == pThis->m_index) + template + static void VisitBinary( + VariantImpl *pThis, + VariantImpl &&other, + VisitorStruct &&visitor) { - using AlternativeT = typename ThisVariantAlternative::type; - AlternativeT *value = reinterpret_cast(pThis->m_storage); - const AlternativeT &otherValue = other.get(); - visitor(*value, otherValue); + 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!"); + } } - else + + template + static void VisitBinary( + VariantImpl *pThis, + const VariantImpl &other, + VisitorStruct &&visitor) { - AWS_FATAL_ASSERT(!"Unknown variant alternative to visit!"); + 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 VariantImpl &) {} + + VariantAlternative(const VariantImpl *) {} + }; + + template class VariantSize + { + constexpr static const std::size_t Value = T::AlternativeCount; }; - }; - /* Helper template to get an actual type from an Index */ - template class VariantAlternative + } // namespace VariantDetail + + /** + * Custom implementation of a Variant type. std::variant requires C++17. + * @tparam Ts Types of the variant value. + */ + template + class Variant : public VariantDetail::MovableVariant...>::value>, + public VariantDetail::CopyableVariant...>::value> { + /* Copyability and Movability depend only on constructors (copy and move correspondingly) of the + * underlying types. This means that a class with move constructor but with no move assignment operator + * might cause compilation issues. If such a type ever needs to be supported, additional base class needs + * to be introduced: MoveAssignable. For now, it'll be overengineering. + * The same applies to copy constructor and copy assignment operator. */ + + private: + template using ThisVariantAlternative = VariantDetail::VariantAlternative; + + template + using EnableIfOtherIsThisVariantAlternative = typename std:: + enable_if::type, Ts...>::value, int>::type; + + static constexpr bool isFirstAlternativeNothrowDefaultConstructible = + VariantDetail::VariantImpl::isFirstAlternativeNothrowDefaultConstructible; + public: - // uses std::tuple as a helper struct to provide index-based access of a parameter pack - using type = typename std::tuple_element>::type; + using IndexT = VariantDetail::Index::VariantIndex; + static constexpr std::size_t AlternativeCount = sizeof...(Ts); - VariantAlternative(const Variant &) {} + template < + typename T = typename VariantDetail::VariantImpl::FirstAlternative, + typename std::enable_if::value, bool>::type = true> + Variant() noexcept(isFirstAlternativeNothrowDefaultConstructible) + { + } - VariantAlternative(const Variant *) {} - }; + template < + typename T = typename VariantDetail::VariantImpl::FirstAlternative, + typename std::enable_if::value, bool>::type = true> + Variant() = delete; - template class VariantSize - { - constexpr static const std::size_t Value = T::AlternativeCount; + template = 1> + Variant(const T &val) noexcept( + std::is_nothrow_constructible::type, decltype(val)>::value) + : m_variant(val) + { + } + + template = 1> + Variant(T &&val) noexcept(std::is_nothrow_constructible::type, decltype(val)>::value) + : m_variant(std::forward(val)) + { + } + + template + explicit Variant(Aws::Crt::InPlaceTypeT ipt, Args &&...args) + : m_variant(ipt, std::forward(args)...) + { + } + + template = 1> + T &emplace(Args &&...args) + { + return m_variant.template emplace(std::forward(args)...); + } + + template + auto emplace(Args &&...args) -> typename ThisVariantAlternative::type & + { + return m_variant.template emplace(std::forward(args)...); + } + + template = 1> bool holds_alternative() const + { + return m_variant.template holds_alternative(); + } + + template = 1> T &get() + { + return m_variant.template get(); + } + + template = 1> T *get_if() + { + return m_variant.template get_if(); + } + + template auto get() -> typename ThisVariantAlternative::type & + { + return m_variant.template get(); + } + + template = 1> const T &get() const + { + return m_variant.template get(); + } + + template = 1> const T *get_if() const + { + return m_variant.template get_if(); + } + + template auto get() const -> const typename ThisVariantAlternative::type & + { + return m_variant.template get(); + } + + template + using RawAlternativePointerT = + typename std::add_pointer::type>::type; + + template auto get_if() -> RawAlternativePointerT + { + return m_variant.template get_if(); + } + + template + using ConstRawAlternativePointerT = typename std::add_pointer< + typename std::add_const::type>::type>::type; + + template auto get_if() const -> ConstRawAlternativePointerT + { + return m_variant.template get_if(); + } + + std::size_t index() const { return m_variant.index(); } + + template void Visit(VisitorT &&visitor) + { + m_variant.Visit(std::forward(visitor)); + } + + private: + VariantDetail::VariantImpl m_variant; }; + } // namespace Crt } // namespace Aws diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index df86e33de..fc6743b4a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -105,7 +105,13 @@ add_test_case(OptionalEmplace) add_test_case(OptionalCopyAndMoveSemantics) add_test_case(VariantCompiles) add_test_case(VariantConstructor) -add_test_case(VariantOperatorEquals) +add_test_case(VariantAssignmentOperator) +add_test_case(VariantWithMoveOnlyUnderlyingType) +add_test_case(VariantWithCopyOnlyUnderlyingType) +add_test_case(VariantWithNoDefaultConstructibleUnderlyingType) +add_test_case(VariantExceptionSafety_DefaultConstructor) +add_test_case(VariantExceptionSafety_MoveConstructor) +add_test_case(VariantExceptionSafety_CopyConstructor) add_test_case(VariantEmplace) add_test_case(VariantVisitor) add_test_case(StreamTestCreateDestroyWrapper) diff --git a/tests/VariantTest.cpp b/tests/VariantTest.cpp index 684d0c50e..80e6a7c8a 100644 --- a/tests/VariantTest.cpp +++ b/tests/VariantTest.cpp @@ -6,6 +6,12 @@ #include #include +#if defined(_WIN32) +# define AWS_VARIANTTEST_WINDOWS_API __declspec(dllexport) +#else +# define AWS_VARIANTTEST_WINDOWS_API +#endif + const char *s_variant_test_str = "This is a string, that should be long enough to avoid small string optimizations"; static int s_VariantBasicOperandsCompile(struct aws_allocator *allocator, void *ctx) @@ -170,7 +176,7 @@ static int s_VariantConstructor(struct aws_allocator *allocator, void *ctx) AWS_TEST_CASE(VariantConstructor, s_VariantConstructor) -static int s_VariantOperatorEquals(struct aws_allocator *allocator, void *ctx) +static int s_VariantAssignmentOperator(struct aws_allocator *allocator, void *ctx) { (void)ctx; { @@ -214,7 +220,192 @@ static int s_VariantOperatorEquals(struct aws_allocator *allocator, void *ctx) return AWS_OP_SUCCESS; } -AWS_TEST_CASE(VariantOperatorEquals, s_VariantOperatorEquals) +AWS_TEST_CASE(VariantAssignmentOperator, s_VariantAssignmentOperator) + +// Test Variant with move-only underlying type. +// If it compiles, it's considered success. +static int s_VariantWithMoveOnlyUnderlyingType(struct aws_allocator *allocator, void *ctx) +{ + (void)ctx; + + Aws::Crt::ApiHandle apiHandle(allocator); + + struct MoveOnlyTestType + { + MoveOnlyTestType() = default; + + MoveOnlyTestType(MoveOnlyTestType &&) = default; + MoveOnlyTestType &operator=(MoveOnlyTestType &&) = default; + + MoveOnlyTestType(const MoveOnlyTestType &) = delete; + MoveOnlyTestType &operator=(const MoveOnlyTestType &) = delete; + }; + + using MoveOnlyVariant = Aws::Crt::Variant; + + /* Regression test. + * The __declspec(dllexport) directive exports class member function on Windows platform. We enable it when + * building shared libraries. In the past, this directive caused msvc to generate special copy members for classes + * containing Crt::Variant with move-only underlying types, which led to compile-time errors. */ + struct AWS_VARIANTTEST_WINDOWS_API MoveOnlyVariantTestResult + { + MoveOnlyVariant m_result; + }; + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(VariantWithMoveOnlyUnderlyingType, s_VariantWithMoveOnlyUnderlyingType) + +// Test Variant with copy-only underlying type. +// If it compiles, it's considered success. +static int s_VariantWithCopyOnlyUnderlyingType(struct aws_allocator *allocator, void *ctx) +{ + (void)ctx; + + Aws::Crt::ApiHandle apiHandle(allocator); + + struct CopyOnlyTestType + { + CopyOnlyTestType() = default; + + CopyOnlyTestType(CopyOnlyTestType &&) = default; + CopyOnlyTestType &operator=(CopyOnlyTestType &&) = default; + + CopyOnlyTestType(const CopyOnlyTestType &) = delete; + CopyOnlyTestType &operator=(const CopyOnlyTestType &) = delete; + }; + + using CopyOnlyVariant = Aws::Crt::Variant; + + /* Regression test. + * The __declspec(dllexport) directive exports class member function on Windows platform. We enable it when + * building shared libraries. In the past, this directive caused msvc to generate special copy members for classes + * containing Crt::Variant with copy-only underlying types, which led to compile-time errors. */ + struct AWS_VARIANTTEST_WINDOWS_API CopyOnlyVariantTestResult + { + CopyOnlyVariant m_result; + }; + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(VariantWithCopyOnlyUnderlyingType, s_VariantWithCopyOnlyUnderlyingType) + +// Test Variant with underlying type without default constructor. +// If it compiles, it's considered success. +static int s_VariantWithNoDefaultConstructibleUnderlyingType(struct aws_allocator *allocator, void *ctx) +{ + (void)ctx; + + Aws::Crt::ApiHandle apiHandle(allocator); + + struct NoDefaultConstructibleTestType + { + explicit NoDefaultConstructibleTestType(int) {} + NoDefaultConstructibleTestType() = delete; + }; + + using NoDefaultConstructibleVariant = Aws::Crt::Variant; + + struct AWS_VARIANTTEST_WINDOWS_API NoDefaultConstructibleVariantTestResult + { + NoDefaultConstructibleVariant m_result; + }; + + NoDefaultConstructibleTestType testType(1); + NoDefaultConstructibleVariant variant(testType); + NoDefaultConstructibleVariantTestResult testResult{variant}; + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(VariantWithNoDefaultConstructibleUnderlyingType, s_VariantWithNoDefaultConstructibleUnderlyingType) + +static int s_VariantExceptionSafety_DefaultConstructor(struct aws_allocator *allocator, void *ctx) +{ + (void)ctx; + + Aws::Crt::ApiHandle apiHandle(allocator); + + struct NothrowDefaultConstructibleTestType + { + NothrowDefaultConstructibleTestType() noexcept {} + NothrowDefaultConstructibleTestType(const NothrowDefaultConstructibleTestType &) noexcept(false) {} + NothrowDefaultConstructibleTestType(NothrowDefaultConstructibleTestType &&) noexcept(false) {} + }; + + using NothrowDefaultConstructibleVariant = Aws::Crt::Variant; + + ASSERT_TRUE(std::is_nothrow_constructible::value); + ASSERT_FALSE(( + std::is_nothrow_constructible:: + value)); + ASSERT_FALSE( + (std::is_nothrow_constructible:: + value)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(VariantExceptionSafety_DefaultConstructor, s_VariantExceptionSafety_DefaultConstructor) + +static int s_VariantExceptionSafety_MoveConstructor(struct aws_allocator *allocator, void *ctx) +{ + (void)ctx; + + Aws::Crt::ApiHandle apiHandle(allocator); + + struct NothrowMoveConstructibleTestType + { + NothrowMoveConstructibleTestType(NothrowMoveConstructibleTestType &&) noexcept {} + + NothrowMoveConstructibleTestType() noexcept(false) {} + NothrowMoveConstructibleTestType(const NothrowMoveConstructibleTestType &) noexcept(false) {} + }; + + using NothrowMoveConstructibleVariant = Aws::Crt::Variant; + + ASSERT_TRUE( + (std::is_nothrow_constructible::value)); + ASSERT_FALSE(std::is_nothrow_constructible::value); + ASSERT_FALSE( + (std::is_nothrow_constructible:: + value)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(VariantExceptionSafety_MoveConstructor, s_VariantExceptionSafety_MoveConstructor) + +static int s_VariantExceptionSafety_CopyConstructor(struct aws_allocator *allocator, void *ctx) +{ + (void)ctx; + + Aws::Crt::ApiHandle apiHandle(allocator); + + struct NothrowCopyConstructibleTestType + { + NothrowCopyConstructibleTestType(const NothrowCopyConstructibleTestType &) noexcept {} + + NothrowCopyConstructibleTestType() noexcept(false) {} + NothrowCopyConstructibleTestType(NothrowCopyConstructibleTestType &&) noexcept(false) {} + }; + + using NothrowCopyConstructibleVariant = Aws::Crt::Variant; + + ASSERT_TRUE( + (std::is_nothrow_constructible:: + value)); + + ASSERT_FALSE(std::is_nothrow_constructible::value); + ASSERT_FALSE( + (std::is_nothrow_constructible::value)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(VariantExceptionSafety_CopyConstructor, s_VariantExceptionSafety_CopyConstructor) struct TestStringOnlyVisitor { @@ -364,4 +555,4 @@ static int s_VariantVisitor(struct aws_allocator *allocator, void *ctx) return AWS_OP_SUCCESS; } -AWS_TEST_CASE(VariantVisitor, s_VariantVisitor) \ No newline at end of file +AWS_TEST_CASE(VariantVisitor, s_VariantVisitor)