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.

### 3479. `semiregular-box` mishandles self-assignment

Section: 99 [range.semi.wrap] Status: Resolved Submitter: Casey Carter Opened: 2020-08-25 Last modified: 2021-06-14

Priority: 3

View all other issues in [range.semi.wrap].

View all issues with Resolved status.

Discussion:

The exposition-only wrapper type `semiregular-box` — specified in 99 [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.2) — […]

> 3. (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;
>        }

> 4. (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 20.6.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.

> 1. Modify 99 [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 like `optional<T>` with the following differences:

>    > 1. (1.1) — […]

>    > 2. (1.2) — […]

>    > 3. (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;
>    >        }

>    > 4. (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: