4045. tuple can create dangling references from tuple-like

Section: 22.4.4.2 [tuple.cnstr] Status: WP Submitter: Jonathan Wakely Opened: 2024-01-24 Last modified: 2024-04-02

Priority: Not Prioritized

View other active issues in [tuple.cnstr].

View all other issues in [tuple.cnstr].

View all issues with WP status.

Discussion:

P2165R4 (Compatibility between tuple, pair and tuple-like objects) added two new constructors to std::tuple:


  template<tuple-likeUTuple>
    constexpr explicit(see below ) tuple(UTuple&& u);

and the allocator-extended equivalent. Unlike the existing constructors taking a single parameter of tuple type, these new constructors are not defined as deleted if they would create a dangling reference to a temporary. The existing constructors gained that restriction from P2255R2 (A type trait to detect reference binding to temporary) which was approved one meeting before P2165R4 so LWG seem to have missed the inconsistency.

The proposal also added a new constructor for std::pair:


  template<pair-like P> constexpr explicit(see below) pair(P&& p);

This is deleted if it would create a dangling reference, although that seems to be an almost accidental consequence of adding the new signature after existing ones which already have the Remarks: about being deleted.

[2024-03-12; Reflector poll]

Set status to Tentatively Ready after eleven votes in favour during reflector poll.

[Tokyo 2024-03-23; Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4971.

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

    
    template<tuple-like UTuple>
      constexpr explicit(see below) tuple(UTuple&& u);
    

    -28- Let I be the pack 0, 1, ..., (sizeof...(Types) - 1).

    -29- Constraints:

    1. (29.1) – different-from<UTuple, tuple> (25.5.2 [range.utility.helpers]) is true,
    2. (29.2) – remove_cvref_t<UTuple> is not a specialization of ranges::subrange,
    3. (29.3) – sizeof...(Types) equals tuple_size_v<remove_cvref_t<UTuple>>,
    4. (29.4) – (is_constructible_v<Types, decltype(get<I>(std::forward<UTuple>(u)))> && ...) is true, and
    5. (29.5) – either sizeof...(Types) is not 1, or (when Types... expands to T) is_convertible_v<UTuple, T> and is_constructible_v<T, UTuple> are both false.

    -30- Effects: For all i, initializes the ith element of *this with get<i>(std::forward<UTuple>(u)).

    -31- Remarks: The expression inside explicit is equivalent to:

      !(is_convertible_v<decltype(get<I>(std::forward<UTuple>(u))), Types> && ...)
    

    The constructor is defined as deleted if

      (reference_constructs_from_temporary_v<Types, decltype(get<I>(std::forward<UTuple>(u)))> || ...)
    

    is true.