variant
's subobject corrupts dataSection: 22.6.3.4 [variant.assign] Status: New Submitter: Antony Polukhin Opened: 2018-02-20 Last modified: 2020-09-06
Priority: 3
View other active issues in [variant.assign].
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
.
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
-2- Effects:j
berhs.index()
.[…]
(2.1) — If neither
*this
norrhs
holds a value, there is no effect.(2.2) — Otherwise, if
*this
holds a value butrhs
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 inrhs
to the value contained in*this
.
(2.4) — Otherwise, if eitheris_nothrow_copy_constructible_v<Tj>
istrue
oris_nothrow_move_constructible_v<Tj>
isfalse
, equivalent toemplace<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
-7- Effects:j
berhs.index()
.[…]
(7.1) — If neither
*this
norrhs
holds a value, there is no effect.(7.2) — Otherwise, if
*this
holds a value butrhs
does not, destroys the value contained in*this
and sets*this
to not hold a value.(7.3) — Otherwise, if
index() == j
, assignsget<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
-11- Effects:Tj
be a type that is determined as follows: build an imaginary functionFUN(Ti)
for each alternative typeTi
. 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.[…]
(11.1) — If
*this
holds aTj
, assignsstd::forward<T>(t)
to the value contained in*this
.
(11.2) — Otherwise, ifis_nothrow_constructible_v<Tj, T> || !is_nothrow_move_constructible_v<Tj>
istrue
, equivalent toemplace<j>(std::forward<T>(t))
.(11.3) — Otherwise, equivalent to
emplace<j>(Tj{std::forward<T>(t)})
.operator=(variant(std::forward<T>(t)))