3121. tuple constructor constraints for UTypes&&... overloads

Section: 22.4.4.2 [tuple.cnstr] Status: C++23 Submitter: Matt Calabrese Opened: 2018-06-12 Last modified: 2023-11-22

Priority: 2

View other active issues in [tuple.cnstr].

View all other issues in [tuple.cnstr].

View all issues with C++23 status.

Discussion:

Currently the tuple constructors of the form:

template<class... UTypes>
EXPLICIT constexpr tuple(UTypes&&...);

are not properly constrained in that in the 1-element tuple case, the constraints do no short-circuit when the constructor would be (incorrectly) considered as a possible copy/move constructor candidate. libc++ has a workaround for this, but the additional short-circuiting does not actually appear in the working draft.

As an example of why this lack of short circuiting is a problem in practice, consider the following line:

bool a = std::is_copy_constructible_v<std::tuple<any>>;

The above code will cause a compile error because of a recursive trait definition. The copy constructibility check implies doing substitution into the UTypes&&... constructor overloads, which in turn will check if tuple<any> is convertible to any, which in turn will check if tuple<any> is copy constructible (and so the trait is dependent on itself).

I do not provide wording for the proposed fix in anticipation of requires clauses potentially changing how we do the specification, however, the basic solution should be similar to what we've done for other standard library types, which is to say that the very first constraint should be to check that if sizeof...(UTypes) == 1 and the type, after applying remove_cvref_t, is the tuple type itself, then we should force substitution failure rather than checking any further constraints.

[2018-06-23 after reflector discussion]

Priority set to 3

[2018-08-20, Jonathan provides wording]

[2018-08-20, Daniel comments]

The wording changes by this issue are very near to those suggested for LWG 3155.

[2018-11 San Diego Thursday night issue processing]

Jonathan to update wording - using conjunction. Priority set to 2

Previous resolution [SUPERSEDED]:

This wording is relative to N4762.

[2021-05-20 Tim updates wording]

The new wording below also resolves LWG 3155, relating to an allocator_arg_t tag argument being treated by this constructor template as converting to the first tuple element instead of as a tag. To minimize collateral damage, this wording takes this constructor out of overload resolution only if the tuple is of size 2 or 3, the first argument is an allocator_arg_t, but the first tuple element isn't of type allocator_arg_t (in both cases after removing cv/ref qualifiers). This avoids damaging tuples that actually contain an allocator_arg_t as the first element (which can be formed during uses-allocator construction, thanks to uses_allocator_construction_args).

The proposed wording has been implemented and tested on top of libstdc++.

[2021-08-20; LWG telecon]

Set status to Tentatively Ready after telecon review.

[2021-10-14 Approved at October 2021 virtual plenary. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4885, and also resolves LWG 3155.

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

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

    -?- Let disambiguating-constraint be:

    1. (?.1) — negation<is_same<remove_cvref_t<U0>, tuple>> if sizeof...(Types) is 1;

    2. (?.2) — otherwise, bool_constant<!is_same_v<remove_cvref_t<U0>, allocator_arg_t> || is_same_v<remove_cvref_t<T0>, allocator_arg_t>> if sizeof...(Types) is 2 or 3;

    3. (?.3) — otherwise, true_type.

    -12- Constraints:

    1. (12.1) — sizeof...(Types) equals sizeof...(UTypes), and

    2. (12.2) — sizeof...(Types) ≥ 1, and

    3. (12.3) — conjunction_v<disambiguating-constraint, is_constructible<Types, UTypes>...> is true is_constructible_v<Ti, Ui> is true for all i.

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

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

    !conjunction_v<is_convertible<UTypes, Types>...>