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.

### 3069. Move assigning `variant`'s subobject corrupts data

Section: 20.7.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 20.7.3.5 [variant.mod] destroy the currently contained value before initializing it to a new value. Assignments in 20.7.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.

1. Change 20.7.3.4 [variant.assign] as indicated:

   >     variant& operator=(const variant& rhs);

   > > -1- Let `j` be `rhs.index()`.

   > > -2- Effects:

   > > 1. (2.1) — If neither `*this` nor `rhs` holds a value, there is no effect.

   > > 2. (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.

   > > 3. (2.3) — Otherwise, if `index() == j`, assigns the value contained in `rhs` to the value contained in `*this`.

   > > 4. (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))`.

   > > 5. (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:

   > > 1. (7.1) — If neither `*this` nor `rhs` holds a value, there is no effect.

   > > 2. (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.

   > > 3. (7.3) — Otherwise, if `index() == j`, assigns `get<j>(std::move(rhs))` to the value contained in `*this`.

   > > 4. (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:

   > > 1. (11.1) — If `*this` holds a `Tj`, assigns `std::forward<T>(t)` to the value contained in `*this`.

   > > 2. (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))`.

   > > 3. (11.3) — Otherwise, equivalent to `emplace<j>(Tj{std::forward<T>(t)})operator=(variant(std::forward<T>(t)))`.

   > > […]