tuple
parameters end up taking references
to temporaries and will create dangling referencesSection: 22.4.4.2 [tuple.cnstr] Status: C++17 Submitter: Ville Voutilainen Opened: 2015-10-11 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 this example:
#include <utility> #include <tuple> struct X { int state; // this has to be here to not allow tuple // to inherit from an empty X. X() { } X(X const&) { } X(X&&) { } ~X() { } }; int main() { X v; std::tuple<X> t1{v}; std::tuple<std::tuple<X>&&> t2{std::move(t1)}; // #1 std::tuple<std::tuple<X>> t3{std::move(t2)}; // #2 }
The line marked with #1
will use the constructor
template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);
and will construct a temporary and bind an rvalue reference to it.
The line marked with #2
will move from a dangling reference.
Types...
is constructible
or convertible from the incoming tuple type and sizeof...(Types)
equals
one. libstdc++ already has this fix applied.
There is an additional check that needs to be done, in order to avoid
infinite meta-recursion during overload resolution, for a case where
the element type is itself constructible from the target tuple. An example
illustrating that problem is as follows:
#include <tuple> struct A { template <typename T> A(T) { } A(const A&) = default; A(A&&) = default; A& operator=(const A&) = default; A& operator=(A&&) = default; ~A() = default; }; int main() { auto x = A{7}; std::make_tuple(x); }
I provide two proposed resolutions, one that merely has a note encouraging trait-based implementations to avoid infinite meta-recursion, and a second one that avoids it normatively (implementations can still do things differently under the as-if rule, so we are not necessarily overspecifying how to do it.)
[2016-02-17, Ville comments]
It was pointed out at gcc bug 69853
that the fix for LWG 2549 is a breaking change. That is, it breaks code
that expects constructors inherited from tuple
to provide an implicit
base-to-derived conversion. I think that's just additional motivation
to apply the fix; that conversion is very much undesirable. The example
I wrote into the bug report is just one example of a very subtle temporary
being created.
Previous resolution from Ville [SUPERSEDED]:
Alternative 1:
In 22.4.4.2 [tuple.cnstr]/17, edit as follows:
template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>& u);[…]
-17- Remarks: This constructor shall not participate in overload resolution unless
is_constructible<Ti, const Ui&>::value
is true for alli
., and
sizeof...(Types) != 1
, oris_convertible<const tuple<UTypes...>&, Types...>::value
is false andis_constructible<Types..., const tuple<UTypes...>&>::value
is false.[Note: to avoid infinite template recursion in a trait-based implementation for the case where
The constructor is explicit if and only ifUTypes...
is a single type that has a constructor template that acceptstuple<Types...>
, implementations need to additionally check thatremove_cv_t<remove_reference_t<const tuple<UTypes...>&>>
andtuple<Types...>
are not the same type. — end note]is_convertible<const Ui&, Ti>::value
is false for at least onei
.In 22.4.4.2 [tuple.cnstr]/20, edit as follows:
template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);[…]
-20- Remarks: This constructor shall not participate in overload resolution unless
is_constructible<Ti, Ui&&>::value
is true for alli
., and
sizeof...(Types) != 1
, oris_convertible<tuple<UTypes...>&&, Types...>::value
is false andis_constructible<Types..., tuple<UTypes...>&&>::value
is false.[Note: to avoid infinite template recursion in a trait-based implementation for the case where
The constructor is explicit if and only ifUTypes...
is a single type that has a constructor template that acceptstuple<Types...>
, implementations need to additionally check thatremove_cv_t<remove_reference_t<tuple<UTypes...>&&>>
andtuple<Types...>
are not the same type. — end note]is_convertible<Ui&&, Ti>::value
is false for at least onei
.Alternative 2 (do the sameness-check normatively):
In 22.4.4.2 [tuple.cnstr]/17, edit as follows:
template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>& u);[…]
-17- Remarks: This constructor shall not participate in overload resolution unless
is_constructible<Ti, const Ui&>::value
is true for alli
., and
sizeof...(Types) != 1
, oris_convertible<const tuple<UTypes...>&, Types...>::value
is false andis_constructible<Types..., const tuple<UTypes...>&>::value
is false andis_same<tuple<Types...>, remove_cv_t<remove_reference_t<const tuple<UTypes...>&>>>::value
is false.The constructor is explicit if and only if
is_convertible<const Ui&, Ti>::value
is false for at least onei
.In 22.4.4.2 [tuple.cnstr]/20, edit as follows:
template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);[…]
-20- Remarks: This constructor shall not participate in overload resolution unless
is_constructible<Ti, Ui&&>::value
is true for alli
., and
sizeof...(Types) != 1
, oris_convertible<tuple<UTypes...>&&, Types...>::value
is false andis_constructible<Types..., tuple<UTypes...>&&>::value
is false andis_same<tuple<Types...>, remove_cv_t<remove_reference_t<tuple<UTypes...>&&>>>::value
is false.The constructor is explicit if and only if
is_convertible<Ui&&, Ti>::value
is false for at least onei
.
[2016-03, Jacksonville]
STL provides a simplification of Ville's alternative #2 (with no semantic changes), and it's shipping in VS 2015 Update 2.
Proposed resolution:
This wording is relative to N4567.
This approach is orthogonal to the Proposed Resolution for LWG 2312:
Edit 22.4.4.2 [tuple.cnstr] as indicated:
template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>& u);[…]
-17- Remarks: This constructor shall not participate in overload resolution unless
is_constructible<Ti, const Ui&>::value
istrue
for alli
, and
sizeof...(Types) != 1
, or (whenTypes...
expands toT
andUTypes...
expands toU
)!is_convertible_v<const tuple<U>&, T> && !is_constructible_v<T, const tuple<U>&> && !is_same_v<T, U>
istrue
.The constructor is explicit if and only if
is_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
is_constructible<Ti, Ui&&>::value
istrue
for alli
, and
sizeof...(Types) != 1
, or (whenTypes...
expands toT
andUTypes...
expands toU
)!is_convertible_v<tuple<U>, T> && !is_constructible_v<T, tuple<U>> && !is_same_v<T, U>
istrue
.The constructor is explicit if and only if
is_convertible<Ui&&, Ti>::value
isfalse
for at least onei
.
This approach is presented as a merge with the parts of the Proposed Resolution for LWG 2312 with overlapping modifications in the same paragraph, to provide editorial guidance if 2312 would be accepted.
Edit 22.4.4.2 [tuple.cnstr] as indicated:
template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>& u);[…]
-17- Remarks: This constructor shall not participate in overload resolution unless
sizeof...(Types) == sizeof...(UTypes)
, and
is_constructible<Ti, const Ui&>::value
istrue
for alli
, and
sizeof...(Types) != 1
, or (whenTypes...
expands toT
andUTypes...
expands toU
)!is_convertible_v<const tuple<U>&, T> && !is_constructible_v<T, const tuple<U>&> && !is_same_v<T, U>
istrue
.The constructor is explicit if and only if
is_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
sizeof...(Types) == sizeof...(UTypes)
, and
is_constructible<Ti, Ui&&>::value
istrue
for alli
, and
sizeof...(Types) != 1
, or (whenTypes...
expands toT
andUTypes...
expands toU
)!is_convertible_v<tuple<U>, T> && !is_constructible_v<T, tuple<U>> && !is_same_v<T, U>
istrue
.The constructor is explicit if and only if
is_convertible<Ui&&, Ti>::value
isfalse
for at least onei
.