tuple's constructor constraints need to be phrased more preciselySection: 22.4.4.2 [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!
Types is "long, long" and UTypes is "int, int, int". 22.4.4.2 [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.6.4 [meta.unary.prop]/6 specifies is_constructible<To, From> and 21.3.8 [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.2 [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.
Edit 22.4.4.2 [tuple.cnstr] as indicated:
template <class... UTypes> explicit constexpr tuple(UTypes&&... u);[…] -10- Remark: This constructor shall not participate in overload resolution unless
-8- Requires:sizeof...(Types) == sizeof...(UTypes).is_constructible<Ti, Ui&&>::valueis true for all i.each type inUTypesis implicitly convertible to its corresponding type inTypessizeof...(Types) == sizeof...(UTypes)and bothis_constructible<Ti, Ui>::valueandis_convertible<Ui, Ti>::valueare true for all i.[…]
template <class... UTypes> constexpr tuple(const tuple<UTypes...>& u);[…] -17- Remark: This constructor shall not participate in overload resolution unless
-15- Requires:sizeof...(Types) == sizeof...(UTypes).is_constructible<Ti, const Ui&>::valueis true for all i.const Ui&is implicitly convertible toTisizeof...(Types) == sizeof...(UTypes)and bothis_constructible<Ti, const Ui&>::valueandis_convertible<const Ui&, Ti>::valueare true for all i.template <class... UTypes> constexpr tuple(tuple<UTypes...>&& u);[…] -20- Remark: This constructor shall not participate in overload resolution unless
-18- Requires:sizeof...(Types) == sizeof...(UTypes).is_constructible<Ti, Ui&&>::valueis true for all i.each type inUTypesis implicitly convertible to its corresponding type inTypessizeof...(Types) == sizeof...(UTypes)and bothis_constructible<Ti, Ui>::valueandis_convertible<Ui, Ti>::valueare true for all i.template <class U1, class U2> constexpr tuple(const pair<U1, U2>& u);[…] -23- Remark: This constructor shall not participate in overload resolution unless
-21- Requires:sizeof...(Types) == 2.is_constructible<T0, const U1&>::valueis true for the first typeT0in Types andis_constructible<T1, const U2&>::valueis true for the second typeT1inTypes.const U1&is implicitly convertible toT0andconst U2&is implicitly convertible toT1sizeof...(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>::valueis true.template <class U1, class U2> constexpr tuple(pair<U1, U2>&& u);[…] -26- Remark: This constructor shall not participate in overload resolution unless
-24- Requires:sizeof...(Types) == 2.is_constructible<T0, U1&&>::valueis true for the first typeT0in Types andis_constructible<T1, U2&&>::valueis true for the second typeT1inTypes.U1is implicitly convertible toT0andU2is implicitly convertible toT1sizeof...(Types) == 2 && is_constructible<T0, U1>::value && is_constructible<T1, U2>::value && is_convertible<U1, T0>::value && is_convertible<U2, T1>::valueis 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.
Edit 22.4.4.2 [tuple.cnstr] as indicated:
template <class... UTypes> EXPLICIT constexpr tuple(UTypes&&... u);[…] -10- Remarks: This constructor shall not participate in overload resolution unless
-8- Requires:sizeof...(Types) == sizeof...(UTypes).sizeof...(Types) >= 1andsizeof...(Types) == sizeof...(UTypes)andis_constructible<Ti, Ui&&>::valueistruefor alli. The constructor is explicit if and only ifis_convertible<Ui&&, Ti>::valueisfalsefor at least onei.[…]
template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>& u);[…] -17- Remarks: This constructor shall not participate in overload resolution unless
-15- Requires:sizeof...(Types) == sizeof...(UTypes).sizeof...(Types) == sizeof...(UTypes)andis_constructible<Ti, const Ui&>::valueistruefor alli. The constructor is explicit if and only ifis_convertible<const Ui&, Ti>::valueisfalsefor at least onei.template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);[…] -20- Remarks: This constructor shall not participate in overload resolution unless
-18- Requires:sizeof...(Types) == sizeof...(UTypes).sizeof...(Types) == sizeof...(UTypes)andis_constructible<Ti, Ui&&>::valueistruefor alli. The constructor is explicit if and only ifis_convertible<Ui&&, Ti>::valueisfalsefor at least onei.template <class U1, class U2> EXPLICIT constexpr tuple(const pair<U1, U2>& u);[…] -23- Remarks: This constructor shall not participate in overload resolution unless
-21- Requires:sizeof...(Types) == 2.sizeof...(Types) == 2andis_constructible<T0, const U1&>::valueistrueandis_constructible<T1, const U2&>::valueistrue. The constructor is explicit if and only ifis_convertible<const U1&, T0>::valueisfalseoris_convertible<const U2&, T1>::valueisfalse.template <class U1, class U2> EXPLICIT constexpr tuple(pair<U1, U2>&& u);[…] -26- Remarks: This constructor shall not participate in overload resolution unless
-24- Requires:sizeof...(Types) == 2.sizeof...(Types) == 2andis_constructible<T0, U1&&>::valueistrueandis_constructible<T1, U2&&>::valueistrue. The constructor is explicit if and only ifis_convertible<U1&&, T0>::valueisfalseoris_convertible<U2&&, T1>::valueisfalse.