Merge cff9da11bd529beb1a907a1d20989659607683b2 into 5fe4a6cbd01f2f6ffe0cff839bbad168a668329f

This commit is contained in:
taltenbach 2026-10-19 01:57:02 +00:00 committed by GitHub
commit c36811132f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 221 additions and 47 deletions

View File

@ -261,6 +261,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.
//*******************************************
@ -1023,22 +1092,27 @@ 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_, 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)
{
static_assert(etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value, "Unsupported type");
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_, 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)
{
static_assert(etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value, "Unsupported type");
}
#include "diagnostic_pop.h"
@ -1056,20 +1130,19 @@ namespace etl
/// Construct from arguments.
//***************************************************************************
#include "diagnostic_uninitialized_push.h"
template <typename T, typename... TArgs, bool Trivial_ = Is_Trivially_Destructible_Suite, etl::enable_if_t<!Trivial_, int> = 0>
template <typename T, typename... TArgs, bool Trivial_ = Is_Trivially_Destructible_Suite,
etl::enable_if_t<!Trivial_ && etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value, int> = 0>
ETL_CONSTEXPR14 explicit variant(etl::in_place_type_t<T>, TArgs&&... args)
: base_type(index_of_type<T>::value)
{
static_assert(etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value, "Unsupported type");
construct_in_place_args<etl::remove_cvref_t<T> >(data, etl::forward<TArgs>(args)...);
}
template <typename T, typename... TArgs, bool Trivial_ = Is_Trivially_Destructible_Suite, etl::enable_if_t<Trivial_, int> = 0>
template <typename T, typename... TArgs, bool Trivial_ = Is_Trivially_Destructible_Suite,
etl::enable_if_t<Trivial_ && etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value, int> = 0>
constexpr explicit variant(etl::in_place_type_t<T>, TArgs&&... args)
: base_type(etl::in_place_index_t<index_of_type<T>::value>{}, etl::remove_cvref_t<T>(etl::forward<TArgs>(args)...), index_of_type<T>::value)
{
static_assert(etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value, "Unsupported type");
}
#include "diagnostic_pop.h"
@ -1077,22 +1150,20 @@ namespace etl
/// Construct from arguments.
//***************************************************************************
#include "diagnostic_uninitialized_push.h"
template <size_t Index, typename... TArgs, bool Trivial_ = Is_Trivially_Destructible_Suite, etl::enable_if_t<!Trivial_, int> = 0>
template <size_t Index, typename... TArgs, bool Trivial_ = Is_Trivially_Destructible_Suite,
etl::enable_if_t<!Trivial_ && (Index < sizeof...(TTypes)), int> = 0>
ETL_CONSTEXPR14 explicit variant(etl::in_place_index_t<Index>, TArgs&&... args)
: base_type(Index)
{
using type = type_from_index<Index>;
static_assert(etl::is_one_of<type, TTypes...>::value, "Unsupported type");
construct_in_place_args<type>(data, etl::forward<TArgs>(args)...);
}
template <size_t Index, typename... TArgs, bool Trivial_ = Is_Trivially_Destructible_Suite, etl::enable_if_t<Trivial_, int> = 0>
template <size_t Index, typename... TArgs, bool Trivial_ = Is_Trivially_Destructible_Suite,
etl::enable_if_t<Trivial_ && (Index < sizeof...(TTypes)), int> = 0>
constexpr explicit variant(etl::in_place_index_t<Index>, TArgs&&... args)
: base_type(etl::in_place_index_t<Index>{}, type_from_index<Index>(etl::forward<TArgs>(args)...), Index)
{
using type = type_from_index<Index>;
static_assert(etl::is_one_of<type, TTypes...>::value, "Unsupported type");
}
#include "diagnostic_pop.h"
@ -1101,12 +1172,10 @@ namespace etl
/// Construct from type, initializer_list and arguments.
//***************************************************************************
#include "diagnostic_uninitialized_push.h"
template <typename T, typename U, typename... TArgs >
template <typename T, typename U, typename... TArgs, etl::enable_if_t<etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value, int> = 0>
ETL_CONSTEXPR14 explicit variant(etl::in_place_type_t<T>, std::initializer_list<U> init, TArgs&&... args)
: base_type(index_of_type<T>::value)
{
static_assert(etl::is_one_of<etl::remove_cvref_t<T>, TTypes...>::value, "Unsupported type");
construct_in_place_args<etl::remove_cvref_t<T> >(data, init, etl::forward<TArgs>(args)...);
}
#include "diagnostic_pop.h"
@ -1115,13 +1184,11 @@ namespace etl
/// Construct from index, initializer_list and arguments.
//***************************************************************************
#include "diagnostic_uninitialized_push.h"
template <size_t Index, typename U, typename... TArgs >
template <size_t Index, typename U, typename... TArgs, etl::enable_if_t<(Index < sizeof...(TTypes)), int> = 0>
ETL_CONSTEXPR14 explicit variant(etl::in_place_index_t<Index>, std::initializer_list<U> init, TArgs&&... args)
: base_type(Index)
{
using type = type_from_index<Index>;
static_assert(etl::is_one_of<type, TTypes...>::value, "Unsupported type");
construct_in_place_args<type>(data, init, etl::forward<TArgs>(args)...);
}
#include "diagnostic_pop.h"
@ -1149,11 +1216,9 @@ namespace etl
//***************************************************************************
/// Emplace by type with variadic constructor parameters.
//***************************************************************************
template <typename T, typename... TArgs>
template <typename T, typename... TArgs, etl::enable_if_t<etl::is_one_of<T, TTypes...>::value, int> = 0>
T& emplace(TArgs&&... args) ETL_NOEXCEPT_IF((etl::is_nothrow_constructible<etl::remove_cvref_t<T>, TArgs...>::value))
{
static_assert(etl::is_one_of<T, TTypes...>::value, "Unsupported type");
using type = etl::remove_cvref_t<T>;
do_destroy();
@ -1168,12 +1233,10 @@ namespace etl
//***************************************************************************
/// Emplace by type with variadic constructor parameters.
//***************************************************************************
template <typename T, typename U, typename... TArgs>
template <typename T, typename U, typename... TArgs, etl::enable_if_t<etl::is_one_of<T, TTypes...>::value, int> = 0>
T& emplace(std::initializer_list<U> il, TArgs&&... args)
ETL_NOEXCEPT_IF((etl::is_nothrow_constructible<etl::remove_cvref_t<T>, std::initializer_list<U>, TArgs...>::value))
{
static_assert(etl::is_one_of<T, TTypes...>::value, "Unsupported type");
using type = etl::remove_cvref_t<T>;
do_destroy();
@ -1188,12 +1251,10 @@ namespace etl
//***************************************************************************
/// Emplace by index with variadic constructor parameters.
//***************************************************************************
template <size_t Index, typename... TArgs>
template <size_t Index, typename... TArgs, etl::enable_if_t<(Index < sizeof...(TTypes)), int> = 0>
typename etl::variant_alternative_t<Index, variant<TTypes...> >& emplace(TArgs&&... args)
ETL_NOEXCEPT_IF((etl::is_nothrow_constructible<type_from_index<Index>, TArgs...>::value))
{
static_assert(Index < sizeof...(TTypes), "Index out of range");
using type = type_from_index<Index>;
do_destroy();
@ -1208,12 +1269,10 @@ namespace etl
//***************************************************************************
/// Emplace by index with variadic constructor parameters.
//***************************************************************************
template <size_t Index, typename U, typename... TArgs>
template <size_t Index, typename U, typename... TArgs, etl::enable_if_t<(Index < sizeof...(TTypes)), int> = 0>
typename etl::variant_alternative_t<Index, variant<TTypes...> >& emplace(std::initializer_list<U> il, TArgs&&... args)
ETL_NOEXCEPT_IF((etl::is_nothrow_constructible<type_from_index<Index>, std::initializer_list<U>, TArgs...>::value))
{
static_assert(Index < sizeof...(TTypes), "Index out of range");
using type = type_from_index<Index>;
do_destroy();
@ -1229,17 +1288,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, 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>;
static_assert(etl::is_one_of<type, TTypes...>::value, "Unsupported type");
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;
}

View File

@ -807,7 +807,7 @@ namespace
}
//*************************************************************************
TEST(test_constructor_value)
TEST(test_constructor_value_no_conversion)
{
// Char.
char c = 'a';
@ -831,6 +831,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)
{
@ -1379,12 +1474,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));
}
//*************************************************************************
@ -2932,6 +3034,21 @@ namespace
CHECK(etl::get_if<int>(&v1) != nullptr);
}
//*************************************************************************
TEST(test_variant_implicit_conversion_constraints)
{
struct overloaded
{
overloaded(etl::variant<char, int>&&) {}
overloaded(etl::variant<float, NonTrivialNonMovable>&&) {}
};
/* Construct from value, trivially destructible suite */
overloaded(1);
/* Construct from value, non-trivially destructible suite */
overloaded(1.0f);
}
//*************************************************************************
// Tests for noexcept properties of etl::variant
// The noexcept specs only take effect when ETL_USING_EXCEPTIONS is enabled,