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

Section: 23.5 [tuple], 23.6 [optional], 23.11.1.2.1 [unique.ptr.single.ctor] Status: Open Submitter: United States Opened: 2017-02-03 Last modified: 2017-07-16

Priority: 2

View all other issues in [tuple].

View all issues with Open 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

Proposed resolution:

This wording is relative to N4640.

  1. Modify 23.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 23.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 ith type in Types, and Ui is the ith type in UTypes.

    […]

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

    -16- Effects: For all i, initializes the ith 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 ith type in Types, and Ui is the ith 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 23.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 ith type in Types, and Ui is the ith 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.