Make etl::variant capable for ROM placement and optimize runtime size (#1441)

Via an variadic_union, the internal storage is adjusted to be
able to be constexpr.
This commit is contained in:
Roland Reichwein 2026-05-18 08:14:26 +02:00 committed by GitHub
parent eba472fa3a
commit 373247e5c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 705 additions and 400 deletions

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,7 @@ SOFTWARE.
#include "etl/visitor.h"
#include "etl/private/variant_variadic.h"
#if ETL_USING_CPP14
#if ETL_USING_CPP11
#include <algorithm>
#include <array>
@ -504,7 +504,7 @@ namespace
test_variant_t variant_etl;
CHECK_TRUE((std::is_same< test_variant_t::type_list, etl::type_list<DefaultConstructible, int, std::string>>::value));
CHECK_TRUE((std::is_same<test_variant_t::type_list, etl::type_list<DefaultConstructible, int, std::string>>::value));
CHECK_TRUE(etl::holds_alternative<DefaultConstructible>(variant_etl));
CHECK_FALSE(etl::holds_alternative<int>(variant_etl));
@ -1904,6 +1904,7 @@ namespace
CHECK_EQUAL(32, res);
}
#if ETL_USING_CPP14
//*************************************************************************
TEST(test_variant_multiple_visit_auto_return)
{
@ -1942,6 +1943,7 @@ namespace
etl::visit<void>(f, variant1);
CHECK_EQUAL(false, variant_was_signed);
}
#endif // ETL_USING_CPP14
#if ETL_USING_CPP17
//*************************************************************************
@ -2371,6 +2373,169 @@ namespace
}
} // namespace
//*************************************************************************
// Tests for constexpr / ROM-placeable variant (trivially destructible types)
//*************************************************************************
#if ETL_USING_CPP17
namespace
{
// Verify that variant of trivially destructible types is itself trivially destructible.
static_assert(std::is_trivially_destructible<etl::variant<int, float, char>>::value, "variant<int, float, char> should be trivially destructible");
// Verify that variant of non-trivially destructible types is NOT trivially destructible.
static_assert(!std::is_trivially_destructible<etl::variant<int, std::string>>::value,
"variant<int, std::string> should NOT be trivially destructible");
// constexpr default construction
constexpr etl::variant<int, float, char> cv_default{};
static_assert(cv_default.index() == 0, "Default constructed variant should have index 0");
// constexpr construction from value
constexpr etl::variant<int, float, char> cv_int{42};
static_assert(cv_int.index() == 0, "variant holding int should have index 0");
static_assert(etl::get<int>(cv_int) == 42, "get<int> should return 42");
static_assert(etl::get<0>(cv_int) == 42, "get<0> should return 42");
constexpr etl::variant<int, float, char> cv_float{3.0f};
static_assert(cv_float.index() == 1, "variant holding float should have index 1");
constexpr etl::variant<int, float, char> cv_char{'A'};
static_assert(cv_char.index() == 2, "variant holding char should have index 2");
static_assert(etl::get<char>(cv_char) == 'A', "get<char> should return 'A'");
static_assert(etl::get<2>(cv_char) == 'A', "get<2> should return 'A'");
// constexpr construction with in_place_type
constexpr etl::variant<int, float, char> cv_ipt{etl::in_place_type_t<float>{}, 2.0f};
static_assert(cv_ipt.index() == 1, "in_place_type_t<float> should set index 1");
// constexpr construction with in_place_index
constexpr etl::variant<int, float, char> cv_ipi{etl::in_place_index_t<2>{}, 'Z'};
static_assert(cv_ipi.index() == 2, "in_place_index_t<2> should set index 2");
static_assert(etl::get<2>(cv_ipi) == 'Z', "get<2> should return 'Z'");
// constexpr holds_alternative
static_assert(etl::holds_alternative<int>(cv_int), "cv_int should hold int");
static_assert(!etl::holds_alternative<float>(cv_int), "cv_int should not hold float");
static_assert(etl::holds_alternative<float>(cv_float), "cv_float should hold float");
// constexpr get_if
static_assert(etl::get_if<int>(&cv_int) != nullptr, "get_if<int> should not be nullptr");
static_assert(*etl::get_if<int>(&cv_int) == 42, "get_if<int> should point to 42");
static_assert(etl::get_if<float>(&cv_int) == nullptr, "get_if<float> on int variant should be nullptr");
// Verify the constexpr variant can be used in ROM-like context
// (static constexpr / const at namespace scope should be placed in .rodata)
static constexpr etl::variant<int, float, char> rom_variant{100};
static_assert(etl::get<int>(rom_variant) == 100, "ROM variant should hold 100");
} // namespace
SUITE(test_variant_constexpr)
{
TEST(test_constexpr_default_construction)
{
constexpr etl::variant<int, float, char> v{};
CHECK_EQUAL(0U, v.index());
}
TEST(test_constexpr_value_construction)
{
constexpr etl::variant<int, float, char> v{42};
CHECK_EQUAL(0U, v.index());
CHECK_EQUAL(42, etl::get<int>(v));
}
TEST(test_constexpr_in_place_type)
{
constexpr etl::variant<int, float, char> v{etl::in_place_type_t<float>{}, 1.5f};
CHECK_EQUAL(1U, v.index());
CHECK_CLOSE(1.5f, etl::get<float>(v), 0.001f);
}
TEST(test_constexpr_in_place_index)
{
constexpr etl::variant<int, float, char> v{etl::in_place_index_t<2>{}, 'X'};
CHECK_EQUAL(2U, v.index());
CHECK_EQUAL('X', etl::get<char>(v));
}
TEST(test_constexpr_get_by_type)
{
constexpr etl::variant<int, float, char> v{99};
constexpr int value = etl::get<int>(v);
CHECK_EQUAL(99, value);
}
TEST(test_constexpr_get_by_index)
{
constexpr etl::variant<int, float, char> v{3.14f};
constexpr float value = etl::get<1>(v);
CHECK_CLOSE(3.14f, value, 0.001f);
}
TEST(test_constexpr_holds_alternative)
{
constexpr etl::variant<int, float, char> v{'B'};
CHECK(etl::holds_alternative<char>(v));
CHECK(!etl::holds_alternative<int>(v));
CHECK(!etl::holds_alternative<float>(v));
}
TEST(test_constexpr_get_if)
{
constexpr etl::variant<int, float, char> v{42};
CHECK(etl::get_if<int>(&v) != nullptr);
CHECK_EQUAL(42, *etl::get_if<int>(&v));
CHECK(etl::get_if<float>(&v) == nullptr);
}
TEST(test_trivially_destructible_trait)
{
using trivial_variant = etl::variant<int, float, char>;
using trivial_variant_1 = etl::variant<int>;
using trivial_variant_3 = etl::variant<int, double, long>;
using nontrivial_variant = etl::variant<int, std::string>;
using nontrivial_variant_1 = etl::variant<std::string>;
CHECK(std::is_trivially_destructible<trivial_variant>::value);
CHECK(std::is_trivially_destructible<trivial_variant_1>::value);
CHECK(std::is_trivially_destructible<trivial_variant_3>::value);
CHECK(!std::is_trivially_destructible<nontrivial_variant>::value);
CHECK(!std::is_trivially_destructible<nontrivial_variant_1>::value);
}
TEST(test_constexpr_rom_placement)
{
// This test verifies that a constexpr variant can be stored in ROM
// (static constexpr at function scope)
static constexpr etl::variant<int, float, char> v1{42};
static constexpr etl::variant<int, float, char> v2{3.14f};
static constexpr etl::variant<int, float, char> v3{'Z'};
CHECK_EQUAL(42, etl::get<int>(v1));
CHECK_CLOSE(3.14f, etl::get<float>(v2), 0.001f);
CHECK_EQUAL('Z', etl::get<char>(v3));
}
TEST(test_runtime_trivially_destructible_variant)
{
// Ensure trivially destructible variants still work at runtime too
etl::variant<int, float, char> v{10};
CHECK_EQUAL(10, etl::get<int>(v));
v = 2.5f;
CHECK_CLOSE(2.5f, etl::get<float>(v), 0.001f);
v = 'Q';
CHECK_EQUAL('Q', etl::get<char>(v));
v.emplace<0>(77);
CHECK_EQUAL(77, etl::get<0>(v));
}
}
#endif // ETL_USING_CPP17
#include "etl/private/diagnostic_pop.h"
#endif