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

### 2312. `tuple`'s constructor constraints need to be phrased more precisely

Section: 22.4.4.1 [tuple.cnstr] Status: C++17 Submitter: Stephan T. Lavavej Opened: 2013-09-21 Last modified: 2017-07-30

Priority: 2

View other active issues in [tuple.cnstr].

View all other issues in [tuple.cnstr].

View all issues with C++17 status.

Discussion:

Consider the following code:

>     void meow(tuple<long, long>) { puts("Two"); }

>     void meow(tuple<long, long, long>) { puts("Three"); }

>     tuple<int, int, int> t(0, 0, 0);

>     meow(t);

This should compile and print "Three" because `tuple<long, long>`'s constructor from `const tuple<int, int, int>&` should remove itself from overload resolution. Implementations sensibly do this, but the Standard doesn't actually say it!

In this case, `Types` is "`long, long`" and `UTypes` is "`int, int, int`". 22.4.4.1 [tuple.cnstr]/3 says "let `i` be in the range `[0,sizeof...(Types))` in order", which is `[0, 2)`. Then /17 says "Remark: This constructor shall not participate in overload resolution unless `const Ui&` is implicitly convertible to `Ti` for all `i`." Interpreted literally, this is true! /15 says "Requires: `sizeof...(Types) == sizeof...(UTypes)`." but requiring the sizes to be identical doesn't help. Only the special phrase "shall not participate in overload resolution unless" mandates SFINAE/`enable_if` machinery.

The wording that we need is almost available in the Requires paragraphs, except that the Requires paragraphs say "`is_constructible`" while the Remark paragraphs say "is implicitly convertible", which is the correct thing for the SFINAE constraints to check. My proposed resolution is to unify the Requires and Remark paragraphs, after which there will be no need for Requires (when a constructor participates in overload resolution if and only if `X` is true, then there's no need for it to Require that `X` is true).

Note: 21.3.5.4 [meta.unary.prop]/6 specifies `is_constructible<To, From>` and 21.3.7 [meta.rel]/4 specifies `is_convertible<From, To>`. Both are specified in terms of "`template <class T> typename add_rvalue_reference<T>::type create();`". Therefore, passing `From` and `From&&` is equivalent, regardless of whether `From` is an object type, an lvalue reference, or an rvalue reference.

Also note that 22.4.4.1 [tuple.cnstr]/3 defines `T0` and `T1` so we don't need to repeat their definitions.

[2014-10-05, Daniel comments]

This issue is closely related to LWG 2419.

[2015-02, Cologne]

AM: Howard wants to do something in this space and I want to wait for him to get a paper in.

Postponed.

[2015-05, Lenexa]

MC: handled by Daniel's tuple paper N4387
STL: look at status after N4387 applied.

[2015-05-05, Daniel comments]

N4387 doesn't touch these area intentionally. I agree with Howard that a different option exists that would introduce a TupleLike concept. Some implementations currently take advantage of this choice and this P/R would forbid them, which seems unfortunate to me.

[2015-09, Telecon]

Proposed resolution is obsolete.
Howard has considered writing a paper.
Status quo gives more implementation freedom.

Previous resolution [SUPERSEDED]:

> This wording is relative to N3691.

> 1. Edit 22.4.4.1 [tuple.cnstr] as indicated:

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

>    > > -8- Requires: `sizeof...(Types) == sizeof...(UTypes)`. `is_constructible<Ti, Ui&&>::value` is true for all i.

>    > > […]

>    > > -10- Remark: This constructor shall not participate in overload resolution unless each type in `UTypes` is implicitly convertible to its corresponding type in `Types` `sizeof...(Types) == sizeof...(UTypes)` and both `is_constructible<Ti, Ui>::value` and `is_convertible<Ui, Ti>::value` are true for all i.

>    > […]

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

>    > > -15- Requires: `sizeof...(Types) == sizeof...(UTypes)`. `is_constructible<Ti, const Ui&>::value` is true for all i.

>    > > […]

>    > > -17- Remark: This constructor shall not participate in overload resolution unless `const Ui&` is implicitly convertible to `Ti` `sizeof...(Types) == sizeof...(UTypes)` and both `is_constructible<Ti, const Ui&>::value` and `is_convertible<const Ui&, Ti>::value` are true for all i.

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

>    > > -18- Requires: `sizeof...(Types) == sizeof...(UTypes)`. `is_constructible<Ti, Ui&&>::value` is true for all i.

>    > > […]

>    > > -20- Remark: This constructor shall not participate in overload resolution unless each type in `UTypes` is implicitly convertible to its corresponding type in `Types` `sizeof...(Types) == sizeof...(UTypes)` and both `is_constructible<Ti, Ui>::value` and `is_convertible<Ui, Ti>::value` are true for all i.

>    >     template <class U1, class U2> constexpr tuple(const pair<U1, U2>& u);

>    > > -21- Requires: `sizeof...(Types) == 2`. `is_constructible<T0, const U1&>::value` is true for the first type `T0` in Types and `is_constructible<T1, const U2&>::value` is true for the second type `T1` in `Types`.

>    > > […]

>    > > -23- Remark: This constructor shall not participate in overload resolution unless `const U1&` is implicitly convertible to `T0` and `const U2&` is implicitly convertible to `T1` `sizeof...(Types) == 2 && is_constructible<T0, const U1&>::value && is_constructible<T1, const U2&>::value && is_convertible<const U1&, T0>::value && is_convertible<const U2&, T1>::value` is true.

>    >     template <class U1, class U2> constexpr tuple(pair<U1, U2>&& u);

>    > > -24- Requires: `sizeof...(Types) == 2`. `is_constructible<T0, U1&&>::value` is true for the first type `T0` in Types and `is_constructible<T1, U2&&>::value` is true for the second type `T1` in `Types`.

>    > > […]

>    > > -26- Remark: This constructor shall not participate in overload resolution unless `U1` is implicitly convertible to `T0` and `U2` is implicitly convertible to `T1` `sizeof...(Types) == 2 && is_constructible<T0, U1>::value && is_constructible<T1, U2>::value && is_convertible<U1, T0>::value && is_convertible<U2, T1>::value` is true.

[2016-03, Jacksonville]

STL provides improved wording.

[2016-06 Oulu]

Tuesday: Adopt option 1, drop option B of 2549, and move to Ready.

Friday: status to Immediate

Proposed resolution:

This wording is relative to N4567.

1. Edit 22.4.4.1 [tuple.cnstr] as indicated:

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

   > > -8- Requires: `sizeof...(Types) == sizeof...(UTypes)`.

   > > […]

   > > -10- Remarks: This constructor shall not participate in overload resolution unless `sizeof...(Types) >= 1` and `sizeof...(Types) == sizeof...(UTypes)` and `is_constructible<Ti, Ui&&>::value` is `true` for all `i`. The constructor is explicit if and only if `is_convertible<Ui&&, Ti>::value` is `false` for at least one `i`.

   > […]

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

   > > -15- Requires: `sizeof...(Types) == sizeof...(UTypes)`.

   > > […]

   > > -17- Remarks: This constructor shall not participate in overload resolution unless `sizeof...(Types) == sizeof...(UTypes)` and `is_constructible<Ti, const Ui&>::value` is `true` for all `i`. The constructor is explicit if and only if `is_convertible<const Ui&, Ti>::value` is `false` for at least one `i`.

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

   > > -18- Requires: `sizeof...(Types) == sizeof...(UTypes)`.

   > > […]

   > > -20- Remarks: This constructor shall not participate in overload resolution unless `sizeof...(Types) == sizeof...(UTypes)` and `is_constructible<Ti, Ui&&>::value` is `true` for all `i`. The constructor is explicit if and only if `is_convertible<Ui&&, Ti>::value` is `false` for at least one `i`.

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

   > > -21- Requires: `sizeof...(Types) == 2`.

   > > […]

   > > -23- Remarks: This constructor shall not participate in overload resolution unless `sizeof...(Types) == 2` and `is_constructible<T0, const U1&>::value` is `true` and `is_constructible<T1, const U2&>::value` is `true`. The constructor is explicit if and only if `is_convertible<const U1&, T0>::value` is `false` or `is_convertible<const U2&, T1>::value` is `false`.

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

   > > -24- Requires: `sizeof...(Types) == 2`.

   > > […]

   > > -26- Remarks: This constructor shall not participate in overload resolution unless `sizeof...(Types) == 2` and `is_constructible<T0, U1&&>::value` is `true` and `is_constructible<T1, U2&&>::value` is `true`. The constructor is explicit if and only if `is_convertible<U1&&, T0>::value` is `false` or `is_convertible<U2&&, T1>::value` is `false`.