Variant converting assignment with immovable alternative
Barry Revzin

Created on 2021-09-01.00:00:00 last changed 3 days ago


Date: 2021-09-20.11:21:54

Proposed resolution:

This wording is relative to N4892.

  1. Modify [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))).

Date: 2021-09-15.00:00:00

[ 2021-09-20; Reflector poll ]

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

Date: 2021-09-01.00:00:00

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.

[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.

Date User Action Args
2021-09-20 11:21:54adminsetmessages: + msg12048
2021-09-20 11:21:54adminsetstatus: new -> ready
2021-09-04 16:14:25adminsetmessages: + msg12025
2021-09-01 00:00:00admincreate