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.
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&>::valueis true for alli., and
sizeof...(Types) != 1, oris_convertible<const tuple<UTypes...>&, Types...>::valueis false andis_constructible<Types..., const tuple<UTypes...>&>::valueis 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>::valueis 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&&>::valueis true for alli., and
sizeof...(Types) != 1, oris_convertible<tuple<UTypes...>&&, Types...>::valueis false andis_constructible<Types..., tuple<UTypes...>&&>::valueis 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>::valueis 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&>::valueis true for alli., and
sizeof...(Types) != 1, oris_convertible<const tuple<UTypes...>&, Types...>::valueis false andis_constructible<Types..., const tuple<UTypes...>&>::valueis false andis_same<tuple<Types...>, remove_cv_t<remove_reference_t<const tuple<UTypes...>&>>>::valueis false.The constructor is explicit if and only if
is_convertible<const Ui&, Ti>::valueis 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&&>::valueis true for alli., and
sizeof...(Types) != 1, oris_convertible<tuple<UTypes...>&&, Types...>::valueis false andis_constructible<Types..., tuple<UTypes...>&&>::valueis false andis_same<tuple<Types...>, remove_cv_t<remove_reference_t<tuple<UTypes...>&&>>>::valueis false.The constructor is explicit if and only if
is_convertible<Ui&&, Ti>::valueis 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&>::valueistruefor alli, and
sizeof...(Types) != 1, or (whenTypes...expands toTandUTypes...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>::valueisfalsefor 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&&>::valueistruefor alli, and
sizeof...(Types) != 1, or (whenTypes...expands toTandUTypes...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>::valueisfalsefor 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&>::valueistruefor alli, and
sizeof...(Types) != 1, or (whenTypes...expands toTandUTypes...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>::valueisfalsefor 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&&>::valueistruefor alli, and
sizeof...(Types) != 1, or (whenTypes...expands toTandUTypes...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>::valueisfalsefor at least onei.