From 0e01c812ea049b9df3e1e88355718e6a56d91afa Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sun, 19 Jul 2026 13:00:41 +0200 Subject: [PATCH] Fix variant copy operations enabled with non-copyable alternatives (#1514) etl::variant was copy-constructible and copy-assignable even when an alternative was not copyable, and copying a non-copyable active alternative silently copied only the index, leaving the variant in an invalid state. Move the copy/move construct and assign logic into variant_base and add a variant_copy_move_control mixin that conditionally deletes the copy and move special members based on whether every alternative is copy/move constructible. The variant's special members are now defaulted, so they are deleted unless all alternatives support the operation. Fixes: #1512 Further, same-type-assignment optimization: Previously, assignment was destroy-and-reconstruct. Now, copy/move-assign are enabled if the types allow for it. Co-authored-by: John Wellbelove --- include/etl/private/variant_variadic.h | 470 ++++++++++++++++++++----- include/etl/type_traits.h | 21 ++ test/test_variant_variadic.cpp | 283 +++++++++++++++ 3 files changed, 682 insertions(+), 92 deletions(-) diff --git a/include/etl/private/variant_variadic.h b/include/etl/private/variant_variadic.h index 545fd5e8..b76ba7a6 100644 --- a/include/etl/private/variant_variadic.h +++ b/include/etl/private/variant_variadic.h @@ -86,6 +86,8 @@ namespace etl static void destroy(char*, size_t) {} static void copy(char*, const char*, size_t) {} static void move(char*, const char*, size_t) {} + static void copy_assign(char*, const char*, size_t) {} + static void move_assign(char*, const char*, size_t) {} }; // Recursive case. @@ -128,6 +130,30 @@ namespace etl } } + static void copy_assign(char* dst, const char* src, size_t type_id) + { + if (type_id == Index) + { + copy_assign_impl(dst, src, etl::integral_constant::value>{}); + } + else + { + variant_operations::copy_assign(dst, src, type_id); + } + } + + static void move_assign(char* dst, const char* src, size_t type_id) + { + if (type_id == Index) + { + move_assign_impl(dst, src, etl::integral_constant::value>{}); + } + else + { + variant_operations::move_assign(dst, src, type_id); + } + } + private: static void copy_impl(char* dst, const char* src, etl::true_type) @@ -143,6 +169,20 @@ namespace etl } static void move_impl(char*, const char*, etl::false_type) {} + + static void copy_assign_impl(char* dst, const char* src, etl::true_type) + { + *reinterpret_cast(dst) = *reinterpret_cast(src); + } + + static void copy_assign_impl(char*, const char*, etl::false_type) {} + + static void move_assign_impl(char* dst, const char* src, etl::true_type) + { + *reinterpret_cast(dst) = etl::move(*reinterpret_cast(const_cast(src))); + } + + static void move_assign_impl(char*, const char*, etl::false_type) {} }; //******************************************* @@ -431,6 +471,80 @@ namespace etl { } + variant_base(const variant_base& other) noexcept(etl::conjunction...>::value) + : type_id(other.type_id) + { + if (other.type_id != variant_npos) + { + variant_operations<0, TTypes...>::copy(data, other.data, other.type_id); + } + } + + variant_base(variant_base&& other) noexcept(etl::conjunction...>::value) + : type_id(other.type_id) + { + if (other.type_id != variant_npos) + { + variant_operations<0, TTypes...>::move(data, other.data, other.type_id); + } + } + + variant_base& operator=(const variant_base& other) noexcept(etl::conjunction...>::value + && etl::conjunction...>::value) + { + if (this != &other) + { + if ((type_id != variant_npos) && (type_id == other.type_id)) + { + variant_operations<0, TTypes...>::copy_assign(data, other.data, type_id); + } + else + { + if (type_id != variant_npos) + { + variant_operations<0, TTypes...>::destroy(data, type_id); + type_id = variant_npos; + } + + if (other.type_id != variant_npos) + { + variant_operations<0, TTypes...>::copy(data, other.data, other.type_id); + type_id = other.type_id; + } + } + } + + return *this; + } + + variant_base& operator=(variant_base&& other) noexcept(etl::conjunction...>::value + && etl::conjunction...>::value) + { + if (this != &other) + { + if ((type_id != variant_npos) && (type_id == other.type_id)) + { + variant_operations<0, TTypes...>::move_assign(data, other.data, type_id); + } + else + { + if (type_id != variant_npos) + { + variant_operations<0, TTypes...>::destroy(data, type_id); + type_id = variant_npos; + } + + if (other.type_id != variant_npos) + { + variant_operations<0, TTypes...>::move(data, other.data, other.type_id); + type_id = other.type_id; + } + } + } + + return *this; + } + ~variant_base() { if (type_id != variant_npos) @@ -474,7 +588,259 @@ namespace etl { } + variant_base(const variant_base& other) noexcept(etl::conjunction...>::value) + : data() + , type_id(other.type_id) + { + copy_construct_from(other, is_all_trivially_copyable{}); + } + + variant_base(variant_base&& other) noexcept(etl::conjunction...>::value) + : data() + , type_id(other.type_id) + { + move_construct_from(other, is_all_trivially_copyable{}); + } + + variant_base& operator=(const variant_base& other) noexcept(etl::conjunction...>::value + && etl::conjunction...>::value) + { + if (this != &other) + { + copy_assign_from(other, is_all_trivially_copyable{}); + } + return *this; + } + + variant_base& operator=(variant_base&& other) noexcept(etl::conjunction...>::value + && etl::conjunction...>::value) + { + if (this != &other) + { + move_assign_from(other, is_all_trivially_copyable{}); + } + return *this; + } + ~variant_base() = default; + + private: + + // All alternatives trivially copyable => a raw-byte copy correctly + // reproduces both copy and move construction. + typedef etl::integral_constant...>::value> is_all_trivially_copyable; + + //******************************************* + // Trivially copyable fast path: raw-byte copy. + //******************************************* + void copy_construct_from(const variant_base& other, etl::true_type) noexcept + { + memcpy(static_cast(&data), static_cast(&other.data), sizeof(data)); + } + + void move_construct_from(const variant_base& other, etl::true_type) noexcept + { + memcpy(static_cast(&data), static_cast(&other.data), sizeof(data)); + } + + void copy_assign_from(const variant_base& other, etl::true_type) noexcept + { + type_id = other.type_id; + memcpy(static_cast(&data), static_cast(&other.data), sizeof(data)); + } + + void move_assign_from(const variant_base& other, etl::true_type) noexcept + { + type_id = other.type_id; + memcpy(static_cast(&data), static_cast(&other.data), sizeof(data)); + } + + //******************************************* + // Non-trivial path: dispatch to the active alternative's own + // copy/move constructor. The active union member shares its address + // with the union, so the raw-pointer dispatch places the new object + // in the correct storage. + //******************************************* + void copy_construct_from(const variant_base& other, etl::false_type) + { + if (other.type_id != variant_npos) + { + variant_operations<0, TTypes...>::copy(reinterpret_cast(&data), reinterpret_cast(&other.data), other.type_id); + } + } + + void move_construct_from(variant_base& other, etl::false_type) + { + if (other.type_id != variant_npos) + { + variant_operations<0, TTypes...>::move(reinterpret_cast(&data), reinterpret_cast(&other.data), other.type_id); + } + } + + void copy_assign_from(const variant_base& other, etl::false_type) + { + if ((type_id != variant_npos) && (type_id == other.type_id)) + { + variant_operations<0, TTypes...>::copy_assign(reinterpret_cast(&data), reinterpret_cast(&other.data), type_id); + return; + } + + if (type_id != variant_npos) + { + variant_operations<0, TTypes...>::destroy(reinterpret_cast(&data), type_id); + type_id = variant_npos; + } + + if (other.type_id != variant_npos) + { + variant_operations<0, TTypes...>::copy(reinterpret_cast(&data), reinterpret_cast(&other.data), other.type_id); + type_id = other.type_id; + } + } + + void move_assign_from(variant_base& other, etl::false_type) + { + if ((type_id != variant_npos) && (type_id == other.type_id)) + { + variant_operations<0, TTypes...>::move_assign(reinterpret_cast(&data), reinterpret_cast(&other.data), type_id); + return; + } + + if (type_id != variant_npos) + { + variant_operations<0, TTypes...>::destroy(reinterpret_cast(&data), type_id); + type_id = variant_npos; + } + + if (other.type_id != variant_npos) + { + variant_operations<0, TTypes...>::move(reinterpret_cast(&data), reinterpret_cast(&other.data), other.type_id); + type_id = other.type_id; + } + } + }; + + //******************************************* + /// Trait: are all types copy constructible? + //******************************************* + template + struct are_all_copy_constructible : etl::conjunction...> + { + }; + + //******************************************* + /// Trait: are all types move constructible? + //******************************************* + template + struct are_all_move_constructible : etl::conjunction...> + { + }; + + //******************************************* + /// Trait: are all types copy assignable? + //******************************************* + template + struct are_all_copy_assignable : etl::conjunction...> + { + }; + + //******************************************* + /// Trait: are all types move assignable? + //******************************************* + template + struct are_all_move_assignable : etl::conjunction...> + { + }; + + //******************************************* + /// Flat base that conditionally deletes the copy and move CONSTRUCTORS. + /// Every special member is declared directly (no intermediate bases) so + /// that MSVC does not spuriously delete the defaulted move members when a + /// sibling/base subobject has a deleted special member. + //******************************************* + template + struct variant_constructor_control + { + variant_constructor_control() = default; + variant_constructor_control(const variant_constructor_control&) = default; + variant_constructor_control(variant_constructor_control&&) = default; + variant_constructor_control& operator=(const variant_constructor_control&) = default; + variant_constructor_control& operator=(variant_constructor_control&&) = default; + }; + + template <> + struct variant_constructor_control + { + variant_constructor_control() = default; + variant_constructor_control(const variant_constructor_control&) = delete; + variant_constructor_control(variant_constructor_control&&) = default; + variant_constructor_control& operator=(const variant_constructor_control&) = default; + variant_constructor_control& operator=(variant_constructor_control&&) = default; + }; + + template <> + struct variant_constructor_control + { + variant_constructor_control() = default; + variant_constructor_control(const variant_constructor_control&) = default; + variant_constructor_control(variant_constructor_control&&) = delete; + variant_constructor_control& operator=(const variant_constructor_control&) = default; + variant_constructor_control& operator=(variant_constructor_control&&) = default; + }; + + template <> + struct variant_constructor_control + { + variant_constructor_control() = default; + variant_constructor_control(const variant_constructor_control&) = delete; + variant_constructor_control(variant_constructor_control&&) = delete; + variant_constructor_control& operator=(const variant_constructor_control&) = default; + variant_constructor_control& operator=(variant_constructor_control&&) = default; + }; + + //******************************************* + /// Flat base that conditionally deletes the copy and move ASSIGNMENT + /// operators. Every special member is declared directly (see the note on + /// variant_constructor_control above). + //******************************************* + template + struct variant_assignment_control + { + variant_assignment_control() = default; + variant_assignment_control(const variant_assignment_control&) = default; + variant_assignment_control(variant_assignment_control&&) = default; + variant_assignment_control& operator=(const variant_assignment_control&) = default; + variant_assignment_control& operator=(variant_assignment_control&&) = default; + }; + + template <> + struct variant_assignment_control + { + variant_assignment_control() = default; + variant_assignment_control(const variant_assignment_control&) = default; + variant_assignment_control(variant_assignment_control&&) = default; + variant_assignment_control& operator=(const variant_assignment_control&) = delete; + variant_assignment_control& operator=(variant_assignment_control&&) = default; + }; + + template <> + struct variant_assignment_control + { + variant_assignment_control() = default; + variant_assignment_control(const variant_assignment_control&) = default; + variant_assignment_control(variant_assignment_control&&) = default; + variant_assignment_control& operator=(const variant_assignment_control&) = default; + variant_assignment_control& operator=(variant_assignment_control&&) = delete; + }; + + template <> + struct variant_assignment_control + { + variant_assignment_control() = default; + variant_assignment_control(const variant_assignment_control&) = default; + variant_assignment_control(variant_assignment_control&&) = default; + variant_assignment_control& operator=(const variant_assignment_control&) = delete; + variant_assignment_control& operator=(variant_assignment_control&&) = delete; }; } // namespace private_variant @@ -483,7 +849,13 @@ namespace etl ///\ingroup variant //*************************************************************************** template - class variant : private private_variant::variant_base::value, TTypes...> + class variant + : private private_variant::variant_base::value, TTypes...> + , private private_variant::variant_constructor_control< private_variant::are_all_copy_constructible::value, + private_variant::are_all_move_constructible::value> + , private private_variant::variant_assignment_control< + private_variant::are_all_copy_constructible::value && private_variant::are_all_copy_assignable::value, + private_variant::are_all_move_constructible::value && private_variant::are_all_move_assignable::value> { using base_type = private_variant::variant_base::value, TTypes...>; @@ -671,31 +1043,13 @@ namespace etl /// Copy constructor. ///\param other The other variant object to copy. //*************************************************************************** - #include "diagnostic_uninitialized_push.h" - ETL_CONSTEXPR14 variant(const variant& other) noexcept(etl::conjunction...>::value) - : base_type(other.type_id) - { - if (other.index() != variant_npos) - { - do_copy_from(other, etl::integral_constant{}); - } - } - #include "diagnostic_pop.h" + variant(const variant& other) = default; //*************************************************************************** /// Move constructor. ///\param other The other variant object to copy. //*************************************************************************** - #include "diagnostic_uninitialized_push.h" - ETL_CONSTEXPR14 variant(variant&& other) noexcept(etl::conjunction...>::value) - : base_type(other.type_id) - { - if (other.index() != variant_npos) - { - do_move_from(other, etl::integral_constant{}); - } - } - #include "diagnostic_pop.h" + variant(variant&& other) = default; //*************************************************************************** /// Destructor. @@ -806,51 +1160,13 @@ namespace etl /// Assignment operator for variant type. ///\param other The variant to assign. //*************************************************************************** - variant& operator=(const variant& other) ETL_NOEXCEPT_IF((etl::conjunction...>::value)) - { - if (this != &other) - { - if (other.index() == variant_npos) - { - type_id = variant_npos; - } - else - { - do_destroy(); - - do_copy_from(other, etl::integral_constant{}); - - type_id = other.type_id; - } - } - - return *this; - } + variant& operator=(const variant& other) = default; //*************************************************************************** - /// Assignment operator for variant type. + /// Move assignment operator for variant type. ///\param other The variant to assign. //*************************************************************************** - variant& operator=(variant&& other) ETL_NOEXCEPT_IF((etl::conjunction...>::value)) - { - if (this != &other) - { - if (other.index() == variant_npos) - { - type_id = variant_npos; - } - else - { - do_destroy(); - - do_move_from(other, etl::integral_constant{}); - - type_id = other.type_id; - } - } - - return *this; - } + variant& operator=(variant&& other) = default; //*************************************************************************** /// Checks whether the variant doesn't contain a valid value. @@ -1149,36 +1465,6 @@ namespace etl ::new (static_cast(data)) T(etl::forward(value)); } - //*************************************************************************** - /// Copy from another variant. - //*************************************************************************** - void do_copy_from(const variant& other, etl::integral_constant) - { - // Trivially destructible: copy via memcpy to avoid issues with non-trivial copy assignment in union. - memcpy(static_cast(&data), static_cast(&other.data), sizeof(data)); - } - - void do_copy_from(const variant& other, etl::integral_constant) - { - // Non-trivially destructible: use switch-based dispatch. - private_variant::variant_operations<0, TTypes...>::copy(data, other.data, other.type_id); - } - - //*************************************************************************** - /// Move from another variant. - //*************************************************************************** - void do_move_from(variant& other, etl::integral_constant) - { - // Trivially destructible: copy via memcpy (trivial move == copy). - memcpy(static_cast(&data), static_cast(&other.data), sizeof(data)); - } - - void do_move_from(variant& other, etl::integral_constant) - { - // Non-trivially destructible: use switch-based dispatch. - private_variant::variant_operations<0, TTypes...>::move(data, other.data, other.type_id); - } - #if ETL_USING_CPP17 && !defined(ETL_VARIANT_FORCE_CPP11) //*************************************************************************** /// Call the relevant visitor by attempting each one. diff --git a/include/etl/type_traits.h b/include/etl/type_traits.h index 52fd9859..a4751fa3 100644 --- a/include/etl/type_traits.h +++ b/include/etl/type_traits.h @@ -3323,9 +3323,30 @@ namespace etl //********************************************* // is_assignable + #if !ETL_USING_BUILTIN_IS_ASSIGNABLE && ETL_USING_CPP11 + namespace private_type_traits + { + template + struct is_assignable_ : etl::false_type + { + }; + + template + struct is_assignable_() = etl::declval())>, T1, T2> : etl::true_type + { + }; + } // namespace private_type_traits + #endif + template #if ETL_USING_BUILTIN_IS_ASSIGNABLE struct is_assignable : public etl::bool_constant<__is_assignable(T1, T2)> + #elif ETL_USING_CPP11 + // Builtin-free assignability detection. Used when the __is_assignable builtin + // is unavailable (e.g. MSVC, where determine_builtin_support.h disables the + // __has_builtin block). Mirrors the void_t-based is_constructible above so + // that class types are correctly reported as (copy/move) assignable. + struct is_assignable : public private_type_traits::is_assignable_, T1, T2> #else struct is_assignable : public etl::bool_constant< (etl::is_arithmetic::value || etl::is_pointer::value) diff --git a/test/test_variant_variadic.cpp b/test/test_variant_variadic.cpp index 5b558e05..11cef693 100644 --- a/test/test_variant_variadic.cpp +++ b/test/test_variant_variadic.cpp @@ -321,6 +321,26 @@ namespace bool copied_to; }; + //********************************************* + // Copy constructible, but copy assignment is deleted. + struct CopyConstructibleNonCopyAssignable + { + CopyConstructibleNonCopyAssignable() = default; + CopyConstructibleNonCopyAssignable(const CopyConstructibleNonCopyAssignable&) = default; + CopyConstructibleNonCopyAssignable& operator=(const CopyConstructibleNonCopyAssignable&) = delete; + }; + + //********************************************* + // Move constructible, but move assignment is deleted. + struct MoveConstructibleNonMoveAssignable + { + MoveConstructibleNonMoveAssignable() = default; + MoveConstructibleNonMoveAssignable(MoveConstructibleNonMoveAssignable&&) = default; + MoveConstructibleNonMoveAssignable& operator=(MoveConstructibleNonMoveAssignable&&) = delete; + MoveConstructibleNonMoveAssignable(const MoveConstructibleNonMoveAssignable&) = delete; + MoveConstructibleNonMoveAssignable& operator=(const MoveConstructibleNonMoveAssignable&) = delete; + }; + //********************************************* // Trivially destructible, but non-copyable and non-movable. // Exercises the trivially-destructible (variadic_union) emplace path. @@ -420,6 +440,109 @@ namespace int value; }; + + //********************************************* + // Non-trivially destructible type whose copy/move ASSIGNMENT has a + // distinguishable effect from copy/move CONSTRUCTION. Used to verify that + // same-type variant assignment dispatches through the alternative's own + // assignment operator (no destroy + reconstruct). Exercises the + // uninitialized_buffer (non-trivial) path. + struct AssignTracker + { + AssignTracker(int value_) + : value(value_) + , constructed(true) + , copy_assigned(false) + , move_assigned(false) + { + } + + AssignTracker(const AssignTracker& other) + : value(other.value) + , constructed(true) + , copy_assigned(false) + , move_assigned(false) + { + } + + AssignTracker(AssignTracker&& other) + : value(other.value) + , constructed(true) + , copy_assigned(false) + , move_assigned(false) + { + } + + ~AssignTracker() {} + + AssignTracker& operator=(const AssignTracker& rhs) + { + value = rhs.value; + copy_assigned = true; + move_assigned = false; + return *this; + } + + AssignTracker& operator=(AssignTracker&& rhs) + { + value = rhs.value; + copy_assigned = false; + move_assigned = true; + return *this; + } + + int value; + bool constructed; + bool copy_assigned; + bool move_assigned; + }; + + //********************************************* + // Trivially destructible variant of AssignTracker (no user destructor) that + // exercises the trivially-destructible (variadic_union) path. + struct TrivialAssignTracker + { + TrivialAssignTracker(int value_) + : value(value_) + , copy_assigned(false) + , move_assigned(false) + { + } + + TrivialAssignTracker(const TrivialAssignTracker& other) + : value(other.value) + , copy_assigned(false) + , move_assigned(false) + { + } + + TrivialAssignTracker(TrivialAssignTracker&& other) + : value(other.value) + , copy_assigned(false) + , move_assigned(false) + { + } + + TrivialAssignTracker& operator=(const TrivialAssignTracker& rhs) + { + value = rhs.value; + copy_assigned = true; + move_assigned = false; + return *this; + } + + TrivialAssignTracker& operator=(TrivialAssignTracker&& rhs) + { + value = rhs.value; + copy_assigned = false; + move_assigned = true; + return *this; + } + + int value; + bool copy_assigned; + bool move_assigned; + }; } // namespace // Moved from the top of the file otherwise clang has issues with @@ -560,6 +683,26 @@ struct etl::is_move_constructible : public etl::false_type template <> struct etl::is_move_constructible : public etl::true_type { +}; + +template <> +struct etl::is_copy_constructible : public etl::true_type +{ +}; + +template <> +struct etl::is_move_constructible : public etl::true_type +{ +}; + +template <> +struct etl::is_copy_constructible : public etl::true_type +{ +}; + +template <> +struct etl::is_move_constructible : public etl::true_type +{ }; #endif @@ -999,6 +1142,146 @@ namespace CHECK_EQUAL(variant_1_etl.index(), variant_2_etl.index()); } + //************************************************************************* + // Issue #1512: a variant must only be copy constructible / copy assignable + // when every alternative is copy constructible. Moveable is move-only + // (its copy operations are deleted), so a variant containing it must not + // be copyable, while remaining move constructible / move assignable. + TEST(test_copy_disabled_with_non_copyable_alternative) + { + typedef etl::variant non_copyable_variant; + + CHECK(!etl::is_copy_constructible::value); + CHECK(!etl::is_copy_assignable::value); + CHECK(etl::is_move_constructible::value); + CHECK(etl::is_move_assignable::value); + + // A variant of purely copyable alternatives must remain copyable. + typedef etl::variant copyable_variant; + + CHECK(etl::is_copy_constructible::value); + CHECK(etl::is_copy_assignable::value); + } + + //************************************************************************* + // A non-copy-assignable alternative must disable the variant's copy + // assignment operator, and a non-move-assignable alternative must disable + // the variant's move assignment operator, while leaving the corresponding + // constructors available. + TEST(test_assignment_disabled_with_non_assignable_alternative) + { + typedef etl::variant copy_non_assignable_variant; + + CHECK(etl::is_copy_constructible::value); + CHECK(!etl::is_copy_assignable::value); + + typedef etl::variant move_non_assignable_variant; + + CHECK(etl::is_move_constructible::value); + CHECK(!etl::is_move_assignable::value); + } + + //************************************************************************* + // When source and destination hold the SAME alternative, assignment must + // dispatch through that alternative's own assignment operator rather than + // destroying and reconstructing it. Non-trivially-destructible path. + TEST(test_copy_assign_same_type_uses_alternative_assignment) + { + typedef etl::variant tracker_variant; + + tracker_variant variant_1(etl::in_place_type_t{}, 1); + tracker_variant variant_2(etl::in_place_type_t{}, 2); + + variant_1 = variant_2; + + CHECK_EQUAL(1U, variant_1.index()); + CHECK_EQUAL(2, etl::get(variant_1).value); + CHECK(etl::get(variant_1).copy_assigned); + CHECK(!etl::get(variant_1).move_assigned); + } + + //************************************************************************* + TEST(test_move_assign_same_type_uses_alternative_assignment) + { + typedef etl::variant tracker_variant; + + tracker_variant variant_1(etl::in_place_type_t{}, 1); + tracker_variant variant_2(etl::in_place_type_t{}, 2); + + variant_1 = etl::move(variant_2); + + CHECK_EQUAL(1U, variant_1.index()); + CHECK_EQUAL(2, etl::get(variant_1).value); + CHECK(!etl::get(variant_1).copy_assigned); + CHECK(etl::get(variant_1).move_assigned); + } + + //************************************************************************* + // When source and destination hold DIFFERENT alternatives, assignment must + // still destroy and reconstruct. + TEST(test_copy_assign_different_type_reconstructs) + { + typedef etl::variant tracker_variant; + + tracker_variant variant_1(etl::in_place_type_t{}, 5); + tracker_variant variant_2(etl::in_place_type_t{}, 2); + + variant_1 = variant_2; + + CHECK_EQUAL(1U, variant_1.index()); + CHECK_EQUAL(2, etl::get(variant_1).value); + CHECK(etl::get(variant_1).constructed); + CHECK(!etl::get(variant_1).copy_assigned); + CHECK(!etl::get(variant_1).move_assigned); + } + + //************************************************************************* + // Same as above but for the trivially-destructible (variadic_union) path. + TEST(test_assign_same_type_uses_alternative_assignment_trivial) + { + typedef etl::variant tracker_variant; + + tracker_variant variant_1(etl::in_place_type_t{}, 1); + tracker_variant variant_2(etl::in_place_type_t{}, 2); + + variant_1 = variant_2; + + CHECK_EQUAL(1U, variant_1.index()); + CHECK_EQUAL(2, etl::get(variant_1).value); + CHECK(etl::get(variant_1).copy_assigned); + CHECK(!etl::get(variant_1).move_assigned); + + tracker_variant variant_3(etl::in_place_type_t{}, 3); + variant_1 = etl::move(variant_3); + + CHECK_EQUAL(1U, variant_1.index()); + CHECK_EQUAL(3, etl::get(variant_1).value); + CHECK(etl::get(variant_1).move_assigned); + } + + //************************************************************************* + // Issue #1512: even though the copy operations are disabled at compile + // time, a move-only variant must still move construct / move assign + // correctly, preserving the active alternative. + TEST(test_move_only_variant_moves_correctly) + { + typedef etl::variant non_copyable_variant; + + non_copyable_variant variant_1(etl::in_place_type_t{}); + CHECK_EQUAL(1U, variant_1.index()); + + // Move construction preserves the active alternative. + non_copyable_variant variant_2(etl::move(variant_1)); + CHECK_EQUAL(1U, variant_2.index()); + CHECK(etl::get(variant_2).moved_to); + + // Move assignment preserves the active alternative. + non_copyable_variant variant_3; + variant_3 = etl::move(variant_2); + CHECK_EQUAL(1U, variant_3.index()); + CHECK(etl::get(variant_3).moved_to); + } + //************************************************************************* TEST(test_assign_from_value) {