From 97deef996f22f623ac3457d1230f973bae55a0c8 Mon Sep 17 00:00:00 2001 From: taltenbach <92919739+taltenbach@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:31:32 +0100 Subject: [PATCH] Allow implicit conversions in variant's constructor and operator= (#1634) * Add docs and tutorial for etl::intrusive_avl_tree (#1608) * Add docs and tutorial for etl::intrusive_avl_tree Documents the intrusive AVL tree added in PR #1425: a full API reference page under Containers/Trees, and a tutorial demonstrating ordered access to intrusively-linked objects with unpredictable lifetimes, including move support and destructor-driven automatic removal from the tree. Co-Authored-By: Claude Sonnet 5 * Add complexity shortcode and document its usage Introduces a {{< complexity >}} Hugo shortcode (with light/dark styling for constant/logarithmic/linear/linearithmic/polynomial/exponential classes) for rendering Big-O complexity inline, applies it throughout the intrusive_avl_tree API reference in place of raw "O(...)" text, and documents the Description/Complexity structure and supported classes in DOCUMENTATION.md. Co-Authored-By: Claude Sonnet 5 * Fix issues found by @CodeRabbit * Put complexity notes on their own lines in intrusive_avl_tree docs Move the complexity shortcode from the inline "**Complexity:**" label to its own line under a "**Complexity**" label. Notes that give several alternatives (e.g. amortized vs. worst-case) now show each one on a separate line. Update the guideline in DOCUMENTATION.md to match. * Update complexity examples in DOCUMENTATION.md Add the hard line break after the "**Complexity**" label in the example, and rewrite the list of supported complexity classes in the new two-line format, including an example with several alternatives. * Add logarithmic complexity example to DOCUMENTATION.md --------- Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove * Updated sponsors pages * Refactor the documentation css to group styles for light and dark themes * Update Codacy badge in README.md Updated Codacy badge link to reflect new project status. * Fix Codacy badge display * refresh codacy integration status * Expose more Zephyr Kconfigs for error handling (#1622) * zephyr: make `ETL_DEBUG` default to on if Zephyr assertions are enabled this is the more appropriate default: ETL_DEBUG controls some container assertions, so it should follow the Zephyr assertions by default * zephyr: expose new error-checking configs * zephyr: add Kconfig choices for error handling * Constrain variant value constructor to supported types (#1614) * Add docs and tutorial for etl::intrusive_avl_tree (#1608) * Add docs and tutorial for etl::intrusive_avl_tree Documents the intrusive AVL tree added in PR #1425: a full API reference page under Containers/Trees, and a tutorial demonstrating ordered access to intrusively-linked objects with unpredictable lifetimes, including move support and destructor-driven automatic removal from the tree. Co-Authored-By: Claude Sonnet 5 * Add complexity shortcode and document its usage Introduces a {{< complexity >}} Hugo shortcode (with light/dark styling for constant/logarithmic/linear/linearithmic/polynomial/exponential classes) for rendering Big-O complexity inline, applies it throughout the intrusive_avl_tree API reference in place of raw "O(...)" text, and documents the Description/Complexity structure and supported classes in DOCUMENTATION.md. Co-Authored-By: Claude Sonnet 5 * Fix issues found by @CodeRabbit * Put complexity notes on their own lines in intrusive_avl_tree docs Move the complexity shortcode from the inline "**Complexity:**" label to its own line under a "**Complexity**" label. Notes that give several alternatives (e.g. amortized vs. worst-case) now show each one on a separate line. Update the guideline in DOCUMENTATION.md to match. * Update complexity examples in DOCUMENTATION.md Add the hard line break after the "**Complexity**" label in the example, and rewrite the list of supported complexity classes in the new two-line format, including an example with several alternatives. * Add logarithmic complexity example to DOCUMENTATION.md --------- Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove * Updated sponsors pages * Refactor the documentation css to group styles for light and dark themes * Update Codacy badge in README.md Updated Codacy badge link to reflect new project status. * Fix Codacy badge display * Constrain variant value constructor to supported types Previously, the implicit value constructor of etl::variant could participate in overload resolution for unsupported types, potentially causing ambiguous calls before the assertion was triggered. The static_assert are replaced by enable_if constraints. Note the same approach is applied to other constructors and methods for consistency. --------- Co-authored-by: Sergei Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove Co-authored-by: John Wellbelove * Allow implicit conversions in variant's value constructor Previously, the etl::variant's value constructor was only allowing values whose types matched an alternative. Implicit conversions weren't considered. For example, unlike std::variant, etl::variant could not directly be constructed from a float. The best matching alternative is now chosen using overload resolution, excluding narrowing and explicit-only conversions and rejecting ambiguous matches. * Allow implicit conversions in variant's operator= Reuse the converting constructor's alternative-selection helper in operator=(T&&). Select the best non-narrowing implicit conversion and reject ambiguous matches. Note that contrary to std::variant's operator=, no constraint is added to refuse types T for which is_assignable_v is false. Indeed, there are existing tests that explicitly check that operator= can be used with a non-assignable type. * Exclude in-place tags from variant's converting constructor Like std::variant, etl::variant should exclude in_place_type_t and in_place_index_t specializations from its converting constructor. This prevents implicit conversions from treating an in-place tag as an ordinary value while preserving dedicated in-place construction. --------- Co-authored-by: Sergei Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove Co-authored-by: John Wellbelove Co-authored-by: Eden Frosst --- include/etl/private/variant_variadic.h | 107 ++++++++++++++++++++----- test/test_variant_variadic.cpp | 104 +++++++++++++++++++++++- 2 files changed, 192 insertions(+), 19 deletions(-) diff --git a/include/etl/private/variant_variadic.h b/include/etl/private/variant_variadic.h index f45be48b..73cf5cbc 100644 --- a/include/etl/private/variant_variadic.h +++ b/include/etl/private/variant_variadic.h @@ -283,6 +283,75 @@ namespace etl } }; + //**************************************************************************************************** + /// Helpers for selecting the best conversion candidate for variant value construction. + /// Only implicit conversions that do not involve narrowing are considered. + //**************************************************************************************************** + // Used to check for valid conversion that don't include narrowing as array copy-list-initialization excludes narrowing conversions. + template + struct conversion_array + { + T value[1]; + }; + + // Create a candidate function accepting TAlternative provided it can be constructed from T without narrowing conversions. When the conversion + // is valid, the function returns an integral constant representing the index of the alternative type in the variant's type list. + template + struct conversion_candidate + { + static void select(); + }; + + template + struct conversion_candidate{{etl::declval()}})> > + { + static etl::integral_constant select(TAlternative); + }; + + // Build all conversion overloads for a given type T across the list of alternative types. + template + struct conversion_overloads_impl; + + template + struct conversion_overloads_impl + { + static void select(); + }; + + template + struct conversion_overloads_impl + : conversion_candidate + , conversion_overloads_impl + { + using conversion_candidate::select; + using conversion_overloads_impl::select; + }; + + template + using conversion_overloads = conversion_overloads_impl; + + // Select the best alternative type for a given type T. + template + using select_alternative_index = decltype(conversion_overloads::select(etl::declval())); + + //************************************************************************* + /// Helpers for identifying in-place tags. + //************************************************************************* + template + struct is_in_place_tag : etl::false_type + { + }; + + template + struct is_in_place_tag > : etl::true_type + { + }; + + template + struct is_in_place_tag > : etl::true_type + { + }; + //******************************************* /// Constexpr get by index from variadic_union. //******************************************* @@ -1016,22 +1085,26 @@ namespace etl /// Construct from a value. //*************************************************************************** #include "diagnostic_uninitialized_push.h" - template < - typename T, bool Trivial_ = Is_Trivially_Destructible_Suite, - etl::enable_if_t, variant>::value && !Trivial_ && etl::is_one_of, TTypes...>::value, - int> = 0> + template < typename T, bool Trivial_ = Is_Trivially_Destructible_Suite, + etl::enable_if_t< !etl::is_same, variant>::value && !Trivial_ + && !private_variant::is_in_place_tag >::value, + int> = 0, + size_t AlternativeIndex = private_variant::select_alternative_index::value, + typename TAlternative = type_from_index, etl::enable_if_t::value, int> = 0> ETL_CONSTEXPR14 variant(T&& value) - : base_type(index_of_type::value) + : base_type(AlternativeIndex) { - construct_in_place >(data, etl::forward(value)); + construct_in_place(data, etl::forward(value)); } - template < - typename T, bool Trivial_ = Is_Trivially_Destructible_Suite, - etl::enable_if_t, variant>::value && Trivial_ && etl::is_one_of, TTypes...>::value, - int> = 0> + template < typename T, bool Trivial_ = Is_Trivially_Destructible_Suite, + etl::enable_if_t, variant>::value && Trivial_ + && !private_variant::is_in_place_tag >::value, + int> = 0, + size_t AlternativeIndex = private_variant::select_alternative_index::value, + typename TAlternative = type_from_index, etl::enable_if_t::value, int> = 0 > constexpr variant(T&& value) - : base_type(etl::in_place_index_t::value>{}, etl::forward(value), index_of_type::value) + : base_type(etl::in_place_index_t{}, etl::forward(value), AlternativeIndex) { } #include "diagnostic_pop.h" @@ -1208,17 +1281,15 @@ namespace etl /// Move assignment operator for type. ///\param value The value to assign. //*************************************************************************** - template < - typename T, - etl::enable_if_t, variant>::value && etl::is_one_of, TTypes...>::value, int> = 0> + template < typename T, etl::enable_if_t, variant>::value, int> = 0, + size_t AlternativeIndex = private_variant::select_alternative_index::value, + typename TAlternative = type_from_index, etl::enable_if_t::value, int> = 0> variant& operator=(T&& value) { - using type = etl::remove_cvref_t; - do_destroy(); - do_construct(etl::forward(value)); + do_construct(etl::forward(value)); - type_id = index_of_type::value; + type_id = AlternativeIndex; return *this; } diff --git a/test/test_variant_variadic.cpp b/test/test_variant_variadic.cpp index de09ff68..b7e69adb 100644 --- a/test/test_variant_variadic.cpp +++ b/test/test_variant_variadic.cpp @@ -831,7 +831,7 @@ namespace } //************************************************************************* - TEST(test_constructor_value) + TEST(test_constructor_value_no_conversion) { // Char. char c = 'a'; @@ -855,6 +855,101 @@ namespace CHECK_EQUAL(text, etl::get(variant_text_etl)); } + //************************************************************************* + TEST(test_constructor_value_conversion) + { + // Non-narrowing numeric conversions should be allowed. + etl::variant variant_double_trivial{1.5f}; + CHECK_EQUAL(etl::holds_alternative(variant_double_trivial), true); + CHECK_EQUAL(1.5, etl::get(variant_double_trivial)); + + etl::variant variant_double_nontrivial{1.5f}; + CHECK_EQUAL(etl::holds_alternative(variant_double_nontrivial), true); + CHECK_EQUAL(1.5, etl::get(variant_double_nontrivial)); + + // Implicit conversions for non-numeric types should also be allowed. + struct ImplicitInt + { + operator int() const + { + return 42; + } + }; + + etl::variant variant_implicit_int_trivial{ImplicitInt{}}; + CHECK_EQUAL(etl::holds_alternative(variant_implicit_int_trivial), true); + CHECK_EQUAL(42, etl::get(variant_implicit_int_trivial)); + + etl::variant variant_implicit_int_nontrivial{ImplicitInt{}}; + CHECK_EQUAL(etl::holds_alternative(variant_implicit_int_nontrivial), true); + CHECK_EQUAL(42, etl::get(variant_implicit_int_nontrivial)); + + // Exact match should be chosen over other conversions. + etl::variant variant_float_trivial{1.5f}; + CHECK_EQUAL(etl::holds_alternative(variant_float_trivial), true); + CHECK_EQUAL(1.5f, etl::get(variant_float_trivial)); + + etl::variant variant_float_nontrivial{1.5f}; + CHECK_EQUAL(etl::holds_alternative(variant_float_nontrivial), true); + CHECK_EQUAL(1.5f, etl::get(variant_float_nontrivial)); + + // Conversion should be possible even in the presence of duplicate alternatives. + etl::variant variant_duplicate_trivial{1.5f}; + CHECK_EQUAL(etl::holds_alternative(variant_duplicate_trivial), true); + CHECK_EQUAL(1.5, etl::get(variant_duplicate_trivial)); + + etl::variant variant_duplicate_nontrivial{1.5f}; + CHECK_EQUAL(etl::holds_alternative(variant_duplicate_nontrivial), true); + CHECK_EQUAL(1.5, etl::get(variant_duplicate_nontrivial)); + + // Narrowing conversion should not be allowed. + CHECK(!(etl::is_constructible, double>::value)); + CHECK(!(etl::is_constructible, double>::value)); + CHECK(!(std::is_constructible, float>::value)); + CHECK(!(std::is_constructible, float>::value)); + CHECK(!(std::is_constructible, int>::value)); + CHECK(!(std::is_constructible, int>::value)); + CHECK(!(std::is_constructible, int>::value)); + CHECK(!(std::is_constructible, int>::value)); + + // Construction should not be allowed when no implicit conversion is possible. + struct ExplicitInt + { + explicit operator int() const + { + return 42; + } + }; + + CHECK(!(std::is_constructible, const int*>::value)); + CHECK(!(std::is_constructible, const int*>::value)); + CHECK(!(std::is_constructible, ExplicitInt>::value)); + CHECK(!(std::is_constructible, ExplicitInt>::value)); + + // Construction should not be allowed when the conversion is ambiguous. + struct AmbiguousIntLong + { + operator int() const + { + return 1; + } + + operator long() const + { + return 1L; + } + }; + + CHECK(!(std::is_constructible, AmbiguousIntLong>::value)); + CHECK(!(std::is_constructible, AmbiguousIntLong>::value)); + + // The converting constructor should not accept in-place type or index tags. + CHECK((!std::is_constructible>, etl::in_place_type_t>::value)); + CHECK((!std::is_constructible>, etl::in_place_type_t>::value)); + CHECK((!std::is_constructible>, etl::in_place_index_t<1U>>::value)); + CHECK((!std::is_constructible>, etl::in_place_index_t<2U>>::value)); + } + //************************************************************************* TEST(test_constructor_move_value) { @@ -1535,12 +1630,19 @@ namespace //************************************************************************* TEST(test_assign_from_value) { + // No conversion required std::string text("Some Text"); test_variant_etl_3 variant_etl; variant_etl = text; CHECK_EQUAL(text, etl::get(variant_etl)); + + // Conversion from float to double required + etl::variant variant_double; + variant_double = 1.5f; + + CHECK_EQUAL(1.5, etl::get(variant_double)); } //*************************************************************************