mirror of
https://github.com/ETLCPP/etl.git
synced 2026-11-09 22:59:47 +08:00
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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com> * 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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com> * 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 <sergej.shirokov@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com> Co-authored-by: John Wellbelove <john.wellbelove@etlcpp.com> * 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<int, double> 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<TAlternative&, T> 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 <sergej.shirokov@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com> Co-authored-by: John Wellbelove <john.wellbelove@etlcpp.com> Co-authored-by: Eden Frosst <edenfrosst@gmail.com>
This commit is contained in:
parent
4020bc9313
commit
97deef996f
@ -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 <typename T>
|
||||
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 <typename T, size_t Index, typename TAlternative, typename = void>
|
||||
struct conversion_candidate
|
||||
{
|
||||
static void select();
|
||||
};
|
||||
|
||||
template <typename T, size_t Index, typename TAlternative>
|
||||
struct conversion_candidate<T, Index, TAlternative, etl::void_t<decltype(conversion_array<TAlternative>{{etl::declval<T>()}})> >
|
||||
{
|
||||
static etl::integral_constant<size_t, Index> select(TAlternative);
|
||||
};
|
||||
|
||||
// Build all conversion overloads for a given type T across the list of alternative types.
|
||||
template <typename T, size_t Index, typename... TTypes>
|
||||
struct conversion_overloads_impl;
|
||||
|
||||
template <typename T, size_t Index>
|
||||
struct conversion_overloads_impl<T, Index>
|
||||
{
|
||||
static void select();
|
||||
};
|
||||
|
||||
template <typename T, size_t Index, typename THead, typename... TRest>
|
||||
struct conversion_overloads_impl<T, Index, THead, TRest...>
|
||||
: conversion_candidate<T, Index, THead>
|
||||
, conversion_overloads_impl<T, Index + 1U, TRest...>
|
||||
{
|
||||
using conversion_candidate<T, Index, THead>::select;
|
||||
using conversion_overloads_impl<T, Index + 1U, TRest...>::select;
|
||||
};
|
||||
|
||||
template <typename T, typename... TTypes>
|
||||
using conversion_overloads = conversion_overloads_impl<T, 0U, TTypes...>;
|
||||
|
||||
// Select the best alternative type for a given type T.
|
||||
template <typename T, typename... TTypes>
|
||||
using select_alternative_index = decltype(conversion_overloads<T, TTypes...>::select(etl::declval<T>()));
|
||||
|
||||
//*************************************************************************
|
||||
/// Helpers for identifying in-place tags.
|
||||
//*************************************************************************
|
||||
template <typename T>
|
||||
struct is_in_place_tag : etl::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_in_place_tag<etl::in_place_type_t<T> > : etl::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <size_t Index>
|
||||
struct is_in_place_tag<etl::in_place_index_t<Index> > : 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<!etl::is_same<etl::remove_cvref_t<T>, variant>::value && !Trivial_ && etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value,
|
||||
int> = 0>
|
||||
template < typename T, bool Trivial_ = Is_Trivially_Destructible_Suite,
|
||||
etl::enable_if_t< !etl::is_same<etl::remove_cvref_t<T>, variant>::value && !Trivial_
|
||||
&& !private_variant::is_in_place_tag<etl::remove_cvref_t<T> >::value,
|
||||
int> = 0,
|
||||
size_t AlternativeIndex = private_variant::select_alternative_index<T, TTypes...>::value,
|
||||
typename TAlternative = type_from_index<AlternativeIndex>, etl::enable_if_t<etl::is_constructible<TAlternative, T>::value, int> = 0>
|
||||
ETL_CONSTEXPR14 variant(T&& value)
|
||||
: base_type(index_of_type<T>::value)
|
||||
: base_type(AlternativeIndex)
|
||||
{
|
||||
construct_in_place<etl::remove_cvref_t<T> >(data, etl::forward<T>(value));
|
||||
construct_in_place<TAlternative>(data, etl::forward<T>(value));
|
||||
}
|
||||
|
||||
template <
|
||||
typename T, bool Trivial_ = Is_Trivially_Destructible_Suite,
|
||||
etl::enable_if_t<!etl::is_same<etl::remove_cvref_t<T>, variant>::value && Trivial_ && etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value,
|
||||
int> = 0>
|
||||
template < typename T, bool Trivial_ = Is_Trivially_Destructible_Suite,
|
||||
etl::enable_if_t<!etl::is_same<etl::remove_cvref_t<T>, variant>::value && Trivial_
|
||||
&& !private_variant::is_in_place_tag<etl::remove_cvref_t<T> >::value,
|
||||
int> = 0,
|
||||
size_t AlternativeIndex = private_variant::select_alternative_index<T, TTypes...>::value,
|
||||
typename TAlternative = type_from_index<AlternativeIndex>, etl::enable_if_t<etl::is_constructible<TAlternative, T>::value, int> = 0 >
|
||||
constexpr variant(T&& value)
|
||||
: base_type(etl::in_place_index_t<index_of_type<T>::value>{}, etl::forward<T>(value), index_of_type<T>::value)
|
||||
: base_type(etl::in_place_index_t<AlternativeIndex>{}, etl::forward<T>(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<!etl::is_same<etl::remove_cvref_t<T>, variant>::value && etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value, int> = 0>
|
||||
template < typename T, etl::enable_if_t<!etl::is_same<etl::remove_cvref_t<T>, variant>::value, int> = 0,
|
||||
size_t AlternativeIndex = private_variant::select_alternative_index<T, TTypes...>::value,
|
||||
typename TAlternative = type_from_index<AlternativeIndex>, etl::enable_if_t<etl::is_constructible<TAlternative, T>::value, int> = 0>
|
||||
variant& operator=(T&& value)
|
||||
{
|
||||
using type = etl::remove_cvref_t<T>;
|
||||
|
||||
do_destroy();
|
||||
do_construct<type>(etl::forward<T>(value));
|
||||
do_construct<TAlternative>(etl::forward<T>(value));
|
||||
|
||||
type_id = index_of_type<type>::value;
|
||||
type_id = AlternativeIndex;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -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<std::string>(variant_text_etl));
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_constructor_value_conversion)
|
||||
{
|
||||
// Non-narrowing numeric conversions should be allowed.
|
||||
etl::variant<int, double> variant_double_trivial{1.5f};
|
||||
CHECK_EQUAL(etl::holds_alternative<double>(variant_double_trivial), true);
|
||||
CHECK_EQUAL(1.5, etl::get<double>(variant_double_trivial));
|
||||
|
||||
etl::variant<std::string, double> variant_double_nontrivial{1.5f};
|
||||
CHECK_EQUAL(etl::holds_alternative<double>(variant_double_nontrivial), true);
|
||||
CHECK_EQUAL(1.5, etl::get<double>(variant_double_nontrivial));
|
||||
|
||||
// Implicit conversions for non-numeric types should also be allowed.
|
||||
struct ImplicitInt
|
||||
{
|
||||
operator int() const
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
};
|
||||
|
||||
etl::variant<int, double> variant_implicit_int_trivial{ImplicitInt{}};
|
||||
CHECK_EQUAL(etl::holds_alternative<int>(variant_implicit_int_trivial), true);
|
||||
CHECK_EQUAL(42, etl::get<int>(variant_implicit_int_trivial));
|
||||
|
||||
etl::variant<int, std::string> variant_implicit_int_nontrivial{ImplicitInt{}};
|
||||
CHECK_EQUAL(etl::holds_alternative<int>(variant_implicit_int_nontrivial), true);
|
||||
CHECK_EQUAL(42, etl::get<int>(variant_implicit_int_nontrivial));
|
||||
|
||||
// Exact match should be chosen over other conversions.
|
||||
etl::variant<int, double, float> variant_float_trivial{1.5f};
|
||||
CHECK_EQUAL(etl::holds_alternative<float>(variant_float_trivial), true);
|
||||
CHECK_EQUAL(1.5f, etl::get<float>(variant_float_trivial));
|
||||
|
||||
etl::variant<std::string, double, float> variant_float_nontrivial{1.5f};
|
||||
CHECK_EQUAL(etl::holds_alternative<float>(variant_float_nontrivial), true);
|
||||
CHECK_EQUAL(1.5f, etl::get<float>(variant_float_nontrivial));
|
||||
|
||||
// Conversion should be possible even in the presence of duplicate alternatives.
|
||||
etl::variant<int, int, double> variant_duplicate_trivial{1.5f};
|
||||
CHECK_EQUAL(etl::holds_alternative<double>(variant_duplicate_trivial), true);
|
||||
CHECK_EQUAL(1.5, etl::get<double>(variant_duplicate_trivial));
|
||||
|
||||
etl::variant<std::string, std::string, double> variant_duplicate_nontrivial{1.5f};
|
||||
CHECK_EQUAL(etl::holds_alternative<double>(variant_duplicate_nontrivial), true);
|
||||
CHECK_EQUAL(1.5, etl::get<double>(variant_duplicate_nontrivial));
|
||||
|
||||
// Narrowing conversion should not be allowed.
|
||||
CHECK(!(etl::is_constructible<etl::variant<float>, double>::value));
|
||||
CHECK(!(etl::is_constructible<etl::variant<std::string, float>, double>::value));
|
||||
CHECK(!(std::is_constructible<etl::variant<int>, float>::value));
|
||||
CHECK(!(std::is_constructible<etl::variant<std::string, int>, float>::value));
|
||||
CHECK(!(std::is_constructible<etl::variant<short>, int>::value));
|
||||
CHECK(!(std::is_constructible<etl::variant<std::string, short>, int>::value));
|
||||
CHECK(!(std::is_constructible<etl::variant<unsigned int>, int>::value));
|
||||
CHECK(!(std::is_constructible<etl::variant<std::string, unsigned int>, 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<etl::variant<bool>, const int*>::value));
|
||||
CHECK(!(std::is_constructible<etl::variant<std::string, bool>, const int*>::value));
|
||||
CHECK(!(std::is_constructible<etl::variant<int>, ExplicitInt>::value));
|
||||
CHECK(!(std::is_constructible<etl::variant<std::string, int>, 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<etl::variant<int, long>, AmbiguousIntLong>::value));
|
||||
CHECK(!(std::is_constructible<etl::variant<std::string, int, long>, AmbiguousIntLong>::value));
|
||||
|
||||
// The converting constructor should not accept in-place type or index tags.
|
||||
CHECK((!std::is_constructible<etl::variant<etl::in_place_type_t<int>>, etl::in_place_type_t<int>>::value));
|
||||
CHECK((!std::is_constructible<etl::variant<std::string, etl::in_place_type_t<int>>, etl::in_place_type_t<int>>::value));
|
||||
CHECK((!std::is_constructible<etl::variant<etl::in_place_index_t<1U>>, etl::in_place_index_t<1U>>::value));
|
||||
CHECK((!std::is_constructible<etl::variant<std::string, etl::in_place_index_t<2U>>, 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<std::string>(variant_etl));
|
||||
|
||||
// Conversion from float to double required
|
||||
etl::variant<int, double> variant_double;
|
||||
variant_double = 1.5f;
|
||||
|
||||
CHECK_EQUAL(1.5, etl::get<double>(variant_double));
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user