This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++17 status.
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.
In order to solve the problem, the constructor templates taking tuples
as parameters need additional SFINAE conditions that SFINAE
those constructor templates away when 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
UTypes...
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]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.[Note: to avoid infinite template recursion in a trait-based implementation for the case where
UTypes...
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]The constructor is explicit if and only if
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(i):
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(i) with overlapping modifications in the same paragraph, to provide editorial guidance if 2312(i) 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
.