diff --git a/include/aws/crt/Utility.h b/include/aws/crt/Utility.h index 07733f26c..00aab4455 100644 --- a/include/aws/crt/Utility.h +++ b/include/aws/crt/Utility.h @@ -17,5 +17,15 @@ namespace Aws }; static constexpr InPlaceT InPlace{}; + template struct InPlaceTypeT + { + explicit InPlaceTypeT() = default; + }; + /** Variable templates are only available since C++14 + * Use a dummy object "Aws::Crt::InPlaceTypeT() in-place instead in C++11"*/ +#if defined(__cplusplus) && __cplusplus > 201103L // + template static constexpr InPlaceTypeT InPlaceType{}; +#endif + } // namespace Crt } // namespace Aws diff --git a/include/aws/crt/Variant.h b/include/aws/crt/Variant.h index ed2886eb0..9b422ed0d 100644 --- a/include/aws/crt/Variant.h +++ b/include/aws/crt/Variant.h @@ -5,6 +5,7 @@ */ #include +#include #include #include @@ -83,7 +84,7 @@ namespace Aws static const bool value = ContainsType(); }; } // namespace Checker -#if defined(DEBUG_BUILD) +#if defined(AWS_CRT_ENABLE_VARIANT_DEBUG) namespace VariantDebug { template class VariantDebugBrowser @@ -93,7 +94,7 @@ namespace Aws std::tuple::type...> as_tuple; private: - template + template void InitTuple(char *storage) { First *value = reinterpret_cast(storage); @@ -101,43 +102,35 @@ namespace Aws InitTuple(storage); } - template void InitTuple(char *storage) + template void InitTuple(char *storage) { Last *value = reinterpret_cast(storage); std::get(as_tuple) = value; } }; } // namespace VariantDebug -#endif /* defined(DEBUG_BUILD) */ +#endif /* defined(AWS_CRT_ENABLE_VARIANT_DEBUG) */ } // namespace VariantDetail template class VariantAlternative; - template struct VariantInPlaceInitT - { - explicit VariantInPlaceInitT() = default; - }; - /** * Custom implementation of a Variant type. std::variant requires C++17 * @tparam Ts types of the variant value */ template class Variant { - public: - static constexpr std::size_t AlternativeCount = sizeof...(Ts); - + private: template using ThisVariantAlternative = VariantAlternative; - using ThisVariantT = Variant; - template - using EnableIfOtherIsSameVariantType = - typename std::enable_if, ThisVariantT>::value, int>::type; - 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; @@ -147,19 +140,19 @@ namespace Aws Variant(const Variant &other) { - auto otherIdx = other.m_index; - if (otherIdx != -1) + m_index = other.m_index; + if (other.m_index != -1) { - CopyConstructUtil<0, Ts...>(other); + VisitorUtil<0, Ts...>::VisitBinary(this, other, CopyMoveConstructor()); } } Variant(Variant &&other) { - auto otherIdx = other.m_index; - if (otherIdx != -1) + m_index = other.m_index; + if (other.m_index != -1) { - MoveConstructUtil<0, Ts...>(std::move(other)); + VisitorUtil<0, Ts...>::VisitBinary(this, std::move(other), CopyMoveConstructor()); } } @@ -169,12 +162,12 @@ namespace Aws VariantDetail::Checker::HasType::type, Ts...>::value, "This variant does not have such alternative T."); static_assert( - sizeof(T) >= STORAGE_SIZE, + 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(); + m_index = VariantDetail::Index::GetIndexOf(); AWS_ASSERT(m_index != -1); } @@ -189,12 +182,12 @@ namespace Aws using PlainT = typename std::decay::type; new (m_storage) PlainT(std::forward(val)); - m_index = VariantDetail::Index::GetIndexOf(); + m_index = VariantDetail::Index::GetIndexOf(); AWS_ASSERT(m_index != -1); } // An overload to initialize with an Alternative T in-place - template explicit Variant(VariantInPlaceInitT, Args &&...args) + template explicit Variant(Aws::Crt::InPlaceTypeT, Args &&...args) { static_assert( VariantDetail::Checker::HasType::type, Ts...>::value, @@ -205,22 +198,24 @@ namespace Aws using PlainT = typename std::decay::type; new (m_storage) PlainT(std::forward(args)...); - m_index = VariantDetail::Index::GetIndexOf(); + m_index = VariantDetail::Index::GetIndexOf(); AWS_ASSERT(m_index != -1); } Variant &operator=(const Variant &other) { - if (this != &other) + AWS_ASSERT(other.m_index != -1); + if (this != &other && other.m_index != -1) { if (m_index != other.m_index) { Destroy(); - CopyConstructUtil<0, Ts...>(other); + m_index = other.m_index; + VisitorUtil<0, Ts...>::VisitBinary(this, other, CopyMoveConstructor()); } else { - CopyAssignUtil<0, Ts...>(other); + VisitorUtil<0, Ts...>::VisitBinary(this, other, CopyMoveAssigner()); } } return *this; @@ -228,16 +223,18 @@ namespace Aws Variant &operator=(Variant &&other) { - if (this != &other) + AWS_ASSERT(other.m_index != -1); + if (this != &other && other.m_index != -1) { if (m_index != other.m_index) { Destroy(); - MoveConstructUtil<0, Ts...>(std::move(other)); + m_index = other.m_index; + VisitorUtil<0, Ts...>::VisitBinary(this, std::move(other), CopyMoveConstructor()); } else { - MoveAssignUtil<0, Ts...>(std::move(other)); + VisitorUtil<0, Ts...>::VisitBinary(this, std::move(other), CopyMoveAssigner()); }; } return *this; @@ -258,7 +255,7 @@ namespace Aws using PlainT = typename std::decay::type; new (m_storage) PlainT(std::forward(args)...); - m_index = VariantDetail::Index::GetIndexOf(); + m_index = VariantDetail::Index::GetIndexOf(); AWS_ASSERT(m_index != -1); T *value = reinterpret_cast(m_storage); @@ -271,7 +268,7 @@ namespace Aws static_assert(Index < AlternativeCount, "Unknown alternative index to emplace"); using AlternativeT = typename ThisVariantAlternative::type; - return emplace(args...); + return emplace(std::forward(args)...); } template = 1> bool holds_alternative() const @@ -280,13 +277,10 @@ namespace Aws return m_index == VariantDetail::Index::GetIndexOf(); } - template constexpr bool holds_alternative() const { return Index < AlternativeCount; } - - template bool holds_alternative() const { return false; } - /* non-const get */ template = 1> T &get() { + AWS_FATAL_ASSERT(holds_alternative()); T *value = reinterpret_cast(m_storage); return *value; } @@ -307,7 +301,7 @@ namespace Aws template auto get() -> typename ThisVariantAlternative::type & { static_assert(Index < AlternativeCount, "Unknown alternative index to get"); - AWS_ASSERT(Index == m_index); + AWS_FATAL_ASSERT(holds_alternative()); using AlternativeT = typename ThisVariantAlternative::type; AlternativeT *ret = reinterpret_cast(m_storage); return *ret; @@ -316,6 +310,7 @@ namespace Aws /* const get */ template = 1> const T &get() const { + AWS_FATAL_ASSERT(holds_alternative()); const T *value = reinterpret_cast(m_storage); return *value; } @@ -387,192 +382,190 @@ namespace Aws ~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]; - VariantDetail::Index::VariantIndex m_index = -1; -#if defined(DEBUG_BUILD) + IndexT m_index = -1; +#if defined(AWS_CRT_ENABLE_VARIANT_DEBUG) VariantDetail::VariantDebug::VariantDebugBrowser browser = m_storage; -#endif /* defined(DEBUG_BUILD) */ - - void Destroy() - { - if (m_index != -1) - { - DestroyUtil(m_index); - } - m_index = -1; - } +#endif /* defined(AWS_CRT_ENABLE_VARIANT_DEBUG) */ - template - void DestroyUtil(const std::size_t requestedToDestroy, std::size_t curIndex = 0) - { - if (curIndex == requestedToDestroy) - { - First *value = reinterpret_cast(m_storage); - value->~First(); - } - else - { - DestroyUtil(requestedToDestroy, ++curIndex); - } - } + template constexpr bool holds_alternative() const { return Index == m_index; } - template void DestroyUtil(const std::size_t requestedToDestroy, std::size_t curIndex = 0) + struct Destroyer { - AWS_ASSERT( - requestedToDestroy == curIndex); // attempting to destroy unknown alternative type in a Variant - if (curIndex == requestedToDestroy) + template void operator()(AlternativeT &&value) const { - Last *value = reinterpret_cast(m_storage); - value->~Last(); + using PlaintT = typename std::remove_reference::type; + value.~PlaintT(); } - } + }; - template - void CopyConstructUtil(const Variant &other) + void Destroy() { - static_assert(Index < AlternativeCount, "Attempting to construct unknown Index Type"); + AWS_FATAL_ASSERT(m_index != -1); + Visit(Destroyer()); - if (Index == other.m_index) - { - using AlternativeT = typename ThisVariantAlternative::type; - new (m_storage) AlternativeT(other.get()); - m_index = Index; - AWS_ASSERT(m_index != -1); - } - else - { - CopyConstructUtil(other); - } + m_index = -1; } - template void CopyConstructUtil(const Variant &other) + struct CopyMoveConstructor { - static_assert(Index < AlternativeCount, "Attempting to construct unknown Index Type"); - - if (Index == other.m_index) + template void operator()(AlternativeT &&value, AlternativeT &&other) const { - using AlternativeT = typename ThisVariantAlternative::type; - new (m_storage) AlternativeT(other.get()); - m_index = Index; - AWS_ASSERT(m_index != -1); + using PlaintT = typename std::remove_reference::type; + new (&value) PlaintT(std::move(other)); } - else - { - AWS_ASSERT(!"Unknown variant alternative type in other!"); - } - } - - template - void MoveConstructUtil(Variant &&other) - { - static_assert(Index < AlternativeCount, "Attempting to construct unknown Index Type"); - if (Index == other.m_index) - { - using AlternativeT = typename ThisVariantAlternative::type; - new (m_storage) AlternativeT(std::move(other.get())); - m_index = Index; - AWS_ASSERT(m_index != -1); - } - else + template + void operator()(AlternativeT &&value, ConstAlternativeT &other) const { - MoveConstructUtil(std::move(other)); + 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); } - } + }; - template void MoveConstructUtil(Variant &&other) + struct CopyMoveAssigner { - static_assert(Index < AlternativeCount, "Attempting to construct unknown Index Type"); - - if (Index == other.m_index) + template void operator()(AlternativeT &&value, AlternativeT &&other) const { - using AlternativeT = typename ThisVariantAlternative::type; - new (m_storage) AlternativeT(std::move(other.get())); - m_index = Index; - AWS_ASSERT(m_index != -1); + value = std::move(other); } - else + + template + void operator()(AlternativeT &&value, ConstAlternativeT &other) const { - AWS_ASSERT(!"Unknown variant alternative type in other!"); + using PlaintT = typename std::remove_reference::type; + using PlaintOtherT = + typename std::remove_const::type>::type; + static_assert(std::is_same::value, "Incompatible types"); + + value = std::move(other); } - } + }; - template - void CopyAssignUtil(const Variant &other) - { - static_assert(Index < AlternativeCount, "Attempting to construct unknown Index Type"); + template struct VisitorUtil; - if (Index == other.m_index) - { - using AlternativeT = typename ThisVariantAlternative::type; - AlternativeT *value = reinterpret_cast(m_storage); - *value = other.get(); - m_index = Index; - AWS_ASSERT(m_index != -1); - } - else + template + struct VisitorUtil + { + template static void Visit(Variant *pThis, VisitorStruct &&visitor) { - CopyAssignUtil(other); - } - } + static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); - template void CopyAssignUtil(const Variant &other) - { - static_assert(Index < AlternativeCount, "Attempting to construct 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)); + } + } - if (Index == other.m_index) + template + static void VisitBinary(Variant *pThis, Variant &&other, VisitorStruct &&visitor) { - using AlternativeT = typename ThisVariantAlternative::type; - AlternativeT *value = reinterpret_cast(m_storage); - *value = other.get(); - m_index = Index; - AWS_ASSERT(m_index != -1); + 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)); + } } - else + + template + static void VisitBinary(Variant *pThis, const Variant &other, VisitorStruct &&visitor) { - AWS_ASSERT(!"Unknown variant alternative type in other!"); + 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 - void MoveAssignUtil(Variant &&other) + template struct VisitorUtil { - static_assert(Index < AlternativeCount, "Attempting to construct unknown Index Type"); - - if (Index == other.m_index) - { - using AlternativeT = typename ThisVariantAlternative::type; - AlternativeT *value = reinterpret_cast(m_storage); - *value = std::move(other.get()); - m_index = Index; - AWS_ASSERT(m_index != -1); - } - else + template static void Visit(Variant *pThis, VisitorStruct &&visitor) { - MoveAssignUtil(std::move(other)); - } - } + static_assert(Index < AlternativeCount, "Attempting to visit unknown Index Type"); - template void MoveAssignUtil(Variant &&other) - { - static_assert(Index < AlternativeCount, "Attempting to construct 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 == other.m_index) + template + static void VisitBinary(Variant *pThis, Variant &&other, VisitorStruct &&visitor) { - using AlternativeT = typename ThisVariantAlternative::type; - AlternativeT *value = reinterpret_cast(m_storage); - *value = std::move(other.get()); - m_index = Index; - AWS_ASSERT(m_index != -1); + 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(Variant *pThis, const Variant &other, VisitorStruct &&visitor) { - AWS_ASSERT(!"Unknown variant alternative type in other!"); + 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 */ diff --git a/tests/VariantTest.cpp b/tests/VariantTest.cpp index 2baff2f2c..fc41782e2 100644 --- a/tests/VariantTest.cpp +++ b/tests/VariantTest.cpp @@ -71,7 +71,7 @@ static int s_VariantConstructor(struct aws_allocator *allocator, void *ctx) } { using VariantStringCharInt = Aws::Crt::Variant; - VariantStringCharInt var1{Aws::Crt::VariantInPlaceInitT(), s_variant_test_str}; + VariantStringCharInt var1{Aws::Crt::InPlaceTypeT(), s_variant_test_str}; ASSERT_STR_EQUALS(s_variant_test_str, var1.get<0>().c_str()); ASSERT_STR_EQUALS(s_variant_test_str, var1.get().c_str()); @@ -135,7 +135,7 @@ static int s_VariantConstructor(struct aws_allocator *allocator, void *ctx) int childState = 0; { MyTestVariant myTestVariant{ - Aws::Crt::VariantInPlaceInitT(), &parentState, &childState}; + Aws::Crt::InPlaceTypeT(), &parentState, &childState}; // constructor of MyTestVirtualClassChild was called only once, destructor was not called (yet) ASSERT_INT_EQUALS(1, parentState); ASSERT_INT_EQUALS(2, childState); @@ -150,7 +150,7 @@ static int s_VariantConstructor(struct aws_allocator *allocator, void *ctx) int childState = 0; { MyTestVariant myTestVariant{ - Aws::Crt::VariantInPlaceInitT(), &parentState, &childState}; + Aws::Crt::InPlaceTypeT(), &parentState, &childState}; myTestVariant.emplace(&parentState); // both were destructed but only a parent got constructed once again ASSERT_INT_EQUALS(-8, parentState); @@ -188,7 +188,7 @@ static int s_VariantOperatorEquals(struct aws_allocator *allocator, void *ctx) var1 = var2; ASSERT_INT_EQUALS(10, var1.get()); - VariantIntCharString varStr1{Aws::Crt::VariantInPlaceInitT(), s_variant_test_str}; + VariantIntCharString varStr1{Aws::Crt::InPlaceTypeT(), s_variant_test_str}; ASSERT_STR_EQUALS(s_variant_test_str, varStr1.get<2>().c_str()); VariantIntCharString varStr2; ASSERT_INT_EQUALS(0, varStr2.get()); @@ -200,6 +200,14 @@ static int s_VariantOperatorEquals(struct aws_allocator *allocator, void *ctx) ASSERT_STR_EQUALS(s_variant_test_str, varStr3.get().c_str()); ASSERT_STR_EQUALS(s_variant_test_str, varStr3.get_if<2>()->c_str()); ASSERT_TRUE(varStr1.get().empty()); + + VariantIntCharString varStr4(varStr3); + ASSERT_STR_EQUALS(s_variant_test_str, varStr3.get_if<2>()->c_str()); // not moved + ASSERT_STR_EQUALS(s_variant_test_str, varStr4.get_if<2>()->c_str()); // copied + + varStr1 = std::move(varStr4); + ASSERT_TRUE(varStr4.get<2>().empty()); // moved from + ASSERT_STR_EQUALS(s_variant_test_str, varStr1.get<2>().c_str()); // moved here } } @@ -208,6 +216,32 @@ static int s_VariantOperatorEquals(struct aws_allocator *allocator, void *ctx) AWS_TEST_CASE(VariantOperatorEquals, s_VariantOperatorEquals) +struct TestStringOnlyVisitor +{ + /* can't specialize member function templates, so using such syntax of dummy structs */ + template struct MyVisitUtil + { + static void Visit(Args &...) + { + ; // not a string + } + }; + + template void operator()(AlternativeT &val) const + { + MyVisitUtil::type>::Visit(val); + } +}; + +template <> struct TestStringOnlyVisitor::MyVisitUtil +{ + static void Visit(Aws::Crt::String &val) + { + auto index = val.find("another"); + val.replace(index, 7, "visited"); + } +}; + static int s_VariantEmplace(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -223,8 +257,17 @@ static int s_VariantEmplace(struct aws_allocator *allocator, void *ctx) var1.emplace(65535); ASSERT_INT_EQUALS(65535, var1.get()); + var1.emplace<0>(1337); + ASSERT_INT_EQUALS(1337, var1.get()); + var1.emplace(Aws::Crt::String("This is a string.")); ASSERT_STR_EQUALS("This is a string.", var1.get().c_str()); + + var1.emplace<2>(Aws::Crt::String("This is another string.")); + ASSERT_STR_EQUALS("This is another string.", var1.get().c_str()); + + var1.Visit(TestStringOnlyVisitor()); + ASSERT_STR_EQUALS("This is visited string.", var1.get().c_str()); } }