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:jberhs.index().[…]
(2.1) — If neither
*thisnorrhsholds a value, there is no effect.(2.2) — Otherwise, if
*thisholds a value butrhsdoes not, destroys the value contained in*thisand sets*thisto not hold a value.(2.3) — Otherwise, if
index() == j, assigns the value contained inrhsto the value contained in*this.
(2.4) — Otherwise, if eitheris_nothrow_copy_constructible_v<Tj>istrueoris_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:jberhs.index().[…]
(7.1) — If neither
*thisnorrhsholds a value, there is no effect.(7.2) — Otherwise, if
*thisholds a value butrhsdoes not, destroys the value contained in*thisand sets*thisto 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:Tjbe 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 alternativeTjwhich is the type of the contained value after assignment.[…]
(11.1) — If
*thisholds 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)))