Fix variant emplace to be valueless after a throwing construction (#1510)

variant::do_destroy() now always resets type_id to variant_npos after
destroying the active alternative. emplace()/operator= set the new
discriminator only after successful construction, so if the constructor
throws the variant is left valueless (index() == variant_npos) instead of
reporting the stale old index.

Add regression test test_emplace_throwing_is_valueless_by_exception
covering both the trivially-destructible (union) and non-trivially-
destructible (buffer) storage suites.

Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>
This commit is contained in:
Roland Reichwein 2026-07-20 12:26:02 +02:00 committed by GitHub
parent 08b9c371a0
commit a74c33bab9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 67 additions and 0 deletions

View File

@ -1400,6 +1400,8 @@ namespace etl
void do_destroy()
{
do_destroy_impl(etl::integral_constant<bool, Is_Trivially_Destructible_Suite>{});
type_id = variant_npos;
}
void do_destroy_impl(etl::integral_constant<bool, true>)

View File

@ -1097,6 +1097,71 @@ namespace
}
#endif
#if ETL_USING_EXCEPTIONS
//*************************************************************************
// A throwing emplace() must leave the variant valueless-after-exception
// (index() == variant_npos) rather than reporting the stale old index.
struct ThrowOnConstruct
{
struct exception
{
};
ThrowOnConstruct() = default;
explicit ThrowOnConstruct(int)
{
throw exception();
}
};
TEST(test_emplace_throwing_is_valueless_by_exception)
{
// Trivially destructible suite (variadic_union storage).
{
etl::variant<int, ThrowOnConstruct> v;
v.emplace<int>(42);
CHECK(!v.valueless_by_exception());
CHECK_EQUAL(0U, v.index());
bool threw = false;
try
{
v.emplace<ThrowOnConstruct>(1);
}
catch (const ThrowOnConstruct::exception&)
{
threw = true;
}
CHECK(threw);
CHECK(v.valueless_by_exception());
CHECK_EQUAL(etl::variant_npos, v.index());
}
// Non-trivially destructible suite (uninitialized_buffer storage).
{
etl::variant<std::string, ThrowOnConstruct> v;
v.emplace<std::string>("Some Text");
CHECK(!v.valueless_by_exception());
CHECK_EQUAL(0U, v.index());
bool threw = false;
try
{
v.emplace<1>(1);
}
catch (const ThrowOnConstruct::exception&)
{
threw = true;
}
CHECK(threw);
CHECK(v.valueless_by_exception());
CHECK_EQUAL(etl::variant_npos, v.index());
}
}
#endif
//*************************************************************************
TEST(test_copy_constructor)
{