Index: include/__tuple =================================================================== --- include/__tuple +++ include/__tuple @@ -341,6 +341,91 @@ tuple_size<_Up>::value, _Tp, _Up> {}; + +/** + * A utility metafunction that gets the first element type of a tuple + * and returns that type with no ref qualifiers. + */ +template +struct __first_tuple_elem : remove_reference< + typename tuple_element<0, + typename remove_reference<_Tuple>::type + >::type +> {}; + +/** + * A metafunction that determines if the tuple-like copy/move ctor should be + * disabled because it would construct a dangling reference to a nested tuple. + * See PR22806 - https://llvm.org/bugs/show_bug.cgi?id=22806. + * + * The goal of this meta-function is to disable the tuple-like ctor for + * instantiations like: + * + * std::tuple&&>::tuple(std::tuple&&); + * + * In this case the constructor would attempt to bind 'ValueType&&' to the first + * element of type 'std::tuple&&' which would create a temporary + * 'std::tuple' from 'ValueType&&' and assign it to a reference. The + * temporary would then go out of scope and the tuple would contain a dangling + * reference. + * + * This metafunction returns true if all of the following are true. + * 1. _From is "tuple-like". This is always true but we need it to guard other + * meta-programming. + * + * 2. _Tuple has size 1. This is the only case where the copy/move ctors can + * be a better match that the UTypes... ctors because a single argument + * ctor is a better match than a variadic one that deduces one argument. + * + * 3. The first element of _Tuple, 'TupleElem0', is a reference. + * + * 4. The first element of _From, 'FromElem0', is not an instance of + * or derived from 'TupleElem0'. If it were then the construction of + * 'TupleElem0' wouldn't result in a dangling reference. + */ +template ::type>::value && + tuple_size<_Tuple>::value == 1 && + is_reference::type>::value + > +struct __disable_tuple_like_ctor : false_type {}; + +template +struct __disable_tuple_like_ctor<_From, _Tuple, true> : integral_constant::type>::value == 1 && + !is_base_of< + typename __first_tuple_elem<_Tuple>::type, + typename __first_tuple_elem<_From>::type + >::value +> {}; + + +/** + * A metafunction that returns 'true' when the tuple(UTypes&&...) constructor + * should not participate in overload resolution. This metafunction is meant to + * guard the other SFINAE on the tuple(UTypes&&...) from being evaluated when + * its clear that a tuple copy/move constructor should be chosen instead. + * See PR23256 - https://llvm.org/bugs/show_bug.cgi?id=23256. + * + * The metafunction returns true if and only if: + * 1. sizeof...(UTypes) == 1. The copy constructor only takes 1 argument + * so we obviously didn't intend to call it if we provide more that one + * argument. + * + * 2. The first element (and only) element type, 'UT0', is the same type as the + * tuple. Ex std::tuple(std::tuple const&). + * + */ +template +struct __disable_tuple_args_ctor : false_type {}; + +template +struct __disable_tuple_args_ctor<_Tuple, _Arg0> + : is_same< + _Tuple, + typename remove_cv::type>::type + > {}; + #endif // _LIBCPP_HAS_NO_VARIADICS _LIBCPP_END_NAMESPACE_STD Index: include/tuple =================================================================== --- include/tuple +++ include/tuple @@ -540,6 +540,8 @@ typename enable_if < sizeof...(_Up) <= sizeof...(_Tp) && + __lazy_and< + __lazy_not<__disable_tuple_args_ctor>, __tuple_convertible < tuple<_Up...>, @@ -547,13 +549,14 @@ sizeof...(_Up) < sizeof...(_Tp) ? sizeof...(_Up) : sizeof...(_Tp)>::type - >::value && + >, __all_default_constructible< typename __make_tuple_types::type - >::value, + > + >::type::value, bool >::type = false > @@ -578,6 +581,8 @@ typename enable_if < sizeof...(_Up) <= sizeof...(_Tp) && + __lazy_and< + __lazy_not<__disable_tuple_args_ctor>, __tuple_constructible < tuple<_Up...>, @@ -585,21 +590,21 @@ sizeof...(_Up) < sizeof...(_Tp) ? sizeof...(_Up) : sizeof...(_Tp)>::type - >::value && - !__tuple_convertible + >, + __lazy_not<__tuple_convertible < tuple<_Up...>, typename __make_tuple_types::type - >::value && + >>, __all_default_constructible< typename __make_tuple_types::type - >::value, + >>::type::value, bool >::type =false > @@ -625,6 +630,8 @@ class = typename enable_if < sizeof...(_Up) <= sizeof...(_Tp) && + __lazy_and< + __lazy_not<__disable_tuple_args_ctor>, __tuple_convertible < tuple<_Up...>, @@ -632,13 +639,13 @@ sizeof...(_Up) < sizeof...(_Tp) ? sizeof...(_Up) : sizeof...(_Tp)>::type - >::value && + >, __all_default_constructible< typename __make_tuple_types::type - >::value + >>::value >::type > _LIBCPP_INLINE_VISIBILITY @@ -653,6 +660,7 @@ template ::value && __tuple_convertible<_Tuple, tuple>::value, bool >::type = false @@ -664,6 +672,7 @@ template ::value && __tuple_constructible<_Tuple, tuple>::value && !__tuple_convertible<_Tuple, tuple>::value, bool @@ -677,6 +686,7 @@ template ::value && __tuple_convertible<_Tuple, tuple>::value >::type > Index: test/std/utilities/tuple/tuple.tuple/tuple.cnstr/PR22806_constrain_tuple_like_ctor.pass.cpp =================================================================== --- /dev/null +++ test/std/utilities/tuple/tuple.tuple/tuple.cnstr/PR22806_constrain_tuple_like_ctor.pass.cpp @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// UNSUPPORTED: c++98, c++03 + +// + +// template class tuple; + +// template +// tuple(TupleLike&&); +// template +// tuple(std::allocator_arg_t, Alloc const&, TupleLike&&); + +// Check that the tuple-like copy/move ctors are properly disabled when the +// UTypes... constructor should be selected. See PR22806. + +#include +#include +#include + +#include "tracked_value.h" + +int main() +{ + { // rvalue reference + TrackedValue v; + std::tuple t1(v); + // This selects the wrong constructor and constructs std::tuple&& + // from a temporary. + std::tuple&&> t2(std::move(t1)); + // This moves from the reference constructed from the temporary. + std::tuple> t3(std::move(t2)); + } + { // const lvalue reference + TrackedValue v; + std::tuple t1(v); + std::tuple const &> t2(t1); + std::tuple> t3(t2); + } + { // lvalue reference + TrackedValue v; + std::tuple t1(v); + std::tuple &> t2(t1); + std::tuple> t3(t2); + } + { // rvalue reference via allocator ctor + TrackedValue v; + std::tuple t1(v); + std::allocator a; + std::tuple&&> t2(std::allocator_arg, a, std::move(t1)); + std::tuple> t3(std::move(t2)); + } + { // const lvalue reference via allocator ctor + TrackedValue v; + std::tuple t1(v); + std::allocator a; + std::tuple const &> t2(std::allocator_arg, a, t1); + std::tuple> t3(t2); + } + { // lvalue reference via allocator ctor + TrackedValue v; + std::tuple t1(v); + std::allocator a; + std::tuple &> t2(std::allocator_arg, a, t1); + std::tuple> t3(t2); + } +} Index: test/std/utilities/tuple/tuple.tuple/tuple.cnstr/PR23256_constrain_UTypes_ctor.pass.cpp =================================================================== --- /dev/null +++ test/std/utilities/tuple/tuple.tuple/tuple.cnstr/PR23256_constrain_UTypes_ctor.pass.cpp @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// UNSUPPORTED: c++98, c++03 + +// + +// template class tuple; + +// template +// EXPLICIT(...) tuple(UTypes&&...) + +// Check that the UTypes... ctor is properly disabled before evaluating any +// SFINAE when the tuple-like copy/move ctor should *clearly* be selected +// instead. This happens 'sizeof...(UTypes) == 1' and the first element of +// 'UTypes...' is an instance of the tuple itself. See PR23256. + +#include +#include +#include + + +class UnconstrainedCtor { +public: + UnconstrainedCtor() : value_(0) {} + + template + constexpr UnconstrainedCtor(T value) : value_(static_cast(value)) { + static_assert(std::is_same::value, ""); + } + +private: + int value_; +}; + +int main() { + typedef UnconstrainedCtor A; + { + static_assert(std::is_trivially_copy_constructible>::value, ""); + static_assert(std::is_trivially_move_constructible>::value, ""); + } + { + static_assert(std::is_constructible< + std::tuple, + std::allocator_arg_t, std::allocator, + std::tuple const& + >::value, ""); + static_assert(std::is_constructible< + std::tuple, + std::allocator_arg_t, std::allocator, + std::tuple && + >::value, ""); + } +}