This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
Section: 22.6.3.4 [variant.assign] Status: New Submitter: Antony Polukhin Opened: 2018-02-20 Last modified: 2020-09-06
Priority: 3
View all other issues in [variant.assign].
View all issues with New status.
Discussion:
variant::emplace functions in 22.6.3.5 [variant.mod] destroy the currently contained value before initializing it to a new value. Assignments in 22.6.3.4 [variant.assign] are described in terms on emplace.
This leads to situation, when move/copy assigning subobject from variant into the same variant corrupts data:#include <variant> #include <memory> #include <iostream> using str_t = std::string; using str_ptr_t = std::unique_ptr<str_t>; using var_t = std::variant<str_t, str_ptr_t>; int main() { var_t v = str_ptr_t{ new str_t{"Long string that does not fit into SS buffer and forces dynamic allocation"} }; // Any of the following lines corrupt the variant's content: v = *std::get<str_ptr_t>(v); //v = std::move(*std::get<str_ptr_t>(v)); std::cout << std::get<str_t>(v) << std::endl; // SEGV - 'str_t' inside 'v' is invalid }
Such behavior confuses users, especially those users who are used to boost::variant's behavior. Consider making variant assignments safer by defining them close to copy-and-swap.
[2018-06-18 after reflector discussion]
Priority set to 3; Antony volunteered to write a paper for Rapperswil.
Proposed resolution:
This wording is relative to N4727.
Change 22.6.3.4 [variant.assign] as indicated:
variant& operator=(const variant& rhs);-1- Let j be rhs.index().
-2- Effects:[…]
(2.1) — If neither *this nor rhs holds a value, there is no effect.
(2.2) — Otherwise, if *this holds a value but rhs does not, destroys the value contained in *this and sets *this to not hold a value.
(2.3) — Otherwise, if index() == j, assigns the value contained in rhs to the value contained in *this.
(2.4) — Otherwise, if either is_nothrow_copy_constructible_v<Tj> is true or is_nothrow_move_constructible_v<Tj> is false, equivalent to emplace<j>(get<j>(rhs)).(2.5) — Otherwise, equivalent to emplace<j>(Tj{get<j>(rhs)})
operator=(variant(rhs)).variant& operator=(variant&& rhs) noexcept(see below);-6- Let j be rhs.index().
-7- Effects:[…]
(7.1) — If neither *this nor rhs holds a value, there is no effect.
(7.2) — Otherwise, if *this holds a value but rhs does not, destroys the value contained in *this and sets *this to not hold a value.
(7.3) — Otherwise, if index() == j, assigns get<j>(std::move(rhs)) to the value contained in *this.
(7.4) — Otherwise, equivalent to emplace<j>(Tj{get<j>(std::move(rhs))}).
-10- Let Tj be a type that is determined as follows: build an imaginary function FUN(Ti) for each alternative type Ti. The overload FUN(Tj) selected by overload resolution for the expression FUN(std::forward<T>(t)) defines the alternative Tj which is the type of the contained value after assignment.
-11- Effects:[…]
(11.1) — If *this holds a Tj, assigns std::forward<T>(t) to the value contained in *this.
(11.2) — Otherwise, if is_nothrow_constructible_v<Tj, T> || !is_nothrow_move_constructible_v<Tj> is true, equivalent to emplace<j>(std::forward<T>(t)).(11.3) — Otherwise, equivalent to emplace<j>(Tj{std::forward<T>(t)})
operator=(variant(std::forward<T>(t))).