Skip to content

Commit 5173c19

Browse files
committed
Compare the index instead of visiting in assignment from a value when nothing needs to be destroyed
1 parent 9390c19 commit 5173c19

2 files changed

Lines changed: 77 additions & 6 deletions

File tree

‎include/iris/rvariant/rvariant.hpp‎

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,9 @@ struct relops_visitor;
137137
template<class... Ts>
138138
struct rvariant_base
139139
{
140-
private:
140+
protected:
141141
static constexpr bool need_destructor_call = !std::conjunction_v<std::is_trivially_destructible<Ts>...>;
142142

143-
protected:
144143
using storage_type = make_variadic_union_t<Ts...>;
145144
static constexpr bool never_valueless = storage_type::never_valueless;
146145

@@ -663,16 +662,48 @@ IRIS_RVARIANT_ALWAYS_THROWING_UNREACHABLE_BEGIN
663662
constexpr std::size_t j = no_narrowing_resolution<T, Ts...>::index;
664663
static_assert(j != std::variant_npos);
665664

665+
// TC(noexcept) && MC(throw) => A maybe valueless if | never |
666+
// TC(noexcept) && MC(noexcept) => A maybe valueless if | never |
667+
// TC(throw) && MC(throw) => A maybe valueless if | TC throws => yes | MC throws => yes |
668+
// TC(throw) && MC(noexcept) => B maybe valueless if | never |
669+
if constexpr (!base_type::need_destructor_call) {
670+
// Nothing needs to be destroyed, so a comparison of the index replaces the visit
671+
if (this->index_ == j) {
672+
detail::raw_get<j>(this->storage_) = std::forward<T>(t);
673+
674+
} else if constexpr (std::is_nothrow_constructible_v<Tj, T> || !std::is_nothrow_move_constructible_v<Tj>) {
675+
#ifndef NDEBUG
676+
// Self-assign on non-valueless instance ALWAYS leads to UB.
677+
// For details, see the comments on `emplace`.
678+
assert(
679+
(this->index_ == detail::variant_npos<sizeof...(Ts)> || (
680+
static_cast<void const*>(std::addressof(t)) != static_cast<void const*>(this) &&
681+
static_cast<void const*>(std::addressof(t)) != static_cast<void const*>(std::addressof(this->storage_))
682+
)) &&
683+
"Self-assigning `variant` will lead to undefined behavior because the standard specifies `emplace` to destruct the contained object *before* emplacing the new value ([variant.mod])."
684+
);
685+
#endif
686+
static_assert(std::is_nothrow_constructible_v<Tj, T> || !base_type::never_valueless);
687+
if constexpr (!std::is_nothrow_constructible_v<Tj, T>) {
688+
this->index_ = detail::variant_npos<sizeof...(Ts)>;
689+
}
690+
detail::alternative_constructor<j>::construct(this->storage_, std::forward<T>(t));
691+
this->index_ = j;
692+
693+
} else {
694+
Tj tmp(std::forward<T>(t));
695+
detail::alternative_constructor<j>::construct(this->storage_, std::move(tmp)); // B
696+
this->index_ = j;
697+
}
698+
return *this;
699+
}
700+
666701
this->raw_visit([this, &t]<std::size_t i, class Ti>(std::in_place_index_t<i>, [[maybe_unused]] Ti& ti)
667702
noexcept(detail::variant_nothrow_assignable<Tj, T>::value)
668703
{
669704
if constexpr (i == j) {
670705
ti = std::forward<T>(t);
671706
} else {
672-
// TC(noexcept) && MC(throw) => A maybe valueless if | never |
673-
// TC(noexcept) && MC(noexcept) => A maybe valueless if | never |
674-
// TC(throw) && MC(throw) => A maybe valueless if | TC throws => yes | MC throws => yes |
675-
// TC(throw) && MC(noexcept) => B maybe valueless if | never |
676707
if constexpr (std::is_nothrow_constructible_v<Tj, T> || !std::is_nothrow_move_constructible_v<Tj>) {
677708
#ifndef NDEBUG
678709
// Self-assign on non-valueless instance ALWAYS leads to UB.

‎test/rvariant/rvariant.cpp‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,46 @@ TEST_CASE("generic assignment")
11281128
REQUIRE_NOTHROW(a = MC_Thrower::potentially_throwing);
11291129
CHECK(a.valueless_by_exception() == false);
11301130
}
1131+
1132+
{
1133+
struct throwing_t {};
1134+
struct potentially_throwing_t {};
1135+
struct TC_Thrower
1136+
{
1137+
struct exception {};
1138+
TC_Thrower(throwing_t) noexcept(false) { throw exception{}; } // NOLINT(hicpp-exception-baseclass)
1139+
TC_Thrower(potentially_throwing_t) noexcept(false) {}
1140+
};
1141+
STATIC_REQUIRE(std::is_trivially_destructible_v<TC_Thrower>);
1142+
STATIC_REQUIRE(std::is_nothrow_move_constructible_v<TC_Thrower>);
1143+
{
1144+
iris::rvariant<int, TC_Thrower> a;
1145+
REQUIRE_THROWS_AS(a = throwing_t{}, TC_Thrower::exception);
1146+
CHECK(a.index() == 0);
1147+
}
1148+
{
1149+
iris::rvariant<int, TC_Thrower> a;
1150+
REQUIRE_NOTHROW(a = potentially_throwing_t{});
1151+
CHECK(a.index() == 1);
1152+
}
1153+
}
1154+
STATIC_CHECK([] {
1155+
iris::rvariant<int, float> a = 42;
1156+
a = 3.14f;
1157+
a = 33;
1158+
return a.index() == 0 && iris::get<0>(a) == 33;
1159+
}());
1160+
1161+
// Alternatives that need to be destroyed
1162+
{
1163+
iris::rvariant<int, std::string> a = 42;
1164+
a = std::string("abc");
1165+
REQUIRE(a.index() == 1);
1166+
CHECK(iris::get<1>(a) == "abc");
1167+
a = 33;
1168+
REQUIRE(a.index() == 0);
1169+
CHECK(iris::get<0>(a) == 33);
1170+
}
11311171
}
11321172

11331173
TEST_CASE("emplace")

0 commit comments

Comments
 (0)