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

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

Section: 22.4.4.1 [tuple.cnstr] Status: WP Submitter: Matt Calabrese Opened: 2018-06-12 Last modified: 2021-10-14

Priority: 2

View other active issues in [tuple.cnstr].

View all other issues in [tuple.cnstr].

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

> - Modify 22.4.4.1 [tuple.cnstr] as indicated:

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

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

>   > > -10- Remarks: This constructor shall not participate in overload resolution unless `sizeof...(Types) == sizeof...(UTypes)` and `sizeof...(Types) >= 1` and `(sizeof...(Types) > 1 || !is_same_v<remove_cvref_t<U0>, tuple>)` and `is_constructible_v<Ti, Ui&&>` is `true` for all `i`. The expression inside `explicit` is equivalent to:

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

[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.1 [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>...>`