This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Resolved status.
semiregular-box
mishandles self-assignmentSection: 25.7.3 [range.move.wrap] Status: Resolved Submitter: Casey Carter Opened: 2020-08-25 Last modified: 2023-02-07
Priority: 3
View all other issues in [range.move.wrap].
View all issues with Resolved status.
Discussion:
The exposition-only wrapper type semiregular-box
— specified in [range.semi.wrap]
— layers behaviors onto std::optional
so semiregular-box<T>
is
semiregular even when T
is only copy constructible. It provides copy and move assignment
operators when optional<T>
's are deleted:
(1.1) — […]
(1.2) — […]
(1.3) — If
assignable_from<T&, const T&>
is not modeled, the copy assignment operator is equivalent to:semiregular-box& operator=(const semiregular-box& that) noexcept(is_nothrow_copy_constructible_v<T>) { if (that) emplace(*that); else reset(); return *this; }(1.4) — If
assignable_from<T&, T>
is not modeled, the move assignment operator is equivalent to:semiregular-box& operator=(semiregular-box&& that) noexcept(is_nothrow_move_constructible_v<T>) { if (that) emplace(std::move(*that)); else reset(); return *this; }
How do these assignment operators handle self-assignment? When *this
is empty, that
will test as false
and reset()
has no effect, so the result state of the object is
the same. No problems so far. When *this
isn't empty, that
will test as true
,
and we evaluate optional::emplace(**this)
(resp. optional::emplace(std::move(**this)))
.
This outcome is not as pretty: emplace
is specified in 22.5.3.4 [optional.assign]/30:
"Effects: Calls *this = nullopt
. Then initializes the contained value as if
direct-non-list-initializing an object of type T
with the arguments
std::forward<Args>(args)...
." When the sole argument is an lvalue (resp. xvalue) of type
T
that denotes the optional
's stored value, emplace
will destroy that
stored value and then try to copy/move construct a new object at the same address from the dead object
that used to live there resulting in undefined behavior. Mandatory undefined behavior does not
meet the semantic requirements for the copyable
or movable
concepts, we should do better.
[2020-09-13; Reflector prioritization]
Set priority to 3 during reflector discussions.
Previous resolution [SUPERSEDED]:
This wording is relative to N4861.
Modify [range.semi.wrap] as indicated:
-1- Many types in this subclause are specified in terms of an exposition-only class template
semiregular-box
.semiregular-box<T>
behaves exactly likeoptional<T>
with the following differences:
(1.1) — […]
(1.2) — […]
(1.3) — If
assignable_from<T&, const T&>
is not modeled, the copy assignment operator is equivalent to:semiregular-box& operator=(const semiregular-box& that) noexcept(is_nothrow_copy_constructible_v<T>) { if (this != addressof(that)) { if (that) emplace(*that); else reset(); } return *this; }(1.4) — If
assignable_from<T&, T>
is not modeled, the move assignment operator is equivalent to:semiregular-box& operator=(semiregular-box&& that) noexcept(is_nothrow_move_constructible_v<T>) { reset(); if (that) emplace(std::move(*that));else reset();return *this; }
[2021-06-13 Resolved by the adoption of P2325R3 at the June 2021 plenary. Status changed: New → Resolved.]
Proposed resolution: