Title
tuple's constructor constraints need to be phrased more precisely
Status
c++17
Section
[tuple.cnstr]
Submitter
Stephan T. Lavavej

Created on 2013-09-21.00:00:00 last changed 82 months ago

Messages

Date: 2016-06-27.16:42:33

Proposed resolution:

This wording is relative to N4567.

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

Date: 2016-06-27.16:42:33

[ 2016-06 Oulu ]

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

Friday: status to Immediate

Date: 2016-03-05.10:49:47

[ 2016-03, Jacksonville ]

STL provides improved wording.

Date: 2016-03-03.22:31:40

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

Date: 2015-05-15.00:00:00

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

Date: 2015-05-05.20:19:47

[ 2015-05, Lenexa ]

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

Date: 2015-03-29.16:49:50

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

Date: 2014-10-15.00:00:00

[ 2014-10-05, Daniel comments ]

This issue is closely related to LWG 2419.

Date: 2013-09-21.00:00:00

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". [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: [meta.unary.prop]/6 specifies is_constructible<To, From> and [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 [tuple.cnstr]/3 defines T0 and T1 so we don't need to repeat their definitions.

History
Date User Action Args
2017-07-30 20:15:43adminsetstatus: wp -> c++17
2016-06-28 13:14:43adminsetstatus: immediate -> wp
2016-06-27 16:42:33adminsetmessages: + msg8201
2016-06-27 16:42:33adminsetstatus: open -> immediate
2016-03-05 10:49:47adminsetmessages: + msg8000
2016-03-03 22:31:40adminsetmessages: + msg7999
2015-05-05 20:19:47adminsetmessages: + msg7356
2015-05-05 20:19:47adminsetmessages: + msg7355
2015-03-29 16:49:50adminsetmessages: + msg7265
2015-03-29 16:49:50adminsetstatus: new -> open
2014-10-05 14:41:34adminsetmessages: + msg7098
2013-10-10 20:04:39adminsetmessages: + msg6693
2013-09-21 00:00:00admincreate