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

Section: 23.5.3.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". 23.5.3.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: 23.15.4.3 [meta.unary.prop]/6 specifies is_constructible<To, From> and 23.15.6 [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 23.5.3.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 23.5.3.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 Typessizeof...(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 Tisizeof...(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 Typessizeof...(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 T1sizeof...(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 T1sizeof...(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 23.5.3.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.