This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++20 status.

### 2899. `is_(nothrow_)move_constructible` and `tuple`, `optional` and `unique_ptr`

Section: 20.5 [tuple], 20.6 [optional], 20.11.1.3.2 [unique.ptr.single.ctor] Status: C++20 Submitter: United States Opened: 2017-02-03 Last modified: 2021-02-25

Priority: 2

View all other issues in [tuple].

View all issues with C++20 status.

Discussion:

Addresses US 110

The move constructors for `tuple`, `optional`, and `unique_ptr` should return false for `is_(nothrow_)move_constructible_v<TYPE>` when their corresponding Requires clauses are not satisfied, as there are now several library clauses that are defined in terms of these traits. The same concern applies to the move-assignment operator. Note that `pair` and `variant` already satisfy this constraint.

[2017-02-26, Scott Schurr provides wording]

[ 2017-06-27 P2 after 5 positive votes on c++std-lib. ]

[2016-07, Toronto Thursday night issues processing]

The description doesn't match the resolution; Alisdair to investigate. Status to Open

Previous resolution [SUPERSEDED]:

> This wording is relative to N4640.

> 1. Modify 20.5.3 [tuple.tuple] as indicated:

>    >     // 20.5.3.1, tuple construction
>    >     EXPLICIT constexpr tuple();
>    >     EXPLICIT constexpr tuple(const Types&...); // only if sizeof...(Types) >= 1
>    >     template <class... UTypes>
>    >     EXPLICIT constexpr tuple(UTypes&&...) noexcept(see below); // only if sizeof...(Types) >= 1

>    >     tuple(const tuple&) = default;
>    >     tuple(tuple&&) = default;

>    >     template <class... UTypes>
>    >     EXPLICIT constexpr tuple(const tuple<UTypes...>&);
>    >     template <class... UTypes>
>    >     EXPLICIT constexpr tuple(tuple<UTypes...>&&) noexcept(see below);

>    >     template <class U1, class U2>
>    >     EXPLICIT constexpr tuple(const pair<U1, U2>&); // only if sizeof...(Types) == 2
>    >     template <class U1, class U2>
>    >     EXPLICIT constexpr tuple(pair<U1, U2>&&) noexcept(see below); // only if sizeof...(Types) == 2

>    >     […]

>    >     // 20.5.3.2, tuple assignment
>    >     tuple& operator=(const tuple&);
>    >     tuple& operator=(tuple&&) noexcept(see below);

>    >     template <class... UTypes>
>    >     tuple& operator=(const tuple<UTypes...>&);
>    >     template <class... UTypes>
>    >     tuple& operator=(tuple<UTypes...>&&) noexcept(see below);

>    >     template <class U1, class U2>
>    >     tuple& operator=(const pair<U1, U2>&); // only if sizeof...(Types) == 2
>    >     template <class U1, class U2>
>    >     tuple& operator=(pair<U1, U2>&&) noexcept(see below); // only if sizeof...(Types) == 2

> 2. Modify 20.5.3.1 [tuple.cnstr] as indicated:

>    >     template <class... UTypes> EXPLICIT constexpr tuple(UTypes&&... u) noexcept(see below);

>    > > -8- Effects: Initializes the elements in the tuple with the corresponding value in `std::forward<UTypes>(u)`.

>    > > -9- Remarks: This constructor shall not participate in overload resolution unless `sizeof...(Types) == sizeof...(UTypes)` and `sizeof...(Types) >= 1` and `is_constructible_v<Ti, Ui&&>` is `true` for all `i`. The constructor is explicit if and only if `is_convertible_v<Ui&&, Ti>` is `false` for at least one `i`. The expression inside `noexcept` is equivalent to the logical AND of the following expressions:

>    > > >     is_nothrow_constructible_v<Ti, Ui&&>

>    > > where `Ti` is the `i`th type in `Types`, and `Ui` is the `i`th type in `UTypes`.

>    > […]

>    >     template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u) noexcept(see below);

>    > > -16- Effects: For all `i`, initializes the `i`th element of `*this` with `std::forward<Ui>(get<i>(u))`.

>    > > -17- Remarks: This constructor shall not participate in overload resolution unless

>    > > […]

>    > > The constructor is explicit if and only if `is_convertible_v<Ui&&, Ti>` is `false` for at least one `i`. The expression inside `noexcept` is equivalent to the logical AND of the following expressions:

>    > > >     is_nothrow_constructible_v<Ti, Ui&&>

>    > > where `Ti` is the `i`th type in `Types`, and `Ui` is the `i`th type in `UTypes`.

>    > […]

>    >     template <class U1, class U2> EXPLICIT constexpr tuple(pair<U1, U2>&& u) noexcept(see below);

>    > > -21- Effects: Initializes the first element with `std::forward<U1>(u.first)` and the second element with `std::forward<U2>(u.second)`.

>    > > -22- Remarks: This constructor shall not participate in overload resolution unless `sizeof...(Types) == 2`, `is_constructible_v<T0, U1&&>` is `true` and `is_constructible_v<T1, U2&&>` is `true`.

>    > > -23- The constructor is explicit if and only if `is_convertible_v<U1&&, T0>` is `false` or `is_convertible_v<U2&&, T1>` is `false`. The expression inside `noexcept` is equivalent to:

>    > > >     is_nothrow_constructible_v<T0, U1&&> && is_nothrow_constructible_v<T1, U2&&>

> 3. Modify 20.5.3.2 [tuple.assign] as indicated:

>    >     template <class... UTypes> tuple& operator=(tuple<UTypes...>&& u) noexcept(see below);

>    > > -12- Effects: For all `i`, assigns `std::forward<Ui>(get<i>(u))` to `get<i>(*this)`.

>    > > -13- Remarks: This operator shall not participate in overload resolution unless `is_assignable_v<Ti&, Ui&&> == true` for all `i` and `sizeof...(Types) == sizeof...(UTypes)`. The expression inside `noexcept` is equivalent to the logical AND of the following expressions:

>    > > >     is_nothrow_assignable_v<Ti&, Ui&&>

>    > > where `Ti` is the `i`th type in `Types`, and `Ui` is the `i`th type in `UTypes`.

>    > > -14- Returns: `*this`.

>    > […]

>    >     template <class U1, class U2> tuple& operator=(pair<U1, U2>&& u) noexcept(see below);

>    > > -18- Effects: Assigns `std::forward<U1>(u.first)` to the first element of `*this` and `std::forward<U2>(u.second)` to the second element of `*this`.

>    > > -19- Remarks: This operator shall not participate in overload resolution unless `sizeof...(Types) == 2` and `is_assignable_v<T0&, U1&&>` is `true` for the first type `T0` in `Types` and `is_assignable_v<T1&, U2&&>` is `true` for the second type `T1` in `Types`. The expression inside `noexcept` is equivalent to:

>    > > >     is_nothrow_assignable_v<T0&, U1&&> && is_nothrow_assignable_v<T1&, U2&&>

>    > > -20- Returns: `*this`.

[2019-02-14; Jonathan comments and provides revised wording]

The suggested change was already made to `std::optional` by LWG 2756. The current P/R for 2899 doesn't resolve the issue for `std::tuple` or `std::unique_ptr`. I hope the following alternative does.

[2019-02; Kona Wednesday night issue processing]

Status to Ready

Proposed resolution:

This wording is relative to N4800.

1. Modify 20.5.3.1 [tuple.cnstr] as indicated:

   >     tuple(tuple&& u) = default;

   > > -13- Requires Constraints: `is_move_constructible_v<Ti>` is `true` for all `i`.

   > > -14- Effects: For all `i`, initializes the `i`th element of `*this` with `std::forward<Ti>(get<i>(u))`.

2. Modify 20.11.1.3.2 [unique.ptr.single.ctor] as indicated:

   >     unique_ptr(unique_ptr&& u) noexcept;

   > > -?- Constraints: `is_move_constructible_v<D>` is `true`.

   > > -15- Requires: If `D` is not a reference type, `D` shall satisfy the Cpp17MoveConstructible requirements (Table 26). Construction of the deleter from an rvalue of type `D` shall not throw an exception.

   > > […]

3. Modify 20.11.1.3.4 [unique.ptr.single.asgn] as indicated:

   >     unique_ptr& operator=(unique_ptr&& u) noexcept;

   > > -?- Constraints: `is_move_assignable_v<D>` is `true`.

   > > -1- Requires: If `D` is not a reference type, `D` shall satisfy the Cpp17MoveAssignable requirements (Table 28) and assignment of the deleter from an rvalue of type `D` shall not throw an exception. Otherwise, `D` is a reference type; `remove_reference_t<D>` shall satisfy the Cpp17CopyAssignable requirements and assignment of the deleter from an lvalue of type `D` shall not throw an exception.

   > > -2- Effects: Calls `reset(u.release())` followed by `get_deleter() = std::forward<D>(u.get_deleter())`.

   > > […]