From f8a5298c97c460d45e888b123fe1bbcdb49b8ad4 Mon Sep 17 00:00:00 2001 From: Jonathan Wakely Date: Wed, 10 Jan 2024 17:29:22 +0000 Subject: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822] This is the last part of PR libstdc++/108822 implementing P2255R2, which makes it ill-formed to create a std::tuple that would bind a reference to a temporary. The dangling checks are implemented as deleted constructors for C++20 and higher, and as Debug Mode static assertions in the constructor body for older standards. This is similar to the r13-6084-g916ce577ad109b changes for std::pair. As part of this change, I've reimplemented most of std::tuple for C++20, making use of concepts to replace the enable_if constraints, and using conditional explicit to avoid duplicating most constructors. We could use conditional explicit for the C++11 implementation too (with pragmas to disables the -Wc++17-extensions warnings), but that should be done as a stage 1 change for GCC 15 rather than now. The partial specialization for std::tuple is no longer used for C++20 (or more precisely, for a C++20 compiler that supports concepts and conditional explicit). The additional constructors and assignment operators that take std::pair arguments have been added to the C++20 implementation of the primary template, with sizeof...(_Elements)==2 constraints. This avoids reimplementing all the other constructors in the std::tuple partial specialization to use concepts. This way we avoid four implementations of every constructor and only have three! (The primary template has an implementation of each constructor for C++11 and another for C++20, and the tuple specialization has an implementation of each for C++11, so that's three for each constructor.) In order to make the constraints more efficient on the C++20 version of the default constructor I've also added a variable template for the __is_implicitly_default_constructible trait, implemented using concepts. libstdc++-v3/ChangeLog: PR libstdc++/108822 * include/std/tuple (tuple): Add checks for dangling references. Reimplement constraints and constant expressions using C++20 features. * include/std/type_traits [C++20] (__is_implicitly_default_constructible_v): Define. (__is_implicitly_default_constructible): Use variable template. * testsuite/20_util/tuple/dangling_ref.cc: New test. Reviewed-by: Patrick Palka --- libstdc++-v3/include/std/tuple | 1017 ++++++++++++----- libstdc++-v3/include/std/type_traits | 11 + .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ 3 files changed, 839 insertions(+), 294 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/tuple/dangling_ref.cc diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple index 50e118437570..5f4a393b532c 100644 --- a/libstdc++-v3/include/std/tuple +++ b/libstdc++-v3/include/std/tuple @@ -752,7 +752,463 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template class tuple : public _Tuple_impl<0, _Elements...> { - typedef _Tuple_impl<0, _Elements...> _Inherited; + using _Inherited = _Tuple_impl<0, _Elements...>; + +#if __cpp_concepts && __cpp_consteval && __cpp_conditional_explicit // >= C++20 + template + static consteval bool + __constructible() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return __and_v...>; + else + return false; + } + + template + static consteval bool + __nothrow_constructible() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return __and_v...>; + else + return false; + } + + template + static consteval bool + __convertible() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return __and_v...>; + else + return false; + } + + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 3121. tuple constructor constraints for UTypes&&... overloads + template + static consteval bool + __disambiguating_constraint() + { + if constexpr (sizeof...(_Elements) != sizeof...(_UTypes)) + return false; + else if constexpr (sizeof...(_Elements) == 1) + { + using _U0 = typename _Nth_type<0, _UTypes...>::type; + return !is_same_v, tuple>; + } + else if constexpr (sizeof...(_Elements) < 4) + { + using _U0 = typename _Nth_type<0, _UTypes...>::type; + if constexpr (!is_same_v, allocator_arg_t>) + return true; + else + { + using _T0 = typename _Nth_type<0, _Elements...>::type; + return is_same_v, allocator_arg_t>; + } + } + return true; + } + + // Return true iff sizeof...(Types) == 1 && tuple_size_v == 1 + // and the single element in Types can be initialized from TUPLE, + // or is the same type as tuple_element_t<0, TUPLE>. + template + static consteval bool + __use_other_ctor() + { + if constexpr (sizeof...(_Elements) != 1) + return false; + else if constexpr (is_same_v, tuple>) + return true; // Should use a copy/move constructor instead. + else + { + using _Tp = typename _Nth_type<0, _Elements...>::type; + if constexpr (is_convertible_v<_Tuple, _Tp>) + return true; + else if constexpr (is_constructible_v<_Tp, _Tuple>) + return true; + } + return false; + } + + template + static consteval bool + __dangles() + { +#if __has_builtin(__reference_constructs_from_temporary) + return (__reference_constructs_from_temporary(_Elements, _Up&&) + || ...); +#else + return false; +#endif + } + + public: + constexpr + explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...)) + tuple() + noexcept((is_nothrow_default_constructible_v<_Elements> && ...)) + requires (is_default_constructible_v<_Elements> && ...) + : _Inherited() + { } + + constexpr explicit(!__convertible()) + tuple(const _Elements&... __elements) + noexcept(__nothrow_constructible()) + requires (__constructible()) + : _Inherited(__elements...) + { } + + template + requires (__disambiguating_constraint<_UTypes...>()) + && (__constructible<_UTypes...>()) + && (!__dangles<_UTypes...>()) + constexpr explicit(!__convertible<_UTypes...>()) + tuple(_UTypes&&... __u) + noexcept(__nothrow_constructible<_UTypes...>()) + : _Inherited(std::forward<_UTypes>(__u)...) + { } + + template + requires (__disambiguating_constraint<_UTypes...>()) + && (__constructible<_UTypes...>()) + && (__dangles<_UTypes...>()) + tuple(_UTypes&&...) = delete; + + constexpr tuple(const tuple&) = default; + + constexpr tuple(tuple&&) = default; + + template + requires (__constructible()) + && (!__use_other_ctor&>()) + && (!__dangles()) + constexpr explicit(!__convertible()) + tuple(const tuple<_UTypes...>& __u) + noexcept(__nothrow_constructible()) + : _Inherited(static_cast&>(__u)) + { } + + template + requires (__constructible()) + && (!__use_other_ctor&>()) + && (__dangles()) + tuple(const tuple<_UTypes...>&) = delete; + + template + requires (__constructible<_UTypes...>()) + && (!__use_other_ctor>()) + && (!__dangles<_UTypes...>()) + constexpr explicit(!__convertible<_UTypes...>()) + tuple(tuple<_UTypes...>&& __u) + noexcept(__nothrow_constructible<_UTypes...>()) + : _Inherited(static_cast<_Tuple_impl<0, _UTypes...>&&>(__u)) + { } + + template + requires (__constructible<_UTypes...>()) + && (!__use_other_ctor>()) + && (__dangles<_UTypes...>()) + tuple(tuple<_UTypes...>&&) = delete; + +#if __cpp_lib_ranges_zip // >= C++23 + template + requires (__constructible<_UTypes&...>()) + && (!__use_other_ctor&>()) + && (!__dangles<_UTypes&...>()) + constexpr explicit(!__convertible<_UTypes&...>()) + tuple(tuple<_UTypes...>& __u) + noexcept(__nothrow_constructible<_UTypes&...>()) + : _Inherited(static_cast<_Tuple_impl<0, _UTypes...>&>(__u)) + { } + + template + requires (__constructible<_UTypes&...>()) + && (!__use_other_ctor&>()) + && (__dangles<_UTypes&...>()) + tuple(tuple<_UTypes...>&) = delete; + + template + requires (__constructible()) + && (!__use_other_ctor>()) + && (!__dangles()) + constexpr explicit(!__convertible()) + tuple(const tuple<_UTypes...>&& __u) + noexcept(__nothrow_constructible()) + : _Inherited(static_cast&&>(__u)) + { } + + template + requires (__constructible()) + && (!__use_other_ctor>()) + && (__dangles()) + tuple(const tuple<_UTypes...>&&) = delete; +#endif // C++23 + + template + requires (sizeof...(_Elements) == 2) + && (__constructible()) + && (!__dangles()) + constexpr explicit(!__convertible()) + tuple(const pair<_U1, _U2>& __u) + noexcept(__nothrow_constructible()) + : _Inherited(__u.first, __u.second) + { } + + template + requires (sizeof...(_Elements) == 2) + && (__constructible()) + && (__dangles()) + tuple(const pair<_U1, _U2>&) = delete; + + template + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1, _U2>()) + && (!__dangles<_U1, _U2>()) + constexpr explicit(!__convertible<_U1, _U2>()) + tuple(pair<_U1, _U2>&& __u) + noexcept(__nothrow_constructible<_U1, _U2>()) + : _Inherited(std::forward<_U1>(__u.first), + std::forward<_U2>(__u.second)) + { } + + template + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1, _U2>()) + && (__dangles<_U1, _U2>()) + tuple(pair<_U1, _U2>&&) = delete; + +#if __cpp_lib_ranges_zip // >= C++23 + template + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1&, _U2&>()) + && (!__dangles<_U1&, _U2&>()) + constexpr explicit(!__convertible<_U1&, _U2&>()) + tuple(pair<_U1, _U2>& __u) + noexcept(__nothrow_constructible<_U1&, _U2&>()) + : _Inherited(__u.first, __u.second) + { } + + template + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1&, _U2&>()) + && (__dangles<_U1&, _U2&>()) + tuple(pair<_U1, _U2>&) = delete; + + template + requires (sizeof...(_Elements) == 2) + && (__constructible()) + && (!__dangles()) + constexpr explicit(!__convertible()) + tuple(const pair<_U1, _U2>&& __u) + noexcept(__nothrow_constructible()) + : _Inherited(std::forward(__u.first), + std::forward(__u.second)) + { } + + template + requires (sizeof...(_Elements) == 2) + && (__constructible()) + && (__dangles()) + tuple(const pair<_U1, _U2>&&) = delete; +#endif // C++23 + +#if 0 && __cpp_lib_tuple_like // >= C++23 + template<__tuple_like _UTuple> + constexpr explicit(...) + tuple(_UTuple&& __u); +#endif // C++23 + + // Allocator-extended constructors. + + template + constexpr + explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...)) + tuple(allocator_arg_t __tag, const _Alloc& __a) + requires (is_default_constructible_v<_Elements> && ...) + : _Inherited(__tag, __a) + { } + + template + constexpr explicit(!__convertible()) + tuple(allocator_arg_t __tag, const _Alloc& __a, + const _Elements&... __elements) + requires (__constructible()) + : _Inherited(__tag, __a, __elements...) + { } + + template + requires (__disambiguating_constraint<_UTypes...>()) + && (__constructible<_UTypes...>()) + && (!__dangles<_UTypes...>()) + constexpr explicit(!__convertible<_UTypes...>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, _UTypes&&... __u) + : _Inherited(__tag, __a, std::forward<_UTypes>(__u)...) + { } + + template + requires (__disambiguating_constraint<_UTypes...>()) + && (__constructible<_UTypes...>()) + && (__dangles<_UTypes...>()) + tuple(allocator_arg_t, const _Alloc&, _UTypes&&...) = delete; + + template + constexpr + tuple(allocator_arg_t __tag, const _Alloc& __a, const tuple& __u) + : _Inherited(__tag, __a, static_cast(__u)) + { } + + template + requires (__constructible<_Elements...>()) + constexpr + tuple(allocator_arg_t __tag, const _Alloc& __a, tuple&& __u) + : _Inherited(__tag, __a, static_cast<_Inherited&&>(__u)) + { } + + template + requires (__constructible()) + && (!__use_other_ctor&>()) + && (!__dangles()) + constexpr explicit(!__convertible()) + tuple(allocator_arg_t __tag, const _Alloc& __a, + const tuple<_UTypes...>& __u) + : _Inherited(__tag, __a, + static_cast&>(__u)) + { } + + template + requires (__constructible()) + && (!__use_other_ctor&>()) + && (__dangles()) + tuple(allocator_arg_t, const _Alloc&, const tuple<_UTypes...>&) = delete; + + template + requires (__constructible<_UTypes...>()) + && (!__use_other_ctor>()) + && (!__dangles<_UTypes...>()) + constexpr explicit(!__use_other_ctor>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, tuple<_UTypes...>&& __u) + : _Inherited(__tag, __a, static_cast<_Tuple_impl<0, _UTypes...>&&>(__u)) + { } + + template + requires (__constructible<_UTypes...>()) + && (!__use_other_ctor>()) + && (__dangles<_UTypes...>()) + tuple(allocator_arg_t, const _Alloc&, tuple<_UTypes...>&&) = delete; + +#if __cpp_lib_ranges_zip // >= C++23 + template + requires (__constructible<_UTypes&...>()) + && (!__use_other_ctor&>()) + && (!__dangles<_UTypes&...>()) + constexpr explicit(!__convertible<_UTypes&...>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, tuple<_UTypes...>& __u) + : _Inherited(__tag, __a, static_cast<_Tuple_impl<0, _UTypes...>&>(__u)) + { } + + template + requires (__constructible<_UTypes&...>()) + && (!__use_other_ctor&>()) + && (__dangles<_UTypes&...>()) + tuple(allocator_arg_t, const _Alloc&, tuple<_UTypes...>&) = delete; + + template + requires (__constructible()) + && (!__use_other_ctor>()) + && (!__dangles()) + constexpr explicit(!__convertible()) + tuple(allocator_arg_t __tag, const _Alloc& __a, + const tuple<_UTypes...>&& __u) + : _Inherited(__tag, __a, + static_cast&&>(__u)) + { } + + template + requires (__constructible()) + && (!__use_other_ctor>()) + && (__dangles()) + tuple(allocator_arg_t, const _Alloc&, const tuple<_UTypes...>&&) = delete; +#endif // C++23 + + template + requires (sizeof...(_Elements) == 2) + && (__constructible()) + && (!__dangles()) + constexpr explicit(!__convertible()) + tuple(allocator_arg_t __tag, const _Alloc& __a, + const pair<_U1, _U2>& __u) + noexcept(__nothrow_constructible()) + : _Inherited(__tag, __a, __u.first, __u.second) + { } + + template + requires (sizeof...(_Elements) == 2) + && (__constructible()) + && (__dangles()) + tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&) = delete; + + template + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1, _U2>()) + && (!__dangles<_U1, _U2>()) + constexpr explicit(!__convertible<_U1, _U2>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, pair<_U1, _U2>&& __u) + noexcept(__nothrow_constructible<_U1, _U2>()) + : _Inherited(__tag, __a, std::move(__u.first), std::move(__u.second)) + { } + + template + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1, _U2>()) + && (__dangles<_U1, _U2>()) + tuple(allocator_arg_t, const _Alloc&, pair<_U1, _U2>&&) = delete; + +#if __cpp_lib_ranges_zip // >= C++23 + template + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1&, _U2&>()) + && (!__dangles<_U1&, _U2&>()) + constexpr explicit(!__convertible<_U1&, _U2&>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, pair<_U1, _U2>& __u) + noexcept(__nothrow_constructible<_U1&, _U2&>()) + : _Inherited(__tag, __a, __u.first, __u.second) + { } + + template + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1&, _U2&>()) + && (__dangles<_U1&, _U2&>()) + tuple(allocator_arg_t, const _Alloc&, pair<_U1, _U2>&) = delete; + + template + requires (sizeof...(_Elements) == 2) + && (__constructible()) + && (!__dangles()) + constexpr explicit(!__convertible()) + tuple(allocator_arg_t __tag, const _Alloc& __a, + const pair<_U1, _U2>&& __u) + noexcept(__nothrow_constructible()) + : _Inherited(__tag, __a, std::move(__u.first), std::move(__u.second)) + { } + + template + requires (sizeof...(_Elements) == 2) + && (__constructible()) + && (__dangles()) + tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete; +#endif // C++23 + +#if 0 && __cpp_lib_tuple_like // >= C++23 + template + constexpr explicit(...) + tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u); +#endif // C++23 + +#else // !(concepts && conditional_explicit) template using _TCC = _TupleConstraints<_Cond, _Elements...>; @@ -850,15 +1306,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION static constexpr bool __use_other_ctor() { return _UseOtherCtor<_Tuple>::value; } -#if __cplusplus > 202002L - template - static constexpr bool __constructible - = _TCC::template __constructible<_Args...>::value; - - template - static constexpr bool __convertible - = _TCC::template __convertible<_Args...>::value; -#endif // C++23 + /// @cond undocumented +#undef __glibcxx_no_dangling_refs +#if __has_builtin(__reference_constructs_from_temporary) \ + && defined _GLIBCXX_DEBUG + // Error if construction from U... would create a dangling ref. +# if __cpp_fold_expressions +# define __glibcxx_dangling_refs(U) \ + (__reference_constructs_from_temporary(_Elements, U) && ...) +# else +# define __glibcxx_dangling_refs(U) \ + __or_<__bool_constant<__reference_constructs_from_temporary(_Elements, U) \ + >...>::value +# endif +# define __glibcxx_no_dangling_refs(U) \ + static_assert(!__glibcxx_dangling_refs(U), \ + "std::tuple constructor creates a dangling reference") +#else +# define __glibcxx_no_dangling_refs(U) +#endif + /// @endcond public: template()) - : _Inherited(std::forward<_UElements>(__elements)...) { } + : _Inherited(std::forward<_UElements>(__elements)...) + { __glibcxx_no_dangling_refs(_UElements&&); } template(), @@ -903,7 +1371,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION explicit constexpr tuple(_UElements&&... __elements) noexcept(__nothrow_constructible<_UElements...>()) - : _Inherited(std::forward<_UElements>(__elements)...) { } + : _Inherited(std::forward<_UElements>(__elements)...) + { __glibcxx_no_dangling_refs(_UElements&&); } constexpr tuple(const tuple&) = default; @@ -917,7 +1386,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(const tuple<_UElements...>& __in) noexcept(__nothrow_constructible()) : _Inherited(static_cast&>(__in)) - { } + { __glibcxx_no_dangling_refs(const _UElements&); } template& __in) noexcept(__nothrow_constructible()) : _Inherited(static_cast&>(__in)) - { } + { __glibcxx_no_dangling_refs(const _UElements&); } template&& __in) noexcept(__nothrow_constructible<_UElements...>()) - : _Inherited(static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) { } + : _Inherited(static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) + { __glibcxx_no_dangling_refs(_UElements&&); } template&& __in) noexcept(__nothrow_constructible<_UElements...>()) - : _Inherited(static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) { } - -#if __cplusplus > 202002L - template - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (!__use_other_ctor&>()) - && __constructible<_UElements&...> - explicit(!__convertible<_UElements&...>) - constexpr - tuple(tuple<_UElements...>& __in) - noexcept(__nothrow_constructible<_UElements&...>()) - : _Inherited(static_cast<_Tuple_impl<0, _UElements...>&>(__in)) - { } - - template - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (!__use_other_ctor&&>()) - && __constructible - explicit(!__convertible) - constexpr - tuple(const tuple<_UElements...>&& __in) - noexcept(__nothrow_constructible()) - : _Inherited(static_cast&&>(__in)) { } -#endif // C++23 + : _Inherited(static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) + { __glibcxx_no_dangling_refs(_UElements&&); } // Allocator-extended constructors. @@ -1000,7 +1448,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(allocator_arg_t __tag, const _Alloc& __a, _UElements&&... __elements) : _Inherited(__tag, __a, std::forward<_UElements>(__elements)...) - { } + { __glibcxx_no_dangling_refs(_UElements&&); } template(), @@ -1010,7 +1458,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(allocator_arg_t __tag, const _Alloc& __a, _UElements&&... __elements) : _Inherited(__tag, __a, std::forward<_UElements>(__elements)...) - { } + { __glibcxx_no_dangling_refs(_UElements&&); } template _GLIBCXX20_CONSTEXPR @@ -1030,8 +1478,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(allocator_arg_t __tag, const _Alloc& __a, const tuple<_UElements...>& __in) : _Inherited(__tag, __a, - static_cast&>(__in)) - { } + static_cast&>(__in)) + { __glibcxx_no_dangling_refs(const _UElements&); } template& __in) : _Inherited(__tag, __a, - static_cast&>(__in)) - { } + static_cast&>(__in)) + { __glibcxx_no_dangling_refs(const _UElements&); } template&& __in) : _Inherited(__tag, __a, - static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) - { } + static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) + { __glibcxx_no_dangling_refs(_UElements&&); } template&& __in) : _Inherited(__tag, __a, - static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) - { } + static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) + { __glibcxx_no_dangling_refs(_UElements&&); } +#endif // concepts && conditional_explicit -#if __cplusplus > 202002L - template - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (!__use_other_ctor&>()) - && __constructible<_UElements&...> - explicit(!__convertible<_UElements&...>) - constexpr - tuple(allocator_arg_t __tag, const _Alloc& __a, - tuple<_UElements...>& __in) - : _Inherited(__tag, __a, - static_cast<_Tuple_impl<0, _UElements...>&>(__in)) - { } + // tuple assignment - template - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (!__use_other_ctor>()) - && __constructible - explicit(!__convertible) - constexpr - tuple(allocator_arg_t __tag, const _Alloc& __a, - const tuple<_UElements...>&& __in) - : _Inherited(__tag, __a, - static_cast&&>(__in)) - { } +#if __cpp_concepts && __cpp_consteval // >= C++20 + private: + template + static consteval bool + __assignable() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return __and_v...>; + else + return false; + } + + template + static consteval bool + __nothrow_assignable() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return __and_v...>; + else + return false; + } + +#if __cpp_lib_ranges_zip // >= C++23 + template + static consteval bool + __const_assignable() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return __and_v...>; + else + return false; + } #endif // C++23 - // tuple assignment + public: + + tuple& operator=(const tuple& __u) = delete; + + constexpr tuple& + operator=(const tuple& __u) + noexcept(__nothrow_assignable()) + requires (__assignable()) + { + this->_M_assign(__u); + return *this; + } + + constexpr tuple& + operator=(tuple&& __u) + noexcept(__nothrow_assignable<_Elements...>()) + requires (__assignable<_Elements...>()) + { + this->_M_assign(std::move(__u)); + return *this; + } + + template + requires (__assignable()) + constexpr tuple& + operator=(const tuple<_UTypes...>& __u) + noexcept(__nothrow_assignable()) + { + this->_M_assign(__u); + return *this; + } + + template + requires (__assignable<_UTypes...>()) + constexpr tuple& + operator=(tuple<_UTypes...>&& __u) + noexcept(__nothrow_assignable<_UTypes...>()) + { + this->_M_assign(std::move(__u)); + return *this; + } + +#if __cpp_lib_ranges_zip // >= C++23 + constexpr const tuple& + operator=(const tuple& __u) const + requires (__const_assignable()) + { + this->_M_assign(__u); + return *this; + } + + constexpr const tuple& + operator=(tuple&& __u) const + requires (__const_assignable<_Elements...>()) + { + this->_M_assign(std::move(__u)); + return *this; + } + + template + constexpr const tuple& + operator=(const tuple<_UTypes...>& __u) const + requires (__const_assignable()) + { + this->_M_assign(__u); + return *this; + } + + template + constexpr const tuple& + operator=(tuple<_UTypes...>&& __u) const + requires (__const_assignable<_UTypes...>()) + { + this->_M_assign(std::move(__u)); + return *this; + } +#endif // C++23 + + template + requires (__assignable()) + constexpr tuple& + operator=(const pair<_U1, _U2>& __u) + noexcept(__nothrow_assignable()) + { + this->_M_head(*this) = __u.first; + this->_M_tail(*this)._M_head(*this) = __u.second; + return *this; + } + + template + requires (__assignable<_U1, _U2>()) + constexpr tuple& + operator=(pair<_U1, _U2>&& __u) + noexcept(__nothrow_assignable<_U1, _U2>()) + { + this->_M_head(*this) = std::forward<_U1>(__u.first); + this->_M_tail(*this)._M_head(*this) = std::forward<_U2>(__u.second); + return *this; + } + +#if __cpp_lib_ranges_zip // >= C++23 + template + requires (__const_assignable()) + constexpr const tuple& + operator=(const pair<_U1, _U2>& __u) const + { + this->_M_head(*this) = __u.first; + this->_M_tail(*this)._M_head(*this) = __u.second; + return *this; + } + + template + requires (__const_assignable<_U1, _U2>()) + constexpr const tuple& + operator=(pair<_U1, _U2>&& __u) const + { + this->_M_head(*this) = std::forward<_U1>(__u.first); + this->_M_tail(*this)._M_head(*this) = std::forward<_U2>(__u.second); + return *this; + } +#endif // C++23 + +#if 0 && __cpp_lib_tuple_like // >= C++23 + template<__tuple_like _UTuple> + constexpr tuple& + operator=(_UTuple&& __u); + + template<__tuple_like _UTuple> + constexpr tuple& + operator=(_UTuple&& __u) const; +#endif // C++23 + +#else // ! concepts _GLIBCXX20_CONSTEXPR tuple& @@ -1137,44 +1728,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION this->_M_assign(std::move(__in)); return *this; } - -#if __cplusplus > 202002L - constexpr const tuple& - operator=(const tuple& __in) const - requires (is_copy_assignable_v && ...) - { - this->_M_assign(__in); - return *this; - } - - constexpr const tuple& - operator=(tuple&& __in) const - requires (is_assignable_v && ...) - { - this->_M_assign(std::move(__in)); - return *this; - } - - template - constexpr const tuple& - operator=(const tuple<_UElements...>& __in) const - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (is_assignable_v && ...) - { - this->_M_assign(__in); - return *this; - } - - template - constexpr const tuple& - operator=(tuple<_UElements...>&& __in) const - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (is_assignable_v && ...) - { - this->_M_assign(std::move(__in)); - return *this; - } -#endif // C++23 +#endif // concepts // tuple swap _GLIBCXX20_CONSTEXPR @@ -1183,7 +1737,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION noexcept(__and_<__is_nothrow_swappable<_Elements>...>::value) { _Inherited::_M_swap(__in); } -#if __cplusplus > 202002L +#if __cpp_lib_ranges_zip // >= C++23 // As an extension, we constrain the const swap member function in order // to continue accepting explicit instantiation of tuples whose elements // are not all const swappable. Without this constraint, such an @@ -1233,6 +1787,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(allocator_arg_t, const _Alloc&, const tuple&) noexcept { } }; +#if !(__cpp_concepts && __cpp_consteval && __cpp_conditional_explicit) // !C++20 /// Partial specialization, 2-element tuple. /// Includes construction and assignment from a pair. template @@ -1300,15 +1855,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION static constexpr bool __is_alloc_arg() { return is_same<__remove_cvref_t<_U1>, allocator_arg_t>::value; } -#if __cplusplus > 202002L - template - static constexpr bool __constructible - = _TCC::template __constructible<_U1, _U2>::value; - - template - static constexpr bool __convertible - = _TCC::template __convertible<_U1, _U2>::value; -#endif // C++23 + /// @cond undocumented +#undef __glibcxx_no_dangling_refs + // Error if construction from _U1 and _U2 would create a dangling ref. +#if __has_builtin(__reference_constructs_from_temporary) \ + && defined _GLIBCXX_DEBUG +# define __glibcxx_no_dangling_refs(_U1, _U2) \ + static_assert(!__reference_constructs_from_temporary(_T1, _U1) \ + && !__reference_constructs_from_temporary(_T2, _U2), \ + "std::tuple constructor creates a dangling reference") +#else +# define __glibcxx_no_dangling_refs(_U1, _U2) +#endif + /// @endcond public: template()) - : _Inherited(std::forward<_U1>(__a1), std::forward<_U2>(__a2)) { } + : _Inherited(std::forward<_U1>(__a1), std::forward<_U2>(__a2)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template(), _U1, _U2> = false> explicit constexpr tuple(_U1&& __a1, _U2&& __a2) noexcept(__nothrow_constructible<_U1, _U2>()) - : _Inherited(std::forward<_U1>(__a1), std::forward<_U2>(__a2)) { } + : _Inherited(std::forward<_U1>(__a1), std::forward<_U2>(__a2)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } constexpr tuple(const tuple&) = default; @@ -1362,60 +1923,48 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION constexpr tuple(const tuple<_U1, _U2>& __in) noexcept(__nothrow_constructible()) - : _Inherited(static_cast&>(__in)) { } + : _Inherited(static_cast&>(__in)) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template = false> explicit constexpr tuple(const tuple<_U1, _U2>& __in) noexcept(__nothrow_constructible()) - : _Inherited(static_cast&>(__in)) { } + : _Inherited(static_cast&>(__in)) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template = true> constexpr tuple(tuple<_U1, _U2>&& __in) noexcept(__nothrow_constructible<_U1, _U2>()) - : _Inherited(static_cast<_Tuple_impl<0, _U1, _U2>&&>(__in)) { } + : _Inherited(static_cast<_Tuple_impl<0, _U1, _U2>&&>(__in)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template = false> explicit constexpr tuple(tuple<_U1, _U2>&& __in) noexcept(__nothrow_constructible<_U1, _U2>()) - : _Inherited(static_cast<_Tuple_impl<0, _U1, _U2>&&>(__in)) { } - -#if __cplusplus > 202002L - template - requires __constructible<_U1&, _U2&> - explicit(!__convertible<_U1&, _U2&>) - constexpr - tuple(tuple<_U1, _U2>& __in) - noexcept(__nothrow_constructible<_U1&, _U2&>()) - : _Inherited(static_cast<_Tuple_impl<0, _U1, _U2>&>(__in)) { } - - template - requires __constructible - explicit(!__convertible) - constexpr - tuple(const tuple<_U1, _U2>&& __in) - noexcept(__nothrow_constructible()) - : _Inherited(static_cast&&>(__in)) { } -#endif // C++23 + : _Inherited(static_cast<_Tuple_impl<0, _U1, _U2>&&>(__in)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template = true> constexpr tuple(const pair<_U1, _U2>& __in) noexcept(__nothrow_constructible()) - : _Inherited(__in.first, __in.second) { } + : _Inherited(__in.first, __in.second) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template = false> explicit constexpr tuple(const pair<_U1, _U2>& __in) noexcept(__nothrow_constructible()) - : _Inherited(__in.first, __in.second) { } + : _Inherited(__in.first, __in.second) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template = true> @@ -1423,7 +1972,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(pair<_U1, _U2>&& __in) noexcept(__nothrow_constructible<_U1, _U2>()) : _Inherited(std::forward<_U1>(__in.first), - std::forward<_U2>(__in.second)) { } + std::forward<_U2>(__in.second)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template = false> @@ -1431,26 +1981,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(pair<_U1, _U2>&& __in) noexcept(__nothrow_constructible<_U1, _U2>()) : _Inherited(std::forward<_U1>(__in.first), - std::forward<_U2>(__in.second)) { } - -#if __cplusplus > 202002L - template - requires __constructible<_U1&, _U2&> - explicit(!__convertible<_U1&, _U2&>) - constexpr - tuple(pair<_U1, _U2>& __in) - noexcept(__nothrow_constructible<_U1&, _U2&>()) - : _Inherited(__in.first, __in.second) { } - - template - requires __constructible - explicit(!__convertible) - constexpr - tuple(const pair<_U1, _U2>&& __in) - noexcept(__nothrow_constructible()) - : _Inherited(std::forward(__in.first), - std::forward(__in.second)) { } -#endif // C++23 + std::forward<_U2>(__in.second)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } // Allocator-extended constructors. @@ -1480,7 +2012,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _GLIBCXX20_CONSTEXPR tuple(allocator_arg_t __tag, const _Alloc& __a, _U1&& __a1, _U2&& __a2) : _Inherited(__tag, __a, std::forward<_U1>(__a1), - std::forward<_U2>(__a2)) { } + std::forward<_U2>(__a2)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template = false> @@ -1489,7 +2022,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(allocator_arg_t __tag, const _Alloc& __a, _U1&& __a1, _U2&& __a2) : _Inherited(__tag, __a, std::forward<_U1>(__a1), - std::forward<_U2>(__a2)) { } + std::forward<_U2>(__a2)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template _GLIBCXX20_CONSTEXPR @@ -1507,8 +2041,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(allocator_arg_t __tag, const _Alloc& __a, const tuple<_U1, _U2>& __in) : _Inherited(__tag, __a, - static_cast&>(__in)) - { } + static_cast&>(__in)) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template = false> @@ -1517,15 +2051,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(allocator_arg_t __tag, const _Alloc& __a, const tuple<_U1, _U2>& __in) : _Inherited(__tag, __a, - static_cast&>(__in)) - { } + static_cast&>(__in)) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template = true> _GLIBCXX20_CONSTEXPR tuple(allocator_arg_t __tag, const _Alloc& __a, tuple<_U1, _U2>&& __in) : _Inherited(__tag, __a, static_cast<_Tuple_impl<0, _U1, _U2>&&>(__in)) - { } + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template = false> @@ -1533,36 +2067,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _GLIBCXX20_CONSTEXPR tuple(allocator_arg_t __tag, const _Alloc& __a, tuple<_U1, _U2>&& __in) : _Inherited(__tag, __a, static_cast<_Tuple_impl<0, _U1, _U2>&&>(__in)) - { } - -#if __cplusplus > 202002L - template - requires __constructible<_U1&, _U2&> - explicit(!__convertible<_U1&, _U2&>) - constexpr - tuple(allocator_arg_t __tag, const _Alloc& __a, - tuple<_U1, _U2>& __in) - : _Inherited(__tag, __a, - static_cast<_Tuple_impl<0, _U1, _U2>&>(__in)) - { } - - template - requires __constructible - explicit(!__convertible) - constexpr - tuple(allocator_arg_t __tag, const _Alloc& __a, - const tuple<_U1, _U2>&& __in) - : _Inherited(__tag, __a, - static_cast&&>(__in)) - { } -#endif // C++23 + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template = true> _GLIBCXX20_CONSTEXPR tuple(allocator_arg_t __tag, const _Alloc& __a, const pair<_U1, _U2>& __in) - : _Inherited(__tag, __a, __in.first, __in.second) { } + : _Inherited(__tag, __a, __in.first, __in.second) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template = false> @@ -1570,14 +2083,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _GLIBCXX20_CONSTEXPR tuple(allocator_arg_t __tag, const _Alloc& __a, const pair<_U1, _U2>& __in) - : _Inherited(__tag, __a, __in.first, __in.second) { } + : _Inherited(__tag, __a, __in.first, __in.second) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template = true> _GLIBCXX20_CONSTEXPR tuple(allocator_arg_t __tag, const _Alloc& __a, pair<_U1, _U2>&& __in) : _Inherited(__tag, __a, std::forward<_U1>(__in.first), - std::forward<_U2>(__in.second)) { } + std::forward<_U2>(__in.second)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template = false> @@ -1585,25 +2100,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _GLIBCXX20_CONSTEXPR tuple(allocator_arg_t __tag, const _Alloc& __a, pair<_U1, _U2>&& __in) : _Inherited(__tag, __a, std::forward<_U1>(__in.first), - std::forward<_U2>(__in.second)) { } - -#if __cplusplus > 202002L - template - requires __constructible<_U1&, _U2&> - explicit(!__convertible<_U1&, _U2&>) - constexpr - tuple(allocator_arg_t __tag, const _Alloc& __a, - pair<_U1, _U2>& __in) - : _Inherited(__tag, __a, __in.first, __in.second) { } - - template - requires __constructible - explicit(!__convertible) - constexpr - tuple(allocator_arg_t __tag, const _Alloc& __a, const pair<_U1, _U2>&& __in) - : _Inherited(__tag, __a, std::forward(__in.first), - std::forward(__in.second)) { } -#endif // C++23 + std::forward<_U2>(__in.second)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } // Tuple assignment. @@ -1649,44 +2147,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return *this; } -#if __cplusplus > 202002L - constexpr const tuple& - operator=(const tuple& __in) const - requires is_copy_assignable_v && is_copy_assignable_v - { - this->_M_assign(__in); - return *this; - } - - constexpr const tuple& - operator=(tuple&& __in) const - requires is_assignable_v && is_assignable_v - { - this->_M_assign(std::move(__in)); - return *this; - } - - template - constexpr const tuple& - operator=(const tuple<_U1, _U2>& __in) const - requires is_assignable_v - && is_assignable_v - { - this->_M_assign(__in); - return *this; - } - - template - constexpr const tuple& - operator=(tuple<_U1, _U2>&& __in) const - requires is_assignable_v - && is_assignable_v - { - this->_M_assign(std::move(__in)); - return *this; - } -#endif // C++23 - template _GLIBCXX20_CONSTEXPR __enable_if_t<__assignable(), tuple&> @@ -1709,47 +2169,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return *this; } -#if __cplusplus > 202002L - template - constexpr const tuple& - operator=(const pair<_U1, _U2>& __in) const - requires is_assignable_v - && is_assignable_v - { - this->_M_head(*this) = __in.first; - this->_M_tail(*this)._M_head(*this) = __in.second; - return *this; - } - - template - constexpr const tuple& - operator=(pair<_U1, _U2>&& __in) const - requires is_assignable_v - && is_assignable_v - { - this->_M_head(*this) = std::forward<_U1>(__in.first); - this->_M_tail(*this)._M_head(*this) = std::forward<_U2>(__in.second); - return *this; - } -#endif // C++23 - _GLIBCXX20_CONSTEXPR void swap(tuple& __in) noexcept(__and_<__is_nothrow_swappable<_T1>, __is_nothrow_swappable<_T2>>::value) { _Inherited::_M_swap(__in); } - -#if __cplusplus > 202002L - constexpr void - swap(const tuple& __in) const - noexcept(__and_v<__is_nothrow_swappable, - __is_nothrow_swappable>) - requires is_swappable_v && is_swappable_v - { _Inherited::_M_swap(__in); } -#endif // C++23 }; - +#endif // concepts && conditional_explicit /// class tuple_size template @@ -2174,7 +2601,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION noexcept(noexcept(__x.swap(__y))) { __x.swap(__y); } -#if __cplusplus > 202002L +#if __cpp_lib_ranges_zip // >= C++23 template requires (is_swappable_v && ...) constexpr void @@ -2329,7 +2756,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } #endif -#if __cplusplus > 202002L +#if __cpp_lib_ranges_zip // >= C++23 template class _TQual, template class _UQual> requires requires { typename tuple, _UQual<_UTypes>>...>; } @@ -2344,6 +2771,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION /// @} +#undef __glibcxx_no_dangling_refs + _GLIBCXX_END_NAMESPACE_VERSION } // namespace std diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits index b6b680a3c583..a9bb2806ca9c 100644 --- a/libstdc++-v3/include/std/type_traits +++ b/libstdc++-v3/include/std/type_traits @@ -1306,6 +1306,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION "template argument must be a complete class or an unbounded array"); }; +#if __cpp_variable_templates && __cpp_concepts + template + constexpr bool __is_implicitly_default_constructible_v + = requires (void(&__f)(_Tp)) { __f({}); }; + + template + struct __is_implicitly_default_constructible + : __bool_constant<__is_implicitly_default_constructible_v<_Tp>> + { }; +#else struct __do_is_implicitly_default_constructible_impl { template @@ -1335,6 +1345,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION : public __and_<__is_constructible_impl<_Tp>, __is_implicitly_default_constructible_safe<_Tp>>::type { }; +#endif /// is_trivially_copy_constructible template diff --git a/libstdc++-v3/testsuite/20_util/tuple/dangling_ref.cc b/libstdc++-v3/testsuite/20_util/tuple/dangling_ref.cc new file mode 100644 index 000000000000..74fdc2423493 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/tuple/dangling_ref.cc @@ -0,0 +1,105 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-Wno-unused-variable" } +// { dg-additional-options "-D_GLIBCXX_DEBUG" { target c++17_down } } + +#include +#include + +#if __cplusplus >= 202002L +// For C++20 and later, constructors are constrained to disallow dangling. +static_assert(!std::is_constructible_v, long, int>); +static_assert(!std::is_constructible_v, int, long>); +static_assert(!std::is_constructible_v, + std::tuple>); +static_assert(!std::is_constructible_v, + std::tuple>); +static_assert(!std::is_constructible_v, + const std::tuple&>); +static_assert(!std::is_constructible_v, + const std::tuple&>); +static_assert(!std::is_constructible_v, + std::pair>); +static_assert(!std::is_constructible_v, + std::pair>); +static_assert(!std::is_constructible_v, + const std::pair&>); +static_assert(!std::is_constructible_v, + const std::pair&>); +#endif + +void +test_ary_ctors() +{ + std::tuple t1(1L, 2); + // { dg-error "here" "" { target { c++17_down && hosted } } 33 } + // { dg-error "use of deleted function" "" { target c++20 } 33 } + + std::tuple t2(1, 2L); + // { dg-error "here" "" { target { c++17_down && hosted } } 37 } + // { dg-error "use of deleted function" "" { target c++20 } 37 } + + std::tuple t3(1L, 2L); + // { dg-error "here" "" { target { c++17_down && hosted } } 41 } + // { dg-error "use of deleted function" "" { target c++20 } 41 } + + std::tuple t4(std::pair{}); + // { dg-error "here" "" { target { c++17_down && hosted } } 45 } + // { dg-error "use of deleted function" "" { target c++20 } 45 } + + std::pair p; + std::tuple t5(p); + // { dg-error "here" "" { target { c++17_down && hosted } } 50 } + // { dg-error "use of deleted function" "" { target c++20 } 50 } +} + +void +test_converting_ctors() +{ + std::tuple t0; + + std::tuple t1(t0); + // { dg-error "here" "" { target { c++17_down && hosted } } 60 } + // { dg-error "use of deleted function" "" { target c++20 } 60 } + + std::tuple t2(t0); + // { dg-error "here" "" { target { c++17_down && hosted } } 64 } + // { dg-error "use of deleted function" "" { target c++20 } 64 } + + std::tuple t3(t0); + // { dg-error "here" "" { target { c++17_down && hosted } } 68 } + // { dg-error "use of deleted function" "" { target c++20 } 68 } + + std::tuple t4(std::move(t0)); + // { dg-error "here" "" { target { c++17_down && hosted } } 72 } + // { dg-error "use of deleted function" "" { target c++20 } 72 } + + std::tuple t5(std::move(t0)); + // { dg-error "here" "" { target { c++17_down && hosted } } 76 } + // { dg-error "use of deleted function" "" { target c++20 } 76 } + + std::tuple t6(std::move(t0)); + // { dg-error "here" "" { target { c++17_down && hosted } } 80 } + // { dg-error "use of deleted function" "" { target c++20 } 80 } + + std::pair p0; + std::tuple t7(p0); + // { dg-error "here" "" { target { c++17_down && hosted } } 85 } + // { dg-error "use of deleted function" "" { target c++20 } 85 } + + std::tuple t8(p0); + // { dg-error "here" "" { target { c++17_down && hosted } } 89 } + // { dg-error "use of deleted function" "" { target c++20 } 89 } + + std::tuple t9(std::move(p0)); + // { dg-error "here" "" { target { c++17_down && hosted } } 93 } + // { dg-error "use of deleted function" "" { target c++20 } 93 } + + std::tuple t10(std::move(p0)); + // { dg-error "here" "" { target { c++17_down && hosted } } 97 } + // { dg-error "use of deleted function" "" { target c++20 } 97 } +} + +// TODO: test allocator-extended ctors +// TODO: test 1-tuple or 3-tuple, not just 2-tuple + +// { dg-error "static assert.* dangling reference" "" { target { c++17_down && hosted } } 0 } -- 2.47.2