Section: 22.6.3.4 [variant.assign] Status: C++23 Submitter: Barry Revzin Opened: 2021-09-01 Last modified: 2023-11-22
Priority: Not Prioritized
View other active issues in [variant.assign].
View all other issues in [variant.assign].
View all issues with C++23 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.
22.6.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 tooperator=(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.
Modify 22.6.3.4 [variant.assign] as indicated:
[Drafting note: This should cover the case that we want to cover: that if construction of
Tj
fromT
throws, we haven't yet changed any state in the variant.]
template<class T> constexpr variant& operator=(T&& t) noexcept(see below);-11- Let
-12- Constraints: […] -13- Effects:Tj
be a type that is determined as follows: build an imaginary functionFUN(Ti)
for each alternative typeTi
for whichTi x[] = {std::forward<T>(t)};
is well-formed for some invented variablex
. The overloadFUN(Tj)
selected by overload resolution for the expressionFUN(std::forward<T>(t))
defines the alternativeTj
which is the type of the contained value after assignment.
(13.1) — If
*this
holds aTj
, assignsstd::forward<T>(t)
to the value contained in*this
.(13.2) — Otherwise, if
is_nothrow_constructible_v<Tj, T> || !is_nothrow_move_constructible_v<Tj>
istrue
, equivalent toemplace<j>(std::forward<T>(t))
.(13.3) — Otherwise, equivalent to
.
operator=(variant(std::forward<T>(t)))emplace<j>(Tj(std::forward<T>(t)))