This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.

### 3585. Variant converting assignment with immovable alternative

Section: 20.7.3.4 [variant.assign] Status: WP Submitter: Barry Revzin Opened: 2021-09-01 Last modified: 2021-10-14

Priority: Not Prioritized

View all other issues in [variant.assign].

View all issues with WP status.

Discussion:

Example originally from StackOverflow but with a more reasonable example from Tim Song:

>     #include <variant>
>     #include <string>

>     struct A {
>       A() = default;
>       A(A&&) = delete;
>     };

>     int main() {
>       std::variant<A, std::string> v;
>       v = "hello";
>     }

There is implementation divergence here: libstdc++ rejects, libc++ and msvc accept.

20.7.3.4 [variant.assign] bullet (13.3) says that if we're changing the alternative in assignment and it is not the case that the converting construction won't throw (as in the above), then "Otherwise, equivalent to `operator=(variant(std::forward<T>(t)))`." That is, we defer to move assignment.

`variant<A, string>` isn't move-assignable (because `A` isn't move constructible). Per the wording in the standard, we have to reject this. libstdc++ follows the wording of the standard.

But we don't actually have to do a full move assignment here, since we know the situation we're in is changing the alternative, so the fact that `A` isn't move-assignable shouldn't matter. libc++ and msvc instead do something more direct, allowing the above program.

[2021-09-20; Reflector poll]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

[2021-10-14 Approved at October 2021 virtual plenary. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4892.

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

   > [Drafting note: This should cover the case that we want to cover: that if construction of `Tj` from `T` throws, we haven't yet changed any state in the variant.]

   >     template<class T> constexpr variant& operator=(T&& t) noexcept(see below);

   > > -11- Let `Tj` be a type that is determined as follows: build an imaginary function `FUN(Ti)` for each alternative type `Ti` for which `Ti x[] = {std::forward<T>(t)};` is well-formed for some invented variable `x`. 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.

   > > -12- Constraints: […]

   > > -13- Effects:

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

   > > 2. (13.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. (13.3) — Otherwise, equivalent to `operator=(variant(std::forward<T>(t)))emplace<j>(Tj(std::forward<T>(t)))`.