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.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.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&&>::value
is true for all i.each type inUTypes
is implicitly convertible to its corresponding type inTypes
sizeof...(Types) == sizeof...(UTypes)
and bothis_constructible<Ti, Ui>::value
andis_convertible<Ui, Ti>::value
are 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&>::value
is true for all i.const Ui&
is implicitly convertible toTi
sizeof...(Types) == sizeof...(UTypes)
and bothis_constructible<Ti, const Ui&>::value
andis_convertible<const Ui&, Ti>::value
are 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&&>::value
is true for all i.each type inUTypes
is implicitly convertible to its corresponding type inTypes
sizeof...(Types) == sizeof...(UTypes)
and bothis_constructible<Ti, Ui>::value
andis_convertible<Ui, Ti>::value
are 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&>::value
is true for the first typeT0
in Types andis_constructible<T1, const U2&>::value
is true for the second typeT1
inTypes
.const U1&
is implicitly convertible toT0
andconst U2&
is implicitly convertible toT1
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);[…] -26- Remark: This constructor shall not participate in overload resolution unless
-24- Requires:sizeof...(Types) == 2
.is_constructible<T0, U1&&>::value
is true for the first typeT0
in Types andis_constructible<T1, U2&&>::value
is true for the second typeT1
inTypes
.U1
is implicitly convertible toT0
andU2
is implicitly convertible toT1
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.
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) >= 1
andsizeof...(Types) == sizeof...(UTypes)
andis_constructible<Ti, Ui&&>::value
istrue
for alli
. The constructor is explicit if and only ifis_convertible<Ui&&, Ti>::value
isfalse
for 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&>::value
istrue
for alli
. The constructor is explicit if and only ifis_convertible<const Ui&, Ti>::value
isfalse
for 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&&>::value
istrue
for alli
. The constructor is explicit if and only ifis_convertible<Ui&&, Ti>::value
isfalse
for 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) == 2
andis_constructible<T0, const U1&>::value
istrue
andis_constructible<T1, const U2&>::value
istrue
. The constructor is explicit if and only ifis_convertible<const U1&, T0>::value
isfalse
oris_convertible<const U2&, T1>::value
isfalse
.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) == 2
andis_constructible<T0, U1&&>::value
istrue
andis_constructible<T1, U2&&>::value
istrue
. The constructor is explicit if and only ifis_convertible<U1&&, T0>::value
isfalse
oris_convertible<U2&&, T1>::value
isfalse
.