Message ID | 20240111221651.585639-1-jwakely@redhat.com |
---|---|
State | New |
Headers | show |
Series | libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822] | expand |
On Fri, 12 Jan 2024 at 00:16, Jonathan Wakely <jwakely@redhat.com> wrote: > > I'd like to commit this to trunk for GCC 14. Please take a look. Without looking at it in excruciating detail, it's pretty much along the lines of what I have always envisioned to be a powerful combination of concepts and if-constexpr. My general principle on this is "looks like an improvement, so if it passes all the tests, ship it". :) Sure, I have envisioned going even further with that combination, such as significantly reducing the number of overloads and doing more of it as an if-constexpr ladder, but there's a balance where emulating the effects of overload resolution in something like that can become such a burden that the benefits are no longer there. If the field were green, I'd consider that as the approach from the get-go when initially designing a type like tuple, instead of doing it as an overload set.
On Thu, 11 Jan 2024 at 22:17, Jonathan Wakely wrote: > > I'd like to commit this to trunk for GCC 14. Please take a look. > > -- >8 -- > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. [snip] > +#if __cpp_concepts // >= C++20 > + private: > + template<typename... _UTypes> > + static consteval bool > + __assignable() This causes errors for -std=c++17 -fconcepts-ts because that defines __cpp_concepts=20157L, but does not allow C++20 consteval to be used. I used a different condition for the constructors: #if __cpp_concepts && __cpp_conditional_explicit // >= C++20 The difference is because the assignment ops don't use explicit. The additional check for __cpp_conditional_explicit means it already requires C++20, so doesn't match for -std=c++17 -fconcepts-ts. So that preprocessor group didn't cause problems. N.B. The different conditions means that for a compiler that supports concepts but not conditional explicit we will use concepts for the assignment ops, but not for the constructors. And you'll still get the partial specialization for std::tuple<T1,T2>, and that partial specialization will be missing the C++23 constructors for ranges::zip. I think that's fine - if you don't have a good enough C++20 compiler (i.e. one that defines __cpp_conditional_explicit) then you don't get a complete C++20 std::tuple, let alone a complete C++23 std::tuple. dealwithit.jpg I could just use constexpr instead of consteval for those helper functions, but I think I will add a check for __cpp_consteval. I don't feel comfortable trying to make the new assignment ops work with -std=c++17 -fconcepts-ts as there might be other interactions with C++20 features that will go unnoticed, as we don't routinely test the whole library with C++17 + Concepts TS.
On Thu, 11 Jan 2024, Jonathan Wakely wrote: > I'd like to commit this to trunk for GCC 14. Please take a look. > > -- >8 -- > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. > --- > libstdc++-v3/include/std/tuple | 1021 ++++++++++++----- > libstdc++-v3/include/std/type_traits | 11 + > .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ > 3 files changed, 841 insertions(+), 296 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 50e11843757..cd05b638923 100644 > --- a/libstdc++-v3/include/std/tuple > +++ b/libstdc++-v3/include/std/tuple > @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > template<typename... _Elements> > class tuple : public _Tuple_impl<0, _Elements...> > { > - typedef _Tuple_impl<0, _Elements...> _Inherited; > + using _Inherited = _Tuple_impl<0, _Elements...>; > > template<bool _Cond> > using _TCC = _TupleConstraints<_Cond, _Elements...>; I guess this should be moved into the #else branch if it's not used in the new impl. > > +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 > + template<typename... _UTypes> > + static consteval bool > + __constructible() > + { > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > + return (is_constructible_v<_Elements, _UTypes> && ...); IIUC this (and all the other new constraints) won't short-circuit like the old versions do :/ Not sure how much that matters? > + else > + return false; > + } > + > + template<typename... _UTypes> > + static consteval bool > + __nothrow_constructible() > + { > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > + return (is_nothrow_constructible_v<_Elements, _UTypes> && ...); > + else > + return false; > + } > + > + template<typename... _UTypes> > + static consteval bool > + __convertible() > + { > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > + return (is_convertible_v<_UTypes, _Elements> && ...); > + else > + return false; > + } > + > + // _GLIBCXX_RESOLVE_LIB_DEFECTS > + // 3121. tuple constructor constraints for UTypes&&... overloads > + template<typename... _UTypes> > + 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<remove_cvref_t<_U0>, tuple>; > + } > + else if constexpr (sizeof...(_Elements) < 4) > + { > + using _U0 = typename _Nth_type<0, _UTypes...>::type; > + if constexpr (!is_same_v<remove_cvref_t<_U0>, allocator_arg_t>) > + return true; > + else > + { > + using _T0 = typename _Nth_type<0, _Elements...>::type; > + return is_same_v<remove_cvref_t<_T0>, allocator_arg_t>; > + } > + } > + return true; > + } > + > + // Return true iff sizeof...(Types) == 1 && tuple_size_v<TUPLE> == 1 > + // and the single element in Types can be initialized from TUPLE, > + // or is the same type as tuple_element_t<0, TUPLE>. > + template<typename _Tuple> > + static consteval bool > + __use_other_ctor() > + { > + if constexpr (sizeof...(_Elements) != 1) > + return false; > + else if constexpr (is_same_v<remove_cvref_t<_Tuple>, 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<typename... _Up> > + 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<const _Elements&...>()) > + tuple(const _Elements&... __elements) > + noexcept(__nothrow_constructible<const _Elements&...>()) > + requires (__constructible<const _Elements&...>()) > + : _Inherited(__elements...) > + { } > + > + template<typename... _UTypes> > + 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<typename... _UTypes> > + requires (__disambiguating_constraint<_UTypes...>()) > + && (__constructible<_UTypes...>()) > + && (__dangles<_UTypes...>()) > + tuple(_UTypes&&...) = delete; > + > + constexpr tuple(const tuple&) = default; > + > + constexpr tuple(tuple&&) = default; > + > + template<typename... _UTypes> > + requires (__constructible<const _UTypes&...>()) > + && (!__use_other_ctor<const tuple<_UTypes...>&>()) > + && (!__dangles<const _UTypes&...>()) > + constexpr explicit(!__convertible<const _UTypes&...>()) > + tuple(const tuple<_UTypes...>& __u) > + noexcept(__nothrow_constructible<const _UTypes&...>()) > + : _Inherited(static_cast<const _Tuple_impl<0, _UTypes...>&>(__u)) > + { } > + > + template<typename... _UTypes> > + requires (__constructible<const _UTypes&...>()) > + && (!__use_other_ctor<const tuple<_UTypes...>&>()) > + && (__dangles<const _UTypes&...>()) > + tuple(const tuple<_UTypes...>&) = delete; > + > + template<typename... _UTypes> > + requires (__constructible<_UTypes...>()) > + && (!__use_other_ctor<tuple<_UTypes...>>()) > + && (!__dangles<_UTypes...>()) > + constexpr explicit(!__convertible<_UTypes...>()) > + tuple(tuple<_UTypes...>&& __u) > + noexcept(__nothrow_constructible<_UTypes...>()) > + : _Inherited(static_cast<_Tuple_impl<0, _UTypes...>&&>(__u)) > + { } > + > + template<typename... _UTypes> > + requires (__constructible<_UTypes...>()) > + && (!__use_other_ctor<tuple<_UTypes...>>()) > + && (__dangles<_UTypes...>()) > + tuple(tuple<_UTypes...>&&) = delete; > + > +#if __cpp_lib_ranges_zip // >= C++23 > + template<typename... _UTypes> > + requires (__constructible<_UTypes&...>()) > + && (!__use_other_ctor<tuple<_UTypes...>&>()) > + && (!__dangles<_UTypes&...>()) > + constexpr explicit(!__convertible<_UTypes&...>()) > + tuple(tuple<_UTypes...>& __u) > + noexcept(__nothrow_constructible<_UTypes&...>()) > + : _Inherited(static_cast<_Tuple_impl<0, _UTypes...>&>(__u)) > + { } > + > + template<typename... _UTypes> > + requires (__constructible<_UTypes&...>()) > + && (!__use_other_ctor<tuple<_UTypes...>&>()) > + && (__dangles<_UTypes&...>()) > + tuple(tuple<_UTypes...>&) = delete; > + > + template<typename... _UTypes> > + requires (__constructible<const _UTypes...>()) > + && (!__use_other_ctor<const tuple<_UTypes...>>()) > + && (!__dangles<const _UTypes...>()) > + constexpr explicit(!__convertible<const _UTypes...>()) > + tuple(const tuple<_UTypes...>&& __u) > + noexcept(__nothrow_constructible<const _UTypes...>()) > + : _Inherited(static_cast<const _Tuple_impl<0, _UTypes...>&&>(__u)) > + { } > + > + template<typename... _UTypes> > + requires (__constructible<const _UTypes...>()) > + && (!__use_other_ctor<const tuple<_UTypes...>>()) > + && (__dangles<const _UTypes...>()) > + tuple(const tuple<_UTypes...>&&) = delete; > +#endif // C++23 > + > + template<typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<const _U1&, const _U2&>()) > + && (!__dangles<const _U1&, const _U2&>()) > + constexpr explicit(!__convertible<const _U1&, const _U2&>()) > + tuple(const pair<_U1, _U2>& __u) > + noexcept(__nothrow_constructible<const _U1&, const _U2&>()) > + : _Inherited(__u.first, __u.second) > + { } > + > + template<typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<const _U1&, const _U2&>()) > + && (__dangles<const _U1&, const _U2&>()) > + tuple(const pair<_U1, _U2>&) = delete; > + > + template<typename _U1, typename _U2> > + 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<typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<_U1, _U2>()) > + && (__dangles<_U1, _U2>()) > + tuple(pair<_U1, _U2>&&) = delete; > + > +#if __cpp_lib_ranges_zip // >= C++23 > + template<typename _U1, typename _U2> > + 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<typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<_U1&, _U2&>()) > + && (__dangles<_U1&, _U2&>()) > + tuple(pair<_U1, _U2>&) = delete; > + > + template<typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<const _U1, const _U2>()) > + && (!__dangles<const _U1, const _U2>()) > + constexpr explicit(!__convertible<const _U1, const _U2>()) > + tuple(const pair<_U1, _U2>&& __u) > + noexcept(__nothrow_constructible<const _U1, const _U2>()) > + : _Inherited(std::forward<const _U1>(__u.first), > + std::forward<const _U2>(__u.second)) > + { } > + > + template<typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<const _U1, const _U2>()) > + && (__dangles<const _U1, const _U2>()) > + 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<typename _Alloc> > + 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<typename _Alloc> > + constexpr explicit(!__convertible<const _Elements&...>()) > + tuple(allocator_arg_t __tag, const _Alloc& __a, > + const _Elements&... __elements) > + requires (__constructible<const _Elements&...>()) > + : _Inherited(__tag, __a, __elements...) > + { } > + > + template<typename _Alloc, typename... _UTypes> > + 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<typename _Alloc, typename... _UTypes> > + requires (__disambiguating_constraint<_UTypes...>()) > + && (__constructible<_UTypes...>()) > + && (__dangles<_UTypes...>()) > + tuple(allocator_arg_t, const _Alloc&, _UTypes&&...) = delete; > + > + template<typename _Alloc> > + constexpr > + tuple(allocator_arg_t __tag, const _Alloc& __a, const tuple& __u) > + : _Inherited(__tag, __a, static_cast<const _Inherited&>(__u)) > + { } > + > + template<typename _Alloc> > + requires (__constructible<_Elements...>()) > + constexpr > + tuple(allocator_arg_t __tag, const _Alloc& __a, tuple&& __u) > + : _Inherited(__tag, __a, static_cast<_Inherited&&>(__u)) > + { } > + > + template<typename _Alloc, typename... _UTypes> > + requires (__constructible<const _UTypes&...>()) > + && (!__use_other_ctor<const tuple<_UTypes...>&>()) > + && (!__dangles<const _UTypes&...>()) > + constexpr explicit(!__convertible<const _UTypes&...>()) > + tuple(allocator_arg_t __tag, const _Alloc& __a, > + const tuple<_UTypes...>& __u) > + : _Inherited(__tag, __a, > + static_cast<const _Tuple_impl<0, _UTypes...>&>(__u)) > + { } > + > + template<typename _Alloc, typename... _UTypes> > + requires (__constructible<const _UTypes&...>()) > + && (!__use_other_ctor<const tuple<_UTypes...>&>()) > + && (__dangles<const _UTypes&...>()) > + tuple(allocator_arg_t, const _Alloc&, const tuple<_UTypes...>&) = delete; > + > + template<typename _Alloc, typename... _UTypes> > + requires (__constructible<_UTypes...>()) > + && (!__use_other_ctor<tuple<_UTypes...>>()) > + && (!__dangles<_UTypes...>()) > + constexpr explicit(!__use_other_ctor<tuple<_UTypes...>>()) > + tuple(allocator_arg_t __tag, const _Alloc& __a, tuple<_UTypes...>&& __u) > + : _Inherited(__tag, __a, static_cast<_Tuple_impl<0, _UTypes...>&&>(__u)) > + { } > + > + template<typename _Alloc, typename... _UTypes> > + requires (__constructible<_UTypes...>()) > + && (!__use_other_ctor<tuple<_UTypes...>>()) > + && (__dangles<_UTypes...>()) > + tuple(allocator_arg_t, const _Alloc&, tuple<_UTypes...>&&) = delete; > + > +#if __cpp_lib_ranges_zip // >= C++23 > + template<typename _Alloc, typename... _UTypes> > + requires (__constructible<_UTypes&...>()) > + && (!__use_other_ctor<tuple<_UTypes...>&>()) > + && (!__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<typename _Alloc, typename... _UTypes> > + requires (__constructible<_UTypes&...>()) > + && (!__use_other_ctor<tuple<_UTypes...>&>()) > + && (__dangles<_UTypes&...>()) > + tuple(allocator_arg_t, const _Alloc&, tuple<_UTypes...>&) = delete; > + > + template<typename _Alloc, typename... _UTypes> > + requires (__constructible<const _UTypes...>()) > + && (!__use_other_ctor<const tuple<_UTypes...>>()) > + && (!__dangles<const _UTypes...>()) > + constexpr explicit(!__convertible<const _UTypes...>()) > + tuple(allocator_arg_t __tag, const _Alloc& __a, > + const tuple<_UTypes...>&& __u) > + : _Inherited(__tag, __a, > + static_cast<const _Tuple_impl<0, _UTypes...>&&>(__u)) > + { } > + > + template<typename _Alloc, typename... _UTypes> > + requires (__constructible<const _UTypes...>()) > + && (!__use_other_ctor<const tuple<_UTypes...>>()) > + && (__dangles<const _UTypes...>()) > + tuple(allocator_arg_t, const _Alloc&, const tuple<_UTypes...>&&) = delete; > +#endif // C++23 > + > + template<typename _Alloc, typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<const _U1&, const _U2&>()) > + && (!__dangles<const _U1&, const _U2&>()) > + constexpr explicit(!__convertible<const _U1&, const _U2&>()) > + tuple(allocator_arg_t __tag, const _Alloc& __a, > + const pair<_U1, _U2>& __u) > + noexcept(__nothrow_constructible<const _U1&, const _U2&>()) > + : _Inherited(__tag, __a, __u.first, __u.second) > + { } > + > + template<typename _Alloc, typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<const _U1&, const _U2&>()) > + && (__dangles<const _U1&, const _U2&>()) > + tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&) = delete; > + > + template<typename _Alloc, typename _U1, typename _U2> > + 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<typename _Alloc, typename _U1, typename _U2> > + 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<typename _Alloc, typename _U1, typename _U2> > + 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<typename _Alloc, typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<_U1&, _U2&>()) > + && (__dangles<_U1&, _U2&>()) > + tuple(allocator_arg_t, const _Alloc&, pair<_U1, _U2>&) = delete; > + > + template<typename _Alloc, typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<const _U1, const _U2>()) > + && (!__dangles<const _U1, const _U2>()) > + constexpr explicit(!__convertible<const _U1, const _U2>()) > + tuple(allocator_arg_t __tag, const _Alloc& __a, > + const pair<_U1, _U2>&& __u) > + noexcept(__nothrow_constructible<const _U1, const _U2>()) > + : _Inherited(__tag, __a, std::move(__u.first), std::move(__u.second)) > + { } > + > + template<typename _Alloc, typename _U1, typename _U2> > + requires (sizeof...(_Elements) == 2) > + && (__constructible<const _U1, const _U2>()) > + && (__dangles<const _U1, const _U2>()) > + tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete; > +#endif // C++23 > + > +#if 0 && __cpp_lib_tuple_like // >= C++23 > + template<typename _Alloc, __tuple_like _UTuple> > + constexpr explicit(...) > + tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u); > +#endif // C++23 > + > +#else // !(concepts && conditional_explicit) > + > // Constraint for non-explicit default constructor > template<bool _Dummy> > using _ImplicitDefaultCtor = __enable_if_t< > @@ -850,15 +1306,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > static constexpr bool __use_other_ctor() > { return _UseOtherCtor<_Tuple>::value; } > > -#if __cplusplus > 202002L > - template<typename... _Args> > - static constexpr bool __constructible > - = _TCC<true>::template __constructible<_Args...>::value; > - > - template<typename... _Args> > - static constexpr bool __convertible > - = _TCC<true>::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<typename _Dummy = void, > @@ -895,7 +1362,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > constexpr > tuple(_UElements&&... __elements) > noexcept(__nothrow_constructible<_UElements...>()) > - : _Inherited(std::forward<_UElements>(__elements)...) { } > + : _Inherited(std::forward<_UElements>(__elements)...) > + { __glibcxx_no_dangling_refs(_UElements&&); } > > template<typename... _UElements, > bool _Valid = __valid_args<_UElements...>(), > @@ -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<const _UElements&...>()) > : _Inherited(static_cast<const _Tuple_impl<0, _UElements...>&>(__in)) > - { } > + { __glibcxx_no_dangling_refs(const _UElements&); } > > template<typename... _UElements, > bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) > @@ -927,7 +1396,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > tuple(const tuple<_UElements...>& __in) > noexcept(__nothrow_constructible<const _UElements&...>()) > : _Inherited(static_cast<const _Tuple_impl<0, _UElements...>&>(__in)) > - { } > + { __glibcxx_no_dangling_refs(const _UElements&); } > > template<typename... _UElements, > bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) > @@ -936,7 +1405,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > constexpr > tuple(tuple<_UElements...>&& __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<typename... _UElements, > bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) > @@ -945,30 +1415,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > explicit constexpr > tuple(tuple<_UElements...>&& __in) > noexcept(__nothrow_constructible<_UElements...>()) > - : _Inherited(static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) { } > - > -#if __cplusplus > 202002L > - template<typename... _UElements> > - requires (sizeof...(_Elements) == sizeof...(_UElements)) > - && (!__use_other_ctor<tuple<_UElements...>&>()) > - && __constructible<_UElements&...> > - explicit(!__convertible<_UElements&...>) > - constexpr > - tuple(tuple<_UElements...>& __in) > - noexcept(__nothrow_constructible<_UElements&...>()) > - : _Inherited(static_cast<_Tuple_impl<0, _UElements...>&>(__in)) > - { } > - > - template<typename... _UElements> > - requires (sizeof...(_Elements) == sizeof...(_UElements)) > - && (!__use_other_ctor<const tuple<_UElements...>&&>()) > - && __constructible<const _UElements...> > - explicit(!__convertible<const _UElements...>) > - constexpr > - tuple(const tuple<_UElements...>&& __in) > - noexcept(__nothrow_constructible<const _UElements...>()) > - : _Inherited(static_cast<const _Tuple_impl<0, _UElements...>&&>(__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<typename _Alloc, typename... _UElements, > bool _Valid = __valid_args<_UElements...>(), > @@ -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<typename _Alloc> > _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<const _Tuple_impl<0, _UElements...>&>(__in)) > - { } > + static_cast<const _Tuple_impl<0, _UElements...>&>(__in)) > + { __glibcxx_no_dangling_refs(const _UElements&); } > > template<typename _Alloc, typename... _UElements, > bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) > @@ -1042,8 +1490,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > tuple(allocator_arg_t __tag, const _Alloc& __a, > const tuple<_UElements...>& __in) > : _Inherited(__tag, __a, > - static_cast<const _Tuple_impl<0, _UElements...>&>(__in)) > - { } > + static_cast<const _Tuple_impl<0, _UElements...>&>(__in)) > + { __glibcxx_no_dangling_refs(const _UElements&); } > > template<typename _Alloc, typename... _UElements, > bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) > @@ -1053,8 +1501,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > tuple(allocator_arg_t __tag, const _Alloc& __a, > tuple<_UElements...>&& __in) > : _Inherited(__tag, __a, > - static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) > - { } > + static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) > + { __glibcxx_no_dangling_refs(_UElements&&); } > > template<typename _Alloc, typename... _UElements, > bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) > @@ -1065,37 +1513,180 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > tuple(allocator_arg_t __tag, const _Alloc& __a, > tuple<_UElements...>&& __in) > : _Inherited(__tag, __a, > - static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) > - { } > - > -#if __cplusplus > 202002L > - template<typename _Alloc, typename... _UElements> > - requires (sizeof...(_Elements) == sizeof...(_UElements)) > - && (!__use_other_ctor<tuple<_UElements...>&>()) > - && __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)) > - { } > - > - template<typename _Alloc, typename... _UElements> > - requires (sizeof...(_Elements) == sizeof...(_UElements)) > - && (!__use_other_ctor<const tuple<_UElements...>>()) > - && __constructible<const _UElements...> > - explicit(!__convertible<const _UElements...>) > - constexpr > - tuple(allocator_arg_t __tag, const _Alloc& __a, > - const tuple<_UElements...>&& __in) > - : _Inherited(__tag, __a, > - static_cast<const _Tuple_impl<0, _UElements...>&&>(__in)) > - { } > -#endif // C++23 > + static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) > + { __glibcxx_no_dangling_refs(_UElements&&); } > +#endif // concepts && conditional_explicit > > // tuple assignment > > +#if __cpp_concepts // >= C++20 > + private: > + template<typename... _UTypes> > + static consteval bool > + __assignable() > + { > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > + return (is_assignable_v<_Elements&, _UTypes> && ...); > + else > + return false; > + } > + > + template<typename... _UTypes> > + static consteval bool > + __nothrow_assignable() > + { > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > + return (is_nothrow_assignable_v<_Elements&, _UTypes> && ...); > + else > + return false; > + } > + > +#if __cpp_lib_ranges_zip // >= C++23 > + template<typename... _UTypes> > + static consteval bool > + __const_assignable() > + { > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > + return (is_assignable_v<const _Elements&, _UTypes> && ...); > + else > + return false; > + } > +#endif // C++23 > + > + public: > + > + tuple& operator=(const tuple& __u) = delete; > + > + constexpr tuple& > + operator=(const tuple& __u) > + noexcept(__nothrow_assignable<const _Elements&...>()) > + requires (__assignable<const _Elements&...>()) > + { > + 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<typename... _UTypes> > + requires (__assignable<const _UTypes&...>()) > + constexpr tuple& > + operator=(const tuple<_UTypes...>& __u) > + noexcept(__nothrow_assignable<const _UTypes&...>()) > + { > + this->_M_assign(__u); > + return *this; > + } > + > + template<typename... _UTypes> > + 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<const _Elements&...>()) > + { > + 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<typename... _UTypes> > + constexpr const tuple& > + operator=(const tuple<_UTypes...>& __u) const > + requires (__const_assignable<const _UTypes&...>()) > + { > + this->_M_assign(__u); > + return *this; > + } > + > + template<typename... _UTypes> > + constexpr const tuple& > + operator=(tuple<_UTypes...>&& __u) const > + requires (__const_assignable<_UTypes...>()) > + { > + this->_M_assign(std::move(__u)); > + return *this; > + } > +#endif // C++23 > + > + template<typename _U1, typename _U2> > + requires (__assignable<const _U1&, const _U2&>()) > + constexpr tuple& > + operator=(const pair<_U1, _U2>& __u) > + noexcept(__nothrow_assignable<const _U1&, const _U2&>()) > + { > + this->_M_head(*this) = __u.first; > + this->_M_tail(*this)._M_head(*this) = __u.second; > + return *this; > + } > + > + template<typename _U1, typename _U2> > + 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<typename _U1, typename _U2> > + requires (__const_assignable<const _U1&, const _U2>()) > + 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<typename _U1, typename _U2> > + 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 // ! concepts > + > _GLIBCXX20_CONSTEXPR > tuple& > operator=(__conditional_t<__assignable<const _Elements&...>(), > @@ -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<const _Elements> && ...) > - { > - this->_M_assign(__in); > - return *this; > - } > - > - constexpr const tuple& > - operator=(tuple&& __in) const > - requires (is_assignable_v<const _Elements&, _Elements> && ...) > - { > - this->_M_assign(std::move(__in)); > - return *this; > - } > - > - template<typename... _UElements> > - constexpr const tuple& > - operator=(const tuple<_UElements...>& __in) const > - requires (sizeof...(_Elements) == sizeof...(_UElements)) > - && (is_assignable_v<const _Elements&, const _UElements&> && ...) > - { > - this->_M_assign(__in); > - return *this; > - } > - > - template<typename... _UElements> > - constexpr const tuple& > - operator=(tuple<_UElements...>&& __in) const > - requires (sizeof...(_Elements) == sizeof...(_UElements)) > - && (is_assignable_v<const _Elements&, _UElements> && ...) > - { > - 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_conditional_explicit) // >= C++20 > /// Partial specialization, 2-element tuple. > /// Includes construction and assignment from a pair. > template<typename _T1, typename _T2> > @@ -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<typename _U1, typename _U2> > - static constexpr bool __constructible > - = _TCC<true>::template __constructible<_U1, _U2>::value; > - > - template<typename _U1, typename _U2> > - static constexpr bool __convertible > - = _TCC<true>::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<bool _Dummy = true, > @@ -1344,14 +1903,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > 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&&); } > > template<typename _U1, typename _U2, > _ExplicitCtor<!__is_alloc_arg<_U1>(), _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<const _U1&, const _U2&>()) > - : _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) { } > + : _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) > + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } > > template<typename _U1, typename _U2, > _ExplicitCtor<true, const _U1&, const _U2&> = false> > explicit constexpr > tuple(const tuple<_U1, _U2>& __in) > noexcept(__nothrow_constructible<const _U1&, const _U2&>()) > - : _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) { } > + : _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) > + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } > > template<typename _U1, typename _U2, > _ImplicitCtor<true, _U1, _U2> = 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<typename _U1, typename _U2, > _ExplicitCtor<true, _U1, _U2> = 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<typename _U1, typename _U2> > - 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<typename _U1, typename _U2> > - requires __constructible<const _U1, const _U2> > - explicit(!__convertible<const _U1, const _U2>) > - constexpr > - tuple(const tuple<_U1, _U2>&& __in) > - noexcept(__nothrow_constructible<const _U1, const _U2>()) > - : _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&&>(__in)) { } > -#endif // C++23 > + : _Inherited(static_cast<_Tuple_impl<0, _U1, _U2>&&>(__in)) > + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } > > template<typename _U1, typename _U2, > _ImplicitCtor<true, const _U1&, const _U2&> = true> > constexpr > tuple(const pair<_U1, _U2>& __in) > noexcept(__nothrow_constructible<const _U1&, const _U2&>()) > - : _Inherited(__in.first, __in.second) { } > + : _Inherited(__in.first, __in.second) > + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } > > template<typename _U1, typename _U2, > _ExplicitCtor<true, const _U1&, const _U2&> = false> > explicit constexpr > tuple(const pair<_U1, _U2>& __in) > noexcept(__nothrow_constructible<const _U1&, const _U2&>()) > - : _Inherited(__in.first, __in.second) { } > + : _Inherited(__in.first, __in.second) > + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } > > template<typename _U1, typename _U2, > _ImplicitCtor<true, _U1, _U2> = 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<typename _U1, typename _U2, > _ExplicitCtor<true, _U1, _U2> = 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<typename _U1, typename _U2> > - 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<typename _U1, typename _U2> > - requires __constructible<const _U1, const _U2> > - explicit(!__convertible<const _U1, const _U2>) > - constexpr > - tuple(const pair<_U1, _U2>&& __in) > - noexcept(__nothrow_constructible<const _U1, const _U2>()) > - : _Inherited(std::forward<const _U1>(__in.first), > - std::forward<const _U2>(__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<typename _Alloc, typename _U1, typename _U2, > _ExplicitCtor<true, _U1, _U2> = 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<typename _Alloc> > _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<const _Tuple_impl<0, _U1, _U2>&>(__in)) > - { } > + static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) > + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } > > template<typename _Alloc, typename _U1, typename _U2, > _ExplicitCtor<true, const _U1&, const _U2&> = 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<const _Tuple_impl<0, _U1, _U2>&>(__in)) > - { } > + static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) > + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } > > template<typename _Alloc, typename _U1, typename _U2, > _ImplicitCtor<true, _U1, _U2> = 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<typename _Alloc, typename _U1, typename _U2, > _ExplicitCtor<true, _U1, _U2> = 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<typename _Alloc, typename _U1, typename _U2> > - 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<typename _Alloc, typename _U1, typename _U2> > - requires __constructible<const _U1, const _U2> > - explicit(!__convertible<const _U1, const _U2>) > - constexpr > - tuple(allocator_arg_t __tag, const _Alloc& __a, > - const tuple<_U1, _U2>&& __in) > - : _Inherited(__tag, __a, > - static_cast<const _Tuple_impl<0, _U1, _U2>&&>(__in)) > - { } > -#endif // C++23 > + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } > > template<typename _Alloc, typename _U1, typename _U2, > _ImplicitCtor<true, const _U1&, const _U2&> = 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<typename _Alloc, typename _U1, typename _U2, > _ExplicitCtor<true, const _U1&, const _U2&> = 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<typename _Alloc, typename _U1, typename _U2, > _ImplicitCtor<true, _U1, _U2> = 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<typename _Alloc, typename _U1, typename _U2, > _ExplicitCtor<true, _U1, _U2> = 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<typename _Alloc, typename _U1, typename _U2> > - 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<typename _Alloc, typename _U1, typename _U2> > - requires __constructible<const _U1, const _U2> > - explicit(!__convertible<const _U1, const _U2>) > - constexpr > - tuple(allocator_arg_t __tag, const _Alloc& __a, const pair<_U1, _U2>&& __in) > - : _Inherited(__tag, __a, std::forward<const _U1>(__in.first), > - std::forward<const _U2>(__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<const _T1> && is_copy_assignable_v<const _T2> > - { > - this->_M_assign(__in); > - return *this; > - } > - > - constexpr const tuple& > - operator=(tuple&& __in) const > - requires is_assignable_v<const _T1&, _T1> && is_assignable_v<const _T2, _T2> > - { > - this->_M_assign(std::move(__in)); > - return *this; > - } > - > - template<typename _U1, typename _U2> > - constexpr const tuple& > - operator=(const tuple<_U1, _U2>& __in) const > - requires is_assignable_v<const _T1&, const _U1&> > - && is_assignable_v<const _T2&, const _U2&> > - { > - this->_M_assign(__in); > - return *this; > - } > - > - template<typename _U1, typename _U2> > - constexpr const tuple& > - operator=(tuple<_U1, _U2>&& __in) const > - requires is_assignable_v<const _T1&, _U1> > - && is_assignable_v<const _T2&, _U2> > - { > - this->_M_assign(std::move(__in)); > - return *this; > - } > -#endif // C++23 > - > template<typename _U1, typename _U2> > _GLIBCXX20_CONSTEXPR > __enable_if_t<__assignable<const _U1&, const _U2&>(), tuple&> > @@ -1709,47 +2169,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > return *this; > } > > -#if __cplusplus > 202002L > - template<typename _U1, typename _U2> > - constexpr const tuple& > - operator=(const pair<_U1, _U2>& __in) const > - requires is_assignable_v<const _T1&, const _U1&> > - && is_assignable_v<const _T2&, const _U2&> > - { > - this->_M_head(*this) = __in.first; > - this->_M_tail(*this)._M_head(*this) = __in.second; > - return *this; > - } > - > - template<typename _U1, typename _U2> > - constexpr const tuple& > - operator=(pair<_U1, _U2>&& __in) const > - requires is_assignable_v<const _T1&, _U1> > - && is_assignable_v<const _T2&, _U2> > - { > - 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<const _T1>, > - __is_nothrow_swappable<const _T2>>) > - requires is_swappable_v<const _T1> && is_swappable_v<const _T2> > - { _Inherited::_M_swap(__in); } > -#endif // C++23 > }; > - > +#endif // concepts && conditional_explicit > > /// class tuple_size > template<typename... _Elements> > @@ -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<typename... _Elements> > requires (is_swappable_v<const _Elements> && ...) > constexpr void > @@ -2329,7 +2756,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > } > #endif > > -#if __cplusplus > 202002L > +#if __cpp_lib_ranges_zip // >= C++23 > template<typename... _TTypes, typename... _UTypes, > template<typename> class _TQual, template<typename> class _UQual> > requires requires { typename tuple<common_reference_t<_TQual<_TTypes>, _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 b6b680a3c58..a9bb2806ca9 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<typename _Tp> > + constexpr bool __is_implicitly_default_constructible_v > + = requires (void(&__f)(_Tp)) { __f({}); }; > + > + template<typename _Tp> > + struct __is_implicitly_default_constructible > + : __bool_constant<__is_implicitly_default_constructible_v<_Tp>> > + { }; > +#else > struct __do_is_implicitly_default_constructible_impl > { > template <typename _Tp> > @@ -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<typename _Tp> > 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 00000000000..c6c8e0c3ef4 > --- /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 <tuple> > +#include <utility> > + > +#if __cplusplus >= 202002L > +// For C++20 and later, constructors are constrained to disallow dangling. > +static_assert(!std::is_constructible_v<std::tuple<const int&, int>, long, int>); > +static_assert(!std::is_constructible_v<std::tuple<int, const int&>, int, long>); > +static_assert(!std::is_constructible_v<std::tuple<const int&, int>, > + std::tuple<long, long>>); > +static_assert(!std::is_constructible_v<std::tuple<int, const int&>, > + std::tuple<long, long>>); > +static_assert(!std::is_constructible_v<std::tuple<const int&, int>, > + const std::tuple<long, long>&>); > +static_assert(!std::is_constructible_v<std::tuple<int, const int&>, > + const std::tuple<long, long>&>); > +static_assert(!std::is_constructible_v<std::tuple<const int&, int>, > + std::pair<long, long>>); > +static_assert(!std::is_constructible_v<std::tuple<int, const int&>, > + std::pair<long, long>>); > +static_assert(!std::is_constructible_v<std::tuple<const int&, int>, > + const std::pair<long, long>&>); > +static_assert(!std::is_constructible_v<std::tuple<int, const int&>, > + const std::pair<long, long>&>); > +#endif > + > +void > +test_ary_ctors() > +{ > + std::tuple<const int&, int> t1(1L, 2); > + // { dg-error "here" "" { target { c++17_down && hosted } } 33 } > + // { dg-error "use of deleted function" "" { target c++20 } 33 } > + > + std::tuple<int, const int&> t2(1, 2L); > + // { dg-error "here" "" { target { c++17_down && hosted } } 37 } > + // { dg-error "use of deleted function" "" { target c++20 } 37 } > + > + std::tuple<const int&, const int&> t3(1L, 2L); > + // { dg-error "here" "" { target { c++17_down && hosted } } 41 } > + // { dg-error "use of deleted function" "" { target c++20 } 41 } > + > + std::tuple<const int&, const int&> t4(std::pair<long, int>{}); > + // { dg-error "here" "" { target { c++17_down && hosted } } 45 } > + // { dg-error "use of deleted function" "" { target c++20 } 45 } > + > + std::pair<int, long> p; > + std::tuple<const int&, const int&> 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<long, long> t0; > + > + std::tuple<const int&, int> t1(t0); > + // { dg-error "here" "" { target { c++17_down && hosted } } 60 } > + // { dg-error "use of deleted function" "" { target c++20 } 60 } > + > + std::tuple<int, const int&> t2(t0); > + // { dg-error "here" "" { target { c++17_down && hosted } } 64 } > + // { dg-error "use of deleted function" "" { target c++20 } 64 } > + > + std::tuple<const int&, const int&> t3(t0); > + // { dg-error "here" "" { target { c++17_down && hosted } } 68 } > + // { dg-error "use of deleted function" "" { target c++20 } 68 } > + > + std::tuple<const int&, int> 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<int, const int&> 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<const int&, const int&> 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<long, long> p0; > + std::tuple<const int&, int> t7(p0); > + // { dg-error "here" "" { target { c++17_down && hosted } } 85 } > + // { dg-error "use of deleted function" "" { target c++20 } 85 } > + > + std::tuple<int, const int&> t8(p0); > + // { dg-error "here" "" { target { c++17_down && hosted } } 89 } > + // { dg-error "use of deleted function" "" { target c++20 } 89 } > + > + std::tuple<const int&, int> 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<int, const int&> 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.43.0 > >
On Fri, 12 Jan 2024 at 17:55, Patrick Palka <ppalka@redhat.com> wrote: > > On Thu, 11 Jan 2024, Jonathan Wakely wrote: > > > I'd like to commit this to trunk for GCC 14. Please take a look. > > > > -- >8 -- > > > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. > > --- > > libstdc++-v3/include/std/tuple | 1021 ++++++++++++----- > > libstdc++-v3/include/std/type_traits | 11 + > > .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ > > 3 files changed, 841 insertions(+), 296 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 50e11843757..cd05b638923 100644 > > --- a/libstdc++-v3/include/std/tuple > > +++ b/libstdc++-v3/include/std/tuple > > @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > template<typename... _Elements> > > class tuple : public _Tuple_impl<0, _Elements...> > > { > > - typedef _Tuple_impl<0, _Elements...> _Inherited; > > + using _Inherited = _Tuple_impl<0, _Elements...>; > > > > template<bool _Cond> > > using _TCC = _TupleConstraints<_Cond, _Elements...>; > > I guess this should be moved into the #else branch if it's not used in > the new impl. Ah yes, I left them there until I was sure I wouldn't need them ... then didn't move them when I didn't need them. > > > > > +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 > > + template<typename... _UTypes> > > + static consteval bool > > + __constructible() > > + { > > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > + return (is_constructible_v<_Elements, _UTypes> && ...); > > IIUC this (and all the other new constraints) won't short-circuit like > the old versions do :/ Not sure how much that matters? Yeah, I thought about that, but we have efficient built-ins for these traits now, so I think it's probably OK? If not we could go back to sharing the _TupleConstraints implementations.
On Fri, 12 Jan 2024, Jonathan Wakely wrote: > On Fri, 12 Jan 2024 at 17:55, Patrick Palka <ppalka@redhat.com> wrote: > > > > On Thu, 11 Jan 2024, Jonathan Wakely wrote: > > > > > I'd like to commit this to trunk for GCC 14. Please take a look. > > > > > > -- >8 -- > > > > > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. > > > --- > > > libstdc++-v3/include/std/tuple | 1021 ++++++++++++----- > > > libstdc++-v3/include/std/type_traits | 11 + > > > .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ > > > 3 files changed, 841 insertions(+), 296 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 50e11843757..cd05b638923 100644 > > > --- a/libstdc++-v3/include/std/tuple > > > +++ b/libstdc++-v3/include/std/tuple > > > @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > template<typename... _Elements> > > > class tuple : public _Tuple_impl<0, _Elements...> > > > { > > > - typedef _Tuple_impl<0, _Elements...> _Inherited; > > > + using _Inherited = _Tuple_impl<0, _Elements...>; > > > > > > template<bool _Cond> > > > using _TCC = _TupleConstraints<_Cond, _Elements...>; > > > > I guess this should be moved into the #else branch if it's not used in > > the new impl. > > Ah yes, I left them there until I was sure I wouldn't need them ... > then didn't move them when I didn't need them. > > > > > > > > > +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 > > > + template<typename... _UTypes> > > > + static consteval bool > > > + __constructible() > > > + { > > > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > + return (is_constructible_v<_Elements, _UTypes> && ...); > > > > IIUC this (and all the other new constraints) won't short-circuit like > > the old versions do :/ Not sure how much that matters? > > Yeah, I thought about that, but we have efficient built-ins for these > traits now, so I think it's probably OK? Performance wise agreed, though I suppose removing the short circuiting could break existing (though not necessarily valid) code that relied on it to prevent an ill-formed template instantiation. It seems the standard https://eel.is/c++draft/tuple uses conjunction_v in some constraints, and fold-expressions in others, implying short circuiting in some cases but not others? > > If not we could go back to sharing the _TupleConstraints implementations. IMHO I'd be more comfortable with that.
On Fri, 12 Jan 2024 at 18:33, Patrick Palka <ppalka@redhat.com> wrote: > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > On Fri, 12 Jan 2024 at 17:55, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > On Thu, 11 Jan 2024, Jonathan Wakely wrote: > > > > > > > I'd like to commit this to trunk for GCC 14. Please take a look. > > > > > > > > -- >8 -- > > > > > > > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. > > > > --- > > > > libstdc++-v3/include/std/tuple | 1021 ++++++++++++----- > > > > libstdc++-v3/include/std/type_traits | 11 + > > > > .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ > > > > 3 files changed, 841 insertions(+), 296 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 50e11843757..cd05b638923 100644 > > > > --- a/libstdc++-v3/include/std/tuple > > > > +++ b/libstdc++-v3/include/std/tuple > > > > @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > template<typename... _Elements> > > > > class tuple : public _Tuple_impl<0, _Elements...> > > > > { > > > > - typedef _Tuple_impl<0, _Elements...> _Inherited; > > > > + using _Inherited = _Tuple_impl<0, _Elements...>; > > > > > > > > template<bool _Cond> > > > > using _TCC = _TupleConstraints<_Cond, _Elements...>; > > > > > > I guess this should be moved into the #else branch if it's not used in > > > the new impl. > > > > Ah yes, I left them there until I was sure I wouldn't need them ... > > then didn't move them when I didn't need them. > > > > > > > > > > > > > +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 > > > > + template<typename... _UTypes> > > > > + static consteval bool > > > > + __constructible() > > > > + { > > > > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > + return (is_constructible_v<_Elements, _UTypes> && ...); > > > > > > IIUC this (and all the other new constraints) won't short-circuit like > > > the old versions do :/ Not sure how much that matters? > > > > Yeah, I thought about that, but we have efficient built-ins for these > > traits now, so I think it's probably OK? > > Performance wise agreed, though I suppose removing the short circuiting > could break existing (though not necessarily valid) code that relied > on it to prevent an ill-formed template instantiation. It seems > the standard https://eel.is/c++draft/tuple uses conjunction_v in some > constraints, and fold-expressions in others, implying short circuiting > in some cases but not others? I don't recall any particular logic to those choices, they might be arbitrary. > > > > > If not we could go back to sharing the _TupleConstraints implementations. > > IMHO I'd be more comfortable with that. >
On Fri, 12 Jan 2024 at 18:33, Patrick Palka <ppalka@redhat.com> wrote: > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > On Fri, 12 Jan 2024 at 17:55, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > On Thu, 11 Jan 2024, Jonathan Wakely wrote: > > > > > > > I'd like to commit this to trunk for GCC 14. Please take a look. > > > > > > > > -- >8 -- > > > > > > > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. > > > > --- > > > > libstdc++-v3/include/std/tuple | 1021 ++++++++++++----- > > > > libstdc++-v3/include/std/type_traits | 11 + > > > > .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ > > > > 3 files changed, 841 insertions(+), 296 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 50e11843757..cd05b638923 100644 > > > > --- a/libstdc++-v3/include/std/tuple > > > > +++ b/libstdc++-v3/include/std/tuple > > > > @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > template<typename... _Elements> > > > > class tuple : public _Tuple_impl<0, _Elements...> > > > > { > > > > - typedef _Tuple_impl<0, _Elements...> _Inherited; > > > > + using _Inherited = _Tuple_impl<0, _Elements...>; > > > > > > > > template<bool _Cond> > > > > using _TCC = _TupleConstraints<_Cond, _Elements...>; > > > > > > I guess this should be moved into the #else branch if it's not used in > > > the new impl. > > > > Ah yes, I left them there until I was sure I wouldn't need them ... > > then didn't move them when I didn't need them. > > > > > > > > > > > > > +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 > > > > + template<typename... _UTypes> > > > > + static consteval bool > > > > + __constructible() > > > > + { > > > > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > + return (is_constructible_v<_Elements, _UTypes> && ...); > > > > > > IIUC this (and all the other new constraints) won't short-circuit like > > > the old versions do :/ Not sure how much that matters? > > > > Yeah, I thought about that, but we have efficient built-ins for these > > traits now, so I think it's probably OK? > > Performance wise agreed, though I suppose removing the short circuiting > could break existing (though not necessarily valid) code that relied > on it to prevent an ill-formed template instantiation. It seems > the standard https://eel.is/c++draft/tuple uses conjunction_v in some > constraints, and fold-expressions in others, implying short circuiting > in some cases but not others? > > > > > If not we could go back to sharing the _TupleConstraints implementations. > > IMHO I'd be more comfortable with that. Here's an incremental diff to make that change: --- a/libstdc++-v3/include/std/tuple +++ b/libstdc++-v3/include/std/tuple @@ -763,7 +763,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __constructible() { if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) - return (is_constructible_v<_Elements, _UTypes> && ...); + return __and_v<is_constructible<_Elements, _UTypes>...>; else return false; } @@ -773,7 +773,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __nothrow_constructible() { if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) - return (is_nothrow_constructible_v<_Elements, _UTypes> && ...); + return __and_v<is_nothrow_constructible<_Elements, _UTypes>...>; else return false; } @@ -783,7 +783,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __convertible() { if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) - return (is_convertible_v<_UTypes, _Elements> && ...); + return __and_v<is_convertible<_UTypes, _Elements>...>; else return false; } @@ -1526,7 +1526,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __assignable() { if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) - return (is_assignable_v<_Elements&, _UTypes> && ...); + return __and_v<is_assignable<_Elements&, _UTypes>...>; else return false; } @@ -1536,7 +1536,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __nothrow_assignable() { if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) - return (is_nothrow_assignable_v<_Elements&, _UTypes> && ...); + return __and_v<is_nothrow_assignable<_Elements&, _UTypes>...>; else return false; } @@ -1547,7 +1547,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __const_assignable() { if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) - return (is_assignable_v<const _Elements&, _UTypes> && ...); + return __and_v<is_assignable<const _Elements&, _UTypes>...>; else return false; } Happier with that? It passes all the tuple tests, I'm running the full suite now.
On Fri, 12 Jan 2024, Jonathan Wakely wrote: > On Fri, 12 Jan 2024 at 18:33, Patrick Palka <ppalka@redhat.com> wrote: > > > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > > > On Fri, 12 Jan 2024 at 17:55, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > On Thu, 11 Jan 2024, Jonathan Wakely wrote: > > > > > > > > > I'd like to commit this to trunk for GCC 14. Please take a look. > > > > > > > > > > -- >8 -- > > > > > > > > > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. > > > > > --- > > > > > libstdc++-v3/include/std/tuple | 1021 ++++++++++++----- > > > > > libstdc++-v3/include/std/type_traits | 11 + > > > > > .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ > > > > > 3 files changed, 841 insertions(+), 296 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 50e11843757..cd05b638923 100644 > > > > > --- a/libstdc++-v3/include/std/tuple > > > > > +++ b/libstdc++-v3/include/std/tuple > > > > > @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > template<typename... _Elements> > > > > > class tuple : public _Tuple_impl<0, _Elements...> > > > > > { > > > > > - typedef _Tuple_impl<0, _Elements...> _Inherited; > > > > > + using _Inherited = _Tuple_impl<0, _Elements...>; > > > > > > > > > > template<bool _Cond> > > > > > using _TCC = _TupleConstraints<_Cond, _Elements...>; > > > > > > > > I guess this should be moved into the #else branch if it's not used in > > > > the new impl. > > > > > > Ah yes, I left them there until I was sure I wouldn't need them ... > > > then didn't move them when I didn't need them. > > > > > > > > > > > > > > > > > +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 > > > > > + template<typename... _UTypes> > > > > > + static consteval bool > > > > > + __constructible() > > > > > + { > > > > > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > + return (is_constructible_v<_Elements, _UTypes> && ...); > > > > > > > > IIUC this (and all the other new constraints) won't short-circuit like > > > > the old versions do :/ Not sure how much that matters? > > > > > > Yeah, I thought about that, but we have efficient built-ins for these > > > traits now, so I think it's probably OK? > > > > Performance wise agreed, though I suppose removing the short circuiting > > could break existing (though not necessarily valid) code that relied > > on it to prevent an ill-formed template instantiation. It seems > > the standard https://eel.is/c++draft/tuple uses conjunction_v in some > > constraints, and fold-expressions in others, implying short circuiting > > in some cases but not others? > > > > > > > > If not we could go back to sharing the _TupleConstraints implementations. > > > > IMHO I'd be more comfortable with that. > > Here's an incremental diff to make that change: > > --- a/libstdc++-v3/include/std/tuple > +++ b/libstdc++-v3/include/std/tuple > @@ -763,7 +763,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > __constructible() > { > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > - return (is_constructible_v<_Elements, _UTypes> && ...); > + return __and_v<is_constructible<_Elements, _UTypes>...>; > else > return false; > } > @@ -773,7 +773,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > __nothrow_constructible() > { > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > - return (is_nothrow_constructible_v<_Elements, _UTypes> && ...); > + return __and_v<is_nothrow_constructible<_Elements, _UTypes>...>; > else > return false; > } > @@ -783,7 +783,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > __convertible() > { > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > - return (is_convertible_v<_UTypes, _Elements> && ...); > + return __and_v<is_convertible<_UTypes, _Elements>...>; > else > return false; > } > @@ -1526,7 +1526,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > __assignable() > { > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > - return (is_assignable_v<_Elements&, _UTypes> && ...); > + return __and_v<is_assignable<_Elements&, _UTypes>...>; > else > return false; > } > @@ -1536,7 +1536,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > __nothrow_assignable() > { > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > - return (is_nothrow_assignable_v<_Elements&, _UTypes> && ...); > + return __and_v<is_nothrow_assignable<_Elements&, _UTypes>...>; > else > return false; > } > @@ -1547,7 +1547,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > __const_assignable() > { > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > - return (is_assignable_v<const _Elements&, _UTypes> && ...); > + return __and_v<is_assignable<const _Elements&, _UTypes>...>; > else > return false; > } > > Happier with that? > > It passes all the tuple tests, I'm running the full suite now. > > LGTM
On Sat, 13 Jan 2024 at 00:06, Patrick Palka <ppalka@redhat.com> wrote: > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > On Fri, 12 Jan 2024 at 18:33, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > > > > > On Fri, 12 Jan 2024 at 17:55, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > > > On Thu, 11 Jan 2024, Jonathan Wakely wrote: > > > > > > > > > > > I'd like to commit this to trunk for GCC 14. Please take a look. > > > > > > > > > > > > -- >8 -- > > > > > > > > > > > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. > > > > > > --- > > > > > > libstdc++-v3/include/std/tuple | 1021 ++++++++++++----- > > > > > > libstdc++-v3/include/std/type_traits | 11 + > > > > > > .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ > > > > > > 3 files changed, 841 insertions(+), 296 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 50e11843757..cd05b638923 100644 > > > > > > --- a/libstdc++-v3/include/std/tuple > > > > > > +++ b/libstdc++-v3/include/std/tuple > > > > > > @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > > template<typename... _Elements> > > > > > > class tuple : public _Tuple_impl<0, _Elements...> > > > > > > { > > > > > > - typedef _Tuple_impl<0, _Elements...> _Inherited; > > > > > > + using _Inherited = _Tuple_impl<0, _Elements...>; > > > > > > > > > > > > template<bool _Cond> > > > > > > using _TCC = _TupleConstraints<_Cond, _Elements...>; > > > > > > > > > > I guess this should be moved into the #else branch if it's not used in > > > > > the new impl. > > > > > > > > Ah yes, I left them there until I was sure I wouldn't need them ... > > > > then didn't move them when I didn't need them. > > > > > > > > > > > > > > > > > > > > > +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 > > > > > > + template<typename... _UTypes> > > > > > > + static consteval bool > > > > > > + __constructible() > > > > > > + { > > > > > > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > > + return (is_constructible_v<_Elements, _UTypes> && ...); > > > > > > > > > > IIUC this (and all the other new constraints) won't short-circuit like > > > > > the old versions do :/ Not sure how much that matters? > > > > > > > > Yeah, I thought about that, but we have efficient built-ins for these > > > > traits now, so I think it's probably OK? > > > > > > Performance wise agreed, though I suppose removing the short circuiting > > > could break existing (though not necessarily valid) code that relied > > > on it to prevent an ill-formed template instantiation. It seems > > > the standard https://eel.is/c++draft/tuple uses conjunction_v in some > > > constraints, and fold-expressions in others, implying short circuiting > > > in some cases but not others? > > > > > > > > > > > If not we could go back to sharing the _TupleConstraints implementations. > > > > > > IMHO I'd be more comfortable with that. > > > > Here's an incremental diff to make that change: > > > > --- a/libstdc++-v3/include/std/tuple > > +++ b/libstdc++-v3/include/std/tuple > > @@ -763,7 +763,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > __constructible() > > { > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > - return (is_constructible_v<_Elements, _UTypes> && ...); > > + return __and_v<is_constructible<_Elements, _UTypes>...>; > > else > > return false; > > } > > @@ -773,7 +773,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > __nothrow_constructible() > > { > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > - return (is_nothrow_constructible_v<_Elements, _UTypes> && ...); > > + return __and_v<is_nothrow_constructible<_Elements, _UTypes>...>; > > else > > return false; > > } > > @@ -783,7 +783,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > __convertible() > > { > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > - return (is_convertible_v<_UTypes, _Elements> && ...); > > + return __and_v<is_convertible<_UTypes, _Elements>...>; > > else > > return false; > > } > > @@ -1526,7 +1526,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > __assignable() > > { > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > - return (is_assignable_v<_Elements&, _UTypes> && ...); > > + return __and_v<is_assignable<_Elements&, _UTypes>...>; > > else > > return false; > > } > > @@ -1536,7 +1536,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > __nothrow_assignable() > > { > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > - return (is_nothrow_assignable_v<_Elements&, _UTypes> && ...); > > + return __and_v<is_nothrow_assignable<_Elements&, _UTypes>...>; > > else > > return false; > > } > > @@ -1547,7 +1547,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > __const_assignable() > > { > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > - return (is_assignable_v<const _Elements&, _UTypes> && ...); > > + return __and_v<is_assignable<const _Elements&, _UTypes>...>; > > else > > return false; > > } > > > > Happier with that? > > > > It passes all the tuple tests, I'm running the full suite now. > > > > > > LGTM Pushed to trunk - thanks for the reviews.
On Sat, 13 Jan 2024, Jonathan Wakely wrote: > On Sat, 13 Jan 2024 at 00:06, Patrick Palka <ppalka@redhat.com> wrote: > > > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > > > On Fri, 12 Jan 2024 at 18:33, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > > > > > > > On Fri, 12 Jan 2024 at 17:55, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > > > > > On Thu, 11 Jan 2024, Jonathan Wakely wrote: > > > > > > > > > > > > > I'd like to commit this to trunk for GCC 14. Please take a look. > > > > > > > > > > > > > > -- >8 -- > > > > > > > > > > > > > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. > > > > > > > --- > > > > > > > libstdc++-v3/include/std/tuple | 1021 ++++++++++++----- > > > > > > > libstdc++-v3/include/std/type_traits | 11 + > > > > > > > .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ > > > > > > > 3 files changed, 841 insertions(+), 296 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 50e11843757..cd05b638923 100644 > > > > > > > --- a/libstdc++-v3/include/std/tuple > > > > > > > +++ b/libstdc++-v3/include/std/tuple > > > > > > > @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > > > template<typename... _Elements> > > > > > > > class tuple : public _Tuple_impl<0, _Elements...> > > > > > > > { > > > > > > > - typedef _Tuple_impl<0, _Elements...> _Inherited; > > > > > > > + using _Inherited = _Tuple_impl<0, _Elements...>; > > > > > > > > > > > > > > template<bool _Cond> > > > > > > > using _TCC = _TupleConstraints<_Cond, _Elements...>; > > > > > > > > > > > > I guess this should be moved into the #else branch if it's not used in > > > > > > the new impl. > > > > > > > > > > Ah yes, I left them there until I was sure I wouldn't need them ... > > > > > then didn't move them when I didn't need them. > > > > > > > > > > > > > > > > > > > > > > > > > +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 > > > > > > > + template<typename... _UTypes> > > > > > > > + static consteval bool > > > > > > > + __constructible() > > > > > > > + { > > > > > > > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > > > + return (is_constructible_v<_Elements, _UTypes> && ...); > > > > > > > > > > > > IIUC this (and all the other new constraints) won't short-circuit like > > > > > > the old versions do :/ Not sure how much that matters? > > > > > > > > > > Yeah, I thought about that, but we have efficient built-ins for these > > > > > traits now, so I think it's probably OK? > > > > > > > > Performance wise agreed, though I suppose removing the short circuiting > > > > could break existing (though not necessarily valid) code that relied > > > > on it to prevent an ill-formed template instantiation. It seems > > > > the standard https://eel.is/c++draft/tuple uses conjunction_v in some > > > > constraints, and fold-expressions in others, implying short circuiting > > > > in some cases but not others? > > > > > > > > > > > > > > If not we could go back to sharing the _TupleConstraints implementations. > > > > > > > > IMHO I'd be more comfortable with that. > > > > > > Here's an incremental diff to make that change: > > > > > > --- a/libstdc++-v3/include/std/tuple > > > +++ b/libstdc++-v3/include/std/tuple > > > @@ -763,7 +763,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > __constructible() > > > { > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > - return (is_constructible_v<_Elements, _UTypes> && ...); > > > + return __and_v<is_constructible<_Elements, _UTypes>...>; > > > else > > > return false; > > > } > > > @@ -773,7 +773,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > __nothrow_constructible() > > > { > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > - return (is_nothrow_constructible_v<_Elements, _UTypes> && ...); > > > + return __and_v<is_nothrow_constructible<_Elements, _UTypes>...>; > > > else > > > return false; > > > } > > > @@ -783,7 +783,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > __convertible() > > > { > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > - return (is_convertible_v<_UTypes, _Elements> && ...); > > > + return __and_v<is_convertible<_UTypes, _Elements>...>; > > > else > > > return false; > > > } > > > @@ -1526,7 +1526,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > __assignable() > > > { > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > - return (is_assignable_v<_Elements&, _UTypes> && ...); > > > + return __and_v<is_assignable<_Elements&, _UTypes>...>; > > > else > > > return false; > > > } > > > @@ -1536,7 +1536,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > __nothrow_assignable() > > > { > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > - return (is_nothrow_assignable_v<_Elements&, _UTypes> && ...); > > > + return __and_v<is_nothrow_assignable<_Elements&, _UTypes>...>; > > > else > > > return false; > > > } > > > @@ -1547,7 +1547,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > __const_assignable() > > > { > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > - return (is_assignable_v<const _Elements&, _UTypes> && ...); > > > + return __and_v<is_assignable<const _Elements&, _UTypes>...>; > > > else > > > return false; > > > } > > > > > > Happier with that? > > > > > > It passes all the tuple tests, I'm running the full suite now. > > > > > > > > > > LGTM > > Pushed to trunk - thanks for the reviews. I'm seeing a redefinition error when compiling <tuple> with -std=c++20 -U__cpp_conditional_explicit (which IIUC is intended to work?): /home/ppalka/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/tuple:1536:9: error: ‘template<class ... _Elements> template<class ... _UTypes> static consteval bool std::tuple< <template-parameter-1-1> >::__nothrow_assignable()’ cannot be overloaded with ‘template<class ... _Elements> template<class ... _UElements> static constexpr bool std::tuple< <template-parameter-1-1> >::__nothrow_assignable()’ 1536 | __nothrow_assignable() | ^~~~~~~~~~~~~~~~~~~~ /home/ppalka/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/tuple:1248:31: note: previous declaration ‘template<class ... _Elements> template<class ... _UElements> static constexpr bool std::tuple< <template-parameter-1-1> >::__nothrow_assignable()’ 1248 | static constexpr bool __nothrow_assignable() | ^~~~~~~~~~~~~~~~~~~~
On Mon, 15 Jan 2024 at 16:27, Patrick Palka <ppalka@redhat.com> wrote: > > On Sat, 13 Jan 2024, Jonathan Wakely wrote: > > > On Sat, 13 Jan 2024 at 00:06, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > > > > > On Fri, 12 Jan 2024 at 18:33, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > > > > > > > > > On Fri, 12 Jan 2024 at 17:55, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > > > > > > > On Thu, 11 Jan 2024, Jonathan Wakely wrote: > > > > > > > > > > > > > > > I'd like to commit this to trunk for GCC 14. Please take a look. > > > > > > > > > > > > > > > > -- >8 -- > > > > > > > > > > > > > > > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. > > > > > > > > --- > > > > > > > > libstdc++-v3/include/std/tuple | 1021 ++++++++++++----- > > > > > > > > libstdc++-v3/include/std/type_traits | 11 + > > > > > > > > .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ > > > > > > > > 3 files changed, 841 insertions(+), 296 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 50e11843757..cd05b638923 100644 > > > > > > > > --- a/libstdc++-v3/include/std/tuple > > > > > > > > +++ b/libstdc++-v3/include/std/tuple > > > > > > > > @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > > > > template<typename... _Elements> > > > > > > > > class tuple : public _Tuple_impl<0, _Elements...> > > > > > > > > { > > > > > > > > - typedef _Tuple_impl<0, _Elements...> _Inherited; > > > > > > > > + using _Inherited = _Tuple_impl<0, _Elements...>; > > > > > > > > > > > > > > > > template<bool _Cond> > > > > > > > > using _TCC = _TupleConstraints<_Cond, _Elements...>; > > > > > > > > > > > > > > I guess this should be moved into the #else branch if it's not used in > > > > > > > the new impl. > > > > > > > > > > > > Ah yes, I left them there until I was sure I wouldn't need them ... > > > > > > then didn't move them when I didn't need them. > > > > > > > > > > > > > > > > > > > > > > > > > > > > > +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 > > > > > > > > + template<typename... _UTypes> > > > > > > > > + static consteval bool > > > > > > > > + __constructible() > > > > > > > > + { > > > > > > > > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > > > > + return (is_constructible_v<_Elements, _UTypes> && ...); > > > > > > > > > > > > > > IIUC this (and all the other new constraints) won't short-circuit like > > > > > > > the old versions do :/ Not sure how much that matters? > > > > > > > > > > > > Yeah, I thought about that, but we have efficient built-ins for these > > > > > > traits now, so I think it's probably OK? > > > > > > > > > > Performance wise agreed, though I suppose removing the short circuiting > > > > > could break existing (though not necessarily valid) code that relied > > > > > on it to prevent an ill-formed template instantiation. It seems > > > > > the standard https://eel.is/c++draft/tuple uses conjunction_v in some > > > > > constraints, and fold-expressions in others, implying short circuiting > > > > > in some cases but not others? > > > > > > > > > > > > > > > > > If not we could go back to sharing the _TupleConstraints implementations. > > > > > > > > > > IMHO I'd be more comfortable with that. > > > > > > > > Here's an incremental diff to make that change: > > > > > > > > --- a/libstdc++-v3/include/std/tuple > > > > +++ b/libstdc++-v3/include/std/tuple > > > > @@ -763,7 +763,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > __constructible() > > > > { > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > - return (is_constructible_v<_Elements, _UTypes> && ...); > > > > + return __and_v<is_constructible<_Elements, _UTypes>...>; > > > > else > > > > return false; > > > > } > > > > @@ -773,7 +773,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > __nothrow_constructible() > > > > { > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > - return (is_nothrow_constructible_v<_Elements, _UTypes> && ...); > > > > + return __and_v<is_nothrow_constructible<_Elements, _UTypes>...>; > > > > else > > > > return false; > > > > } > > > > @@ -783,7 +783,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > __convertible() > > > > { > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > - return (is_convertible_v<_UTypes, _Elements> && ...); > > > > + return __and_v<is_convertible<_UTypes, _Elements>...>; > > > > else > > > > return false; > > > > } > > > > @@ -1526,7 +1526,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > __assignable() > > > > { > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > - return (is_assignable_v<_Elements&, _UTypes> && ...); > > > > + return __and_v<is_assignable<_Elements&, _UTypes>...>; > > > > else > > > > return false; > > > > } > > > > @@ -1536,7 +1536,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > __nothrow_assignable() > > > > { > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > - return (is_nothrow_assignable_v<_Elements&, _UTypes> && ...); > > > > + return __and_v<is_nothrow_assignable<_Elements&, _UTypes>...>; > > > > else > > > > return false; > > > > } > > > > @@ -1547,7 +1547,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > __const_assignable() > > > > { > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > - return (is_assignable_v<const _Elements&, _UTypes> && ...); > > > > + return __and_v<is_assignable<const _Elements&, _UTypes>...>; > > > > else > > > > return false; > > > > } > > > > > > > > Happier with that? > > > > > > > > It passes all the tuple tests, I'm running the full suite now. > > > > > > > > > > > > > > LGTM > > > > Pushed to trunk - thanks for the reviews. > > I'm seeing a redefinition error when compiling <tuple> with > -std=c++20 -U__cpp_conditional_explicit (which IIUC is intended > to work?): Yes ... well, a compiler that doesn't define that is supposed to work. Manually undef'ing predefined macros yourself is UB of course :-) > > /home/ppalka/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/tuple:1536:9: error: ‘template<class ... _Elements> template<class ... _UTypes> static consteval bool std::tuple< <template-parameter-1-1> >::__nothrow_assignable()’ cannot be overloaded with ‘template<class ... _Elements> template<class ... _UElements> static constexpr bool std::tuple< <template-parameter-1-1> >::__nothrow_assignable()’ > 1536 | __nothrow_assignable() > | ^~~~~~~~~~~~~~~~~~~~ > /home/ppalka/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/tuple:1248:31: note: previous declaration ‘template<class ... _Elements> template<class ... _UElements> static constexpr bool std::tuple< <template-parameter-1-1> >::__nothrow_assignable()’ > 1248 | static constexpr bool __nothrow_assignable() > | ^~~~~~~~~~~~~~~~~~~~ It needs this patch: --- a/libstdc++-v3/include/std/tuple +++ b/libstdc++-v3/include/std/tuple @@ -1237,20 +1237,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _TCC<_Cond>::template __is_explicitly_constructible<_Args...>(), bool>; - template<typename... _UElements> - static constexpr - __enable_if_t<sizeof...(_UElements) == sizeof...(_Elements), bool> - __assignable() - { return __and_<is_assignable<_Elements&, _UElements>...>::value; } - - // Condition for noexcept-specifier of an assignment operator. - template<typename... _UElements> - static constexpr bool __nothrow_assignable() - { - return - __and_<is_nothrow_assignable<_Elements&, _UElements>...>::value; - } - // Condition for noexcept-specifier of a constructor. template<typename... _UElements> static constexpr bool __nothrow_constructible() @@ -1687,6 +1673,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #else // ! concepts + private: + template<typename... _UElements> + static constexpr + __enable_if_t<sizeof...(_UElements) == sizeof...(_Elements), bool> + __assignable() + { return __and_<is_assignable<_Elements&, _UElements>...>::value; } + + // Condition for noexcept-specifier of an assignment operator. + template<typename... _UElements> + static constexpr bool __nothrow_assignable() + { + return + __and_<is_nothrow_assignable<_Elements&, _UElements>...>::value; + } + + public: + _GLIBCXX20_CONSTEXPR tuple& operator=(__conditional_t<__assignable<const _Elements&...>(),
On Mon, 15 Jan 2024 at 16:51, Jonathan Wakely <jwakely@redhat.com> wrote: > > On Mon, 15 Jan 2024 at 16:27, Patrick Palka <ppalka@redhat.com> wrote: > > > > On Sat, 13 Jan 2024, Jonathan Wakely wrote: > > > > > On Sat, 13 Jan 2024 at 00:06, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > > > > > > > On Fri, 12 Jan 2024 at 18:33, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > > > > > On Fri, 12 Jan 2024, Jonathan Wakely wrote: > > > > > > > > > > > > > On Fri, 12 Jan 2024 at 17:55, Patrick Palka <ppalka@redhat.com> wrote: > > > > > > > > > > > > > > > > On Thu, 11 Jan 2024, Jonathan Wakely wrote: > > > > > > > > > > > > > > > > > I'd like to commit this to trunk for GCC 14. Please take a look. > > > > > > > > > > > > > > > > > > -- >8 -- > > > > > > > > > > > > > > > > > > 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<T1, T2> 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<T1, T2> 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<T1,T2> 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. > > > > > > > > > --- > > > > > > > > > libstdc++-v3/include/std/tuple | 1021 ++++++++++++----- > > > > > > > > > libstdc++-v3/include/std/type_traits | 11 + > > > > > > > > > .../testsuite/20_util/tuple/dangling_ref.cc | 105 ++ > > > > > > > > > 3 files changed, 841 insertions(+), 296 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 50e11843757..cd05b638923 100644 > > > > > > > > > --- a/libstdc++-v3/include/std/tuple > > > > > > > > > +++ b/libstdc++-v3/include/std/tuple > > > > > > > > > @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > > > > > template<typename... _Elements> > > > > > > > > > class tuple : public _Tuple_impl<0, _Elements...> > > > > > > > > > { > > > > > > > > > - typedef _Tuple_impl<0, _Elements...> _Inherited; > > > > > > > > > + using _Inherited = _Tuple_impl<0, _Elements...>; > > > > > > > > > > > > > > > > > > template<bool _Cond> > > > > > > > > > using _TCC = _TupleConstraints<_Cond, _Elements...>; > > > > > > > > > > > > > > > > I guess this should be moved into the #else branch if it's not used in > > > > > > > > the new impl. > > > > > > > > > > > > > > Ah yes, I left them there until I was sure I wouldn't need them ... > > > > > > > then didn't move them when I didn't need them. > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 > > > > > > > > > + template<typename... _UTypes> > > > > > > > > > + static consteval bool > > > > > > > > > + __constructible() > > > > > > > > > + { > > > > > > > > > + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > > > > > + return (is_constructible_v<_Elements, _UTypes> && ...); > > > > > > > > > > > > > > > > IIUC this (and all the other new constraints) won't short-circuit like > > > > > > > > the old versions do :/ Not sure how much that matters? > > > > > > > > > > > > > > Yeah, I thought about that, but we have efficient built-ins for these > > > > > > > traits now, so I think it's probably OK? > > > > > > > > > > > > Performance wise agreed, though I suppose removing the short circuiting > > > > > > could break existing (though not necessarily valid) code that relied > > > > > > on it to prevent an ill-formed template instantiation. It seems > > > > > > the standard https://eel.is/c++draft/tuple uses conjunction_v in some > > > > > > constraints, and fold-expressions in others, implying short circuiting > > > > > > in some cases but not others? > > > > > > > > > > > > > > > > > > > > If not we could go back to sharing the _TupleConstraints implementations. > > > > > > > > > > > > IMHO I'd be more comfortable with that. > > > > > > > > > > Here's an incremental diff to make that change: > > > > > > > > > > --- a/libstdc++-v3/include/std/tuple > > > > > +++ b/libstdc++-v3/include/std/tuple > > > > > @@ -763,7 +763,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > __constructible() > > > > > { > > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > - return (is_constructible_v<_Elements, _UTypes> && ...); > > > > > + return __and_v<is_constructible<_Elements, _UTypes>...>; > > > > > else > > > > > return false; > > > > > } > > > > > @@ -773,7 +773,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > __nothrow_constructible() > > > > > { > > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > - return (is_nothrow_constructible_v<_Elements, _UTypes> && ...); > > > > > + return __and_v<is_nothrow_constructible<_Elements, _UTypes>...>; > > > > > else > > > > > return false; > > > > > } > > > > > @@ -783,7 +783,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > __convertible() > > > > > { > > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > - return (is_convertible_v<_UTypes, _Elements> && ...); > > > > > + return __and_v<is_convertible<_UTypes, _Elements>...>; > > > > > else > > > > > return false; > > > > > } > > > > > @@ -1526,7 +1526,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > __assignable() > > > > > { > > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > - return (is_assignable_v<_Elements&, _UTypes> && ...); > > > > > + return __and_v<is_assignable<_Elements&, _UTypes>...>; > > > > > else > > > > > return false; > > > > > } > > > > > @@ -1536,7 +1536,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > __nothrow_assignable() > > > > > { > > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > - return (is_nothrow_assignable_v<_Elements&, _UTypes> && ...); > > > > > + return __and_v<is_nothrow_assignable<_Elements&, _UTypes>...>; > > > > > else > > > > > return false; > > > > > } > > > > > @@ -1547,7 +1547,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > > > __const_assignable() > > > > > { > > > > > if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) > > > > > - return (is_assignable_v<const _Elements&, _UTypes> && ...); > > > > > + return __and_v<is_assignable<const _Elements&, _UTypes>...>; > > > > > else > > > > > return false; > > > > > } > > > > > > > > > > Happier with that? > > > > > > > > > > It passes all the tuple tests, I'm running the full suite now. > > > > > > > > > > > > > > > > > > LGTM > > > > > > Pushed to trunk - thanks for the reviews. > > > > I'm seeing a redefinition error when compiling <tuple> with > > -std=c++20 -U__cpp_conditional_explicit (which IIUC is intended > > to work?): > > Yes ... well, a compiler that doesn't define that is supposed to work. > Manually undef'ing predefined macros yourself is UB of course :-) > > > > /home/ppalka/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/tuple:1536:9: error: ‘template<class ... _Elements> template<class ... _UTypes> static consteval bool std::tuple< <template-parameter-1-1> >::__nothrow_assignable()’ cannot be overloaded with ‘template<class ... _Elements> template<class ... _UElements> static constexpr bool std::tuple< <template-parameter-1-1> >::__nothrow_assignable()’ > > 1536 | __nothrow_assignable() > > | ^~~~~~~~~~~~~~~~~~~~ > > /home/ppalka/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/tuple:1248:31: note: previous declaration ‘template<class ... _Elements> template<class ... _UElements> static constexpr bool std::tuple< <template-parameter-1-1> >::__nothrow_assignable()’ > > 1248 | static constexpr bool __nothrow_assignable() > > | ^~~~~~~~~~~~~~~~~~~~ > > > It needs this patch: > > --- a/libstdc++-v3/include/std/tuple > +++ b/libstdc++-v3/include/std/tuple > @@ -1237,20 +1237,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > _TCC<_Cond>::template __is_explicitly_constructible<_Args...>(), > bool>; > > - template<typename... _UElements> > - static constexpr > - __enable_if_t<sizeof...(_UElements) == sizeof...(_Elements), bool> > - __assignable() > - { return __and_<is_assignable<_Elements&, _UElements>...>::value; } > - > - // Condition for noexcept-specifier of an assignment operator. > - template<typename... _UElements> > - static constexpr bool __nothrow_assignable() > - { > - return > - __and_<is_nothrow_assignable<_Elements&, _UElements>...>::value; > - } > - > // Condition for noexcept-specifier of a constructor. > template<typename... _UElements> > static constexpr bool __nothrow_constructible() > @@ -1687,6 +1673,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > #else // ! concepts > > + private: > + template<typename... _UElements> > + static constexpr > + __enable_if_t<sizeof...(_UElements) == sizeof...(_Elements), bool> > + __assignable() > + { return __and_<is_assignable<_Elements&, _UElements>...>::value; } > + > + // Condition for noexcept-specifier of an assignment operator. > + template<typename... _UElements> > + static constexpr bool __nothrow_assignable() > + { > + return > + __and_<is_nothrow_assignable<_Elements&, _UElements>...>::value; > + } > + > + public: > + > _GLIBCXX20_CONSTEXPR > tuple& > operator=(__conditional_t<__assignable<const _Elements&...>(), Pushed to trunk as r14-7256-g1e88a151f878e0 after testing on aarch64-linux. Thanks for noticing the bug.
diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple index 50e11843757..cd05b638923 100644 --- a/libstdc++-v3/include/std/tuple +++ b/libstdc++-v3/include/std/tuple @@ -752,11 +752,467 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template<typename... _Elements> class tuple : public _Tuple_impl<0, _Elements...> { - typedef _Tuple_impl<0, _Elements...> _Inherited; + using _Inherited = _Tuple_impl<0, _Elements...>; template<bool _Cond> using _TCC = _TupleConstraints<_Cond, _Elements...>; +#if __cpp_concepts && __cpp_conditional_explicit // >= C++20 + template<typename... _UTypes> + static consteval bool + __constructible() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return (is_constructible_v<_Elements, _UTypes> && ...); + else + return false; + } + + template<typename... _UTypes> + static consteval bool + __nothrow_constructible() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return (is_nothrow_constructible_v<_Elements, _UTypes> && ...); + else + return false; + } + + template<typename... _UTypes> + static consteval bool + __convertible() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return (is_convertible_v<_UTypes, _Elements> && ...); + else + return false; + } + + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 3121. tuple constructor constraints for UTypes&&... overloads + template<typename... _UTypes> + 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<remove_cvref_t<_U0>, tuple>; + } + else if constexpr (sizeof...(_Elements) < 4) + { + using _U0 = typename _Nth_type<0, _UTypes...>::type; + if constexpr (!is_same_v<remove_cvref_t<_U0>, allocator_arg_t>) + return true; + else + { + using _T0 = typename _Nth_type<0, _Elements...>::type; + return is_same_v<remove_cvref_t<_T0>, allocator_arg_t>; + } + } + return true; + } + + // Return true iff sizeof...(Types) == 1 && tuple_size_v<TUPLE> == 1 + // and the single element in Types can be initialized from TUPLE, + // or is the same type as tuple_element_t<0, TUPLE>. + template<typename _Tuple> + static consteval bool + __use_other_ctor() + { + if constexpr (sizeof...(_Elements) != 1) + return false; + else if constexpr (is_same_v<remove_cvref_t<_Tuple>, 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<typename... _Up> + 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<const _Elements&...>()) + tuple(const _Elements&... __elements) + noexcept(__nothrow_constructible<const _Elements&...>()) + requires (__constructible<const _Elements&...>()) + : _Inherited(__elements...) + { } + + template<typename... _UTypes> + 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<typename... _UTypes> + requires (__disambiguating_constraint<_UTypes...>()) + && (__constructible<_UTypes...>()) + && (__dangles<_UTypes...>()) + tuple(_UTypes&&...) = delete; + + constexpr tuple(const tuple&) = default; + + constexpr tuple(tuple&&) = default; + + template<typename... _UTypes> + requires (__constructible<const _UTypes&...>()) + && (!__use_other_ctor<const tuple<_UTypes...>&>()) + && (!__dangles<const _UTypes&...>()) + constexpr explicit(!__convertible<const _UTypes&...>()) + tuple(const tuple<_UTypes...>& __u) + noexcept(__nothrow_constructible<const _UTypes&...>()) + : _Inherited(static_cast<const _Tuple_impl<0, _UTypes...>&>(__u)) + { } + + template<typename... _UTypes> + requires (__constructible<const _UTypes&...>()) + && (!__use_other_ctor<const tuple<_UTypes...>&>()) + && (__dangles<const _UTypes&...>()) + tuple(const tuple<_UTypes...>&) = delete; + + template<typename... _UTypes> + requires (__constructible<_UTypes...>()) + && (!__use_other_ctor<tuple<_UTypes...>>()) + && (!__dangles<_UTypes...>()) + constexpr explicit(!__convertible<_UTypes...>()) + tuple(tuple<_UTypes...>&& __u) + noexcept(__nothrow_constructible<_UTypes...>()) + : _Inherited(static_cast<_Tuple_impl<0, _UTypes...>&&>(__u)) + { } + + template<typename... _UTypes> + requires (__constructible<_UTypes...>()) + && (!__use_other_ctor<tuple<_UTypes...>>()) + && (__dangles<_UTypes...>()) + tuple(tuple<_UTypes...>&&) = delete; + +#if __cpp_lib_ranges_zip // >= C++23 + template<typename... _UTypes> + requires (__constructible<_UTypes&...>()) + && (!__use_other_ctor<tuple<_UTypes...>&>()) + && (!__dangles<_UTypes&...>()) + constexpr explicit(!__convertible<_UTypes&...>()) + tuple(tuple<_UTypes...>& __u) + noexcept(__nothrow_constructible<_UTypes&...>()) + : _Inherited(static_cast<_Tuple_impl<0, _UTypes...>&>(__u)) + { } + + template<typename... _UTypes> + requires (__constructible<_UTypes&...>()) + && (!__use_other_ctor<tuple<_UTypes...>&>()) + && (__dangles<_UTypes&...>()) + tuple(tuple<_UTypes...>&) = delete; + + template<typename... _UTypes> + requires (__constructible<const _UTypes...>()) + && (!__use_other_ctor<const tuple<_UTypes...>>()) + && (!__dangles<const _UTypes...>()) + constexpr explicit(!__convertible<const _UTypes...>()) + tuple(const tuple<_UTypes...>&& __u) + noexcept(__nothrow_constructible<const _UTypes...>()) + : _Inherited(static_cast<const _Tuple_impl<0, _UTypes...>&&>(__u)) + { } + + template<typename... _UTypes> + requires (__constructible<const _UTypes...>()) + && (!__use_other_ctor<const tuple<_UTypes...>>()) + && (__dangles<const _UTypes...>()) + tuple(const tuple<_UTypes...>&&) = delete; +#endif // C++23 + + template<typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<const _U1&, const _U2&>()) + && (!__dangles<const _U1&, const _U2&>()) + constexpr explicit(!__convertible<const _U1&, const _U2&>()) + tuple(const pair<_U1, _U2>& __u) + noexcept(__nothrow_constructible<const _U1&, const _U2&>()) + : _Inherited(__u.first, __u.second) + { } + + template<typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<const _U1&, const _U2&>()) + && (__dangles<const _U1&, const _U2&>()) + tuple(const pair<_U1, _U2>&) = delete; + + template<typename _U1, typename _U2> + 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<typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1, _U2>()) + && (__dangles<_U1, _U2>()) + tuple(pair<_U1, _U2>&&) = delete; + +#if __cpp_lib_ranges_zip // >= C++23 + template<typename _U1, typename _U2> + 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<typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1&, _U2&>()) + && (__dangles<_U1&, _U2&>()) + tuple(pair<_U1, _U2>&) = delete; + + template<typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<const _U1, const _U2>()) + && (!__dangles<const _U1, const _U2>()) + constexpr explicit(!__convertible<const _U1, const _U2>()) + tuple(const pair<_U1, _U2>&& __u) + noexcept(__nothrow_constructible<const _U1, const _U2>()) + : _Inherited(std::forward<const _U1>(__u.first), + std::forward<const _U2>(__u.second)) + { } + + template<typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<const _U1, const _U2>()) + && (__dangles<const _U1, const _U2>()) + 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<typename _Alloc> + 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<typename _Alloc> + constexpr explicit(!__convertible<const _Elements&...>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, + const _Elements&... __elements) + requires (__constructible<const _Elements&...>()) + : _Inherited(__tag, __a, __elements...) + { } + + template<typename _Alloc, typename... _UTypes> + 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<typename _Alloc, typename... _UTypes> + requires (__disambiguating_constraint<_UTypes...>()) + && (__constructible<_UTypes...>()) + && (__dangles<_UTypes...>()) + tuple(allocator_arg_t, const _Alloc&, _UTypes&&...) = delete; + + template<typename _Alloc> + constexpr + tuple(allocator_arg_t __tag, const _Alloc& __a, const tuple& __u) + : _Inherited(__tag, __a, static_cast<const _Inherited&>(__u)) + { } + + template<typename _Alloc> + requires (__constructible<_Elements...>()) + constexpr + tuple(allocator_arg_t __tag, const _Alloc& __a, tuple&& __u) + : _Inherited(__tag, __a, static_cast<_Inherited&&>(__u)) + { } + + template<typename _Alloc, typename... _UTypes> + requires (__constructible<const _UTypes&...>()) + && (!__use_other_ctor<const tuple<_UTypes...>&>()) + && (!__dangles<const _UTypes&...>()) + constexpr explicit(!__convertible<const _UTypes&...>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, + const tuple<_UTypes...>& __u) + : _Inherited(__tag, __a, + static_cast<const _Tuple_impl<0, _UTypes...>&>(__u)) + { } + + template<typename _Alloc, typename... _UTypes> + requires (__constructible<const _UTypes&...>()) + && (!__use_other_ctor<const tuple<_UTypes...>&>()) + && (__dangles<const _UTypes&...>()) + tuple(allocator_arg_t, const _Alloc&, const tuple<_UTypes...>&) = delete; + + template<typename _Alloc, typename... _UTypes> + requires (__constructible<_UTypes...>()) + && (!__use_other_ctor<tuple<_UTypes...>>()) + && (!__dangles<_UTypes...>()) + constexpr explicit(!__use_other_ctor<tuple<_UTypes...>>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, tuple<_UTypes...>&& __u) + : _Inherited(__tag, __a, static_cast<_Tuple_impl<0, _UTypes...>&&>(__u)) + { } + + template<typename _Alloc, typename... _UTypes> + requires (__constructible<_UTypes...>()) + && (!__use_other_ctor<tuple<_UTypes...>>()) + && (__dangles<_UTypes...>()) + tuple(allocator_arg_t, const _Alloc&, tuple<_UTypes...>&&) = delete; + +#if __cpp_lib_ranges_zip // >= C++23 + template<typename _Alloc, typename... _UTypes> + requires (__constructible<_UTypes&...>()) + && (!__use_other_ctor<tuple<_UTypes...>&>()) + && (!__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<typename _Alloc, typename... _UTypes> + requires (__constructible<_UTypes&...>()) + && (!__use_other_ctor<tuple<_UTypes...>&>()) + && (__dangles<_UTypes&...>()) + tuple(allocator_arg_t, const _Alloc&, tuple<_UTypes...>&) = delete; + + template<typename _Alloc, typename... _UTypes> + requires (__constructible<const _UTypes...>()) + && (!__use_other_ctor<const tuple<_UTypes...>>()) + && (!__dangles<const _UTypes...>()) + constexpr explicit(!__convertible<const _UTypes...>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, + const tuple<_UTypes...>&& __u) + : _Inherited(__tag, __a, + static_cast<const _Tuple_impl<0, _UTypes...>&&>(__u)) + { } + + template<typename _Alloc, typename... _UTypes> + requires (__constructible<const _UTypes...>()) + && (!__use_other_ctor<const tuple<_UTypes...>>()) + && (__dangles<const _UTypes...>()) + tuple(allocator_arg_t, const _Alloc&, const tuple<_UTypes...>&&) = delete; +#endif // C++23 + + template<typename _Alloc, typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<const _U1&, const _U2&>()) + && (!__dangles<const _U1&, const _U2&>()) + constexpr explicit(!__convertible<const _U1&, const _U2&>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, + const pair<_U1, _U2>& __u) + noexcept(__nothrow_constructible<const _U1&, const _U2&>()) + : _Inherited(__tag, __a, __u.first, __u.second) + { } + + template<typename _Alloc, typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<const _U1&, const _U2&>()) + && (__dangles<const _U1&, const _U2&>()) + tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&) = delete; + + template<typename _Alloc, typename _U1, typename _U2> + 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<typename _Alloc, typename _U1, typename _U2> + 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<typename _Alloc, typename _U1, typename _U2> + 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<typename _Alloc, typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<_U1&, _U2&>()) + && (__dangles<_U1&, _U2&>()) + tuple(allocator_arg_t, const _Alloc&, pair<_U1, _U2>&) = delete; + + template<typename _Alloc, typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<const _U1, const _U2>()) + && (!__dangles<const _U1, const _U2>()) + constexpr explicit(!__convertible<const _U1, const _U2>()) + tuple(allocator_arg_t __tag, const _Alloc& __a, + const pair<_U1, _U2>&& __u) + noexcept(__nothrow_constructible<const _U1, const _U2>()) + : _Inherited(__tag, __a, std::move(__u.first), std::move(__u.second)) + { } + + template<typename _Alloc, typename _U1, typename _U2> + requires (sizeof...(_Elements) == 2) + && (__constructible<const _U1, const _U2>()) + && (__dangles<const _U1, const _U2>()) + tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete; +#endif // C++23 + +#if 0 && __cpp_lib_tuple_like // >= C++23 + template<typename _Alloc, __tuple_like _UTuple> + constexpr explicit(...) + tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u); +#endif // C++23 + +#else // !(concepts && conditional_explicit) + // Constraint for non-explicit default constructor template<bool _Dummy> using _ImplicitDefaultCtor = __enable_if_t< @@ -850,15 +1306,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION static constexpr bool __use_other_ctor() { return _UseOtherCtor<_Tuple>::value; } -#if __cplusplus > 202002L - template<typename... _Args> - static constexpr bool __constructible - = _TCC<true>::template __constructible<_Args...>::value; - - template<typename... _Args> - static constexpr bool __convertible - = _TCC<true>::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<typename _Dummy = void, @@ -895,7 +1362,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION constexpr tuple(_UElements&&... __elements) noexcept(__nothrow_constructible<_UElements...>()) - : _Inherited(std::forward<_UElements>(__elements)...) { } + : _Inherited(std::forward<_UElements>(__elements)...) + { __glibcxx_no_dangling_refs(_UElements&&); } template<typename... _UElements, bool _Valid = __valid_args<_UElements...>(), @@ -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<const _UElements&...>()) : _Inherited(static_cast<const _Tuple_impl<0, _UElements...>&>(__in)) - { } + { __glibcxx_no_dangling_refs(const _UElements&); } template<typename... _UElements, bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) @@ -927,7 +1396,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(const tuple<_UElements...>& __in) noexcept(__nothrow_constructible<const _UElements&...>()) : _Inherited(static_cast<const _Tuple_impl<0, _UElements...>&>(__in)) - { } + { __glibcxx_no_dangling_refs(const _UElements&); } template<typename... _UElements, bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) @@ -936,7 +1405,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION constexpr tuple(tuple<_UElements...>&& __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<typename... _UElements, bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) @@ -945,30 +1415,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION explicit constexpr tuple(tuple<_UElements...>&& __in) noexcept(__nothrow_constructible<_UElements...>()) - : _Inherited(static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) { } - -#if __cplusplus > 202002L - template<typename... _UElements> - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (!__use_other_ctor<tuple<_UElements...>&>()) - && __constructible<_UElements&...> - explicit(!__convertible<_UElements&...>) - constexpr - tuple(tuple<_UElements...>& __in) - noexcept(__nothrow_constructible<_UElements&...>()) - : _Inherited(static_cast<_Tuple_impl<0, _UElements...>&>(__in)) - { } - - template<typename... _UElements> - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (!__use_other_ctor<const tuple<_UElements...>&&>()) - && __constructible<const _UElements...> - explicit(!__convertible<const _UElements...>) - constexpr - tuple(const tuple<_UElements...>&& __in) - noexcept(__nothrow_constructible<const _UElements...>()) - : _Inherited(static_cast<const _Tuple_impl<0, _UElements...>&&>(__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<typename _Alloc, typename... _UElements, bool _Valid = __valid_args<_UElements...>(), @@ -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<typename _Alloc> _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<const _Tuple_impl<0, _UElements...>&>(__in)) - { } + static_cast<const _Tuple_impl<0, _UElements...>&>(__in)) + { __glibcxx_no_dangling_refs(const _UElements&); } template<typename _Alloc, typename... _UElements, bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) @@ -1042,8 +1490,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(allocator_arg_t __tag, const _Alloc& __a, const tuple<_UElements...>& __in) : _Inherited(__tag, __a, - static_cast<const _Tuple_impl<0, _UElements...>&>(__in)) - { } + static_cast<const _Tuple_impl<0, _UElements...>&>(__in)) + { __glibcxx_no_dangling_refs(const _UElements&); } template<typename _Alloc, typename... _UElements, bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) @@ -1053,8 +1501,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(allocator_arg_t __tag, const _Alloc& __a, tuple<_UElements...>&& __in) : _Inherited(__tag, __a, - static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) - { } + static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) + { __glibcxx_no_dangling_refs(_UElements&&); } template<typename _Alloc, typename... _UElements, bool _Valid = (sizeof...(_Elements) == sizeof...(_UElements)) @@ -1065,37 +1513,180 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION tuple(allocator_arg_t __tag, const _Alloc& __a, tuple<_UElements...>&& __in) : _Inherited(__tag, __a, - static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) - { } - -#if __cplusplus > 202002L - template<typename _Alloc, typename... _UElements> - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (!__use_other_ctor<tuple<_UElements...>&>()) - && __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)) - { } - - template<typename _Alloc, typename... _UElements> - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (!__use_other_ctor<const tuple<_UElements...>>()) - && __constructible<const _UElements...> - explicit(!__convertible<const _UElements...>) - constexpr - tuple(allocator_arg_t __tag, const _Alloc& __a, - const tuple<_UElements...>&& __in) - : _Inherited(__tag, __a, - static_cast<const _Tuple_impl<0, _UElements...>&&>(__in)) - { } -#endif // C++23 + static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) + { __glibcxx_no_dangling_refs(_UElements&&); } +#endif // concepts && conditional_explicit // tuple assignment +#if __cpp_concepts // >= C++20 + private: + template<typename... _UTypes> + static consteval bool + __assignable() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return (is_assignable_v<_Elements&, _UTypes> && ...); + else + return false; + } + + template<typename... _UTypes> + static consteval bool + __nothrow_assignable() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return (is_nothrow_assignable_v<_Elements&, _UTypes> && ...); + else + return false; + } + +#if __cpp_lib_ranges_zip // >= C++23 + template<typename... _UTypes> + static consteval bool + __const_assignable() + { + if constexpr (sizeof...(_UTypes) == sizeof...(_Elements)) + return (is_assignable_v<const _Elements&, _UTypes> && ...); + else + return false; + } +#endif // C++23 + + public: + + tuple& operator=(const tuple& __u) = delete; + + constexpr tuple& + operator=(const tuple& __u) + noexcept(__nothrow_assignable<const _Elements&...>()) + requires (__assignable<const _Elements&...>()) + { + 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<typename... _UTypes> + requires (__assignable<const _UTypes&...>()) + constexpr tuple& + operator=(const tuple<_UTypes...>& __u) + noexcept(__nothrow_assignable<const _UTypes&...>()) + { + this->_M_assign(__u); + return *this; + } + + template<typename... _UTypes> + 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<const _Elements&...>()) + { + 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<typename... _UTypes> + constexpr const tuple& + operator=(const tuple<_UTypes...>& __u) const + requires (__const_assignable<const _UTypes&...>()) + { + this->_M_assign(__u); + return *this; + } + + template<typename... _UTypes> + constexpr const tuple& + operator=(tuple<_UTypes...>&& __u) const + requires (__const_assignable<_UTypes...>()) + { + this->_M_assign(std::move(__u)); + return *this; + } +#endif // C++23 + + template<typename _U1, typename _U2> + requires (__assignable<const _U1&, const _U2&>()) + constexpr tuple& + operator=(const pair<_U1, _U2>& __u) + noexcept(__nothrow_assignable<const _U1&, const _U2&>()) + { + this->_M_head(*this) = __u.first; + this->_M_tail(*this)._M_head(*this) = __u.second; + return *this; + } + + template<typename _U1, typename _U2> + 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<typename _U1, typename _U2> + requires (__const_assignable<const _U1&, const _U2>()) + 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<typename _U1, typename _U2> + 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& operator=(__conditional_t<__assignable<const _Elements&...>(), @@ -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<const _Elements> && ...) - { - this->_M_assign(__in); - return *this; - } - - constexpr const tuple& - operator=(tuple&& __in) const - requires (is_assignable_v<const _Elements&, _Elements> && ...) - { - this->_M_assign(std::move(__in)); - return *this; - } - - template<typename... _UElements> - constexpr const tuple& - operator=(const tuple<_UElements...>& __in) const - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (is_assignable_v<const _Elements&, const _UElements&> && ...) - { - this->_M_assign(__in); - return *this; - } - - template<typename... _UElements> - constexpr const tuple& - operator=(tuple<_UElements...>&& __in) const - requires (sizeof...(_Elements) == sizeof...(_UElements)) - && (is_assignable_v<const _Elements&, _UElements> && ...) - { - 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_conditional_explicit) // >= C++20 /// Partial specialization, 2-element tuple. /// Includes construction and assignment from a pair. template<typename _T1, typename _T2> @@ -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<typename _U1, typename _U2> - static constexpr bool __constructible - = _TCC<true>::template __constructible<_U1, _U2>::value; - - template<typename _U1, typename _U2> - static constexpr bool __convertible - = _TCC<true>::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<bool _Dummy = true, @@ -1344,14 +1903,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION 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&&); } template<typename _U1, typename _U2, _ExplicitCtor<!__is_alloc_arg<_U1>(), _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<const _U1&, const _U2&>()) - : _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) { } + : _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template<typename _U1, typename _U2, _ExplicitCtor<true, const _U1&, const _U2&> = false> explicit constexpr tuple(const tuple<_U1, _U2>& __in) noexcept(__nothrow_constructible<const _U1&, const _U2&>()) - : _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) { } + : _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template<typename _U1, typename _U2, _ImplicitCtor<true, _U1, _U2> = 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<typename _U1, typename _U2, _ExplicitCtor<true, _U1, _U2> = 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<typename _U1, typename _U2> - 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<typename _U1, typename _U2> - requires __constructible<const _U1, const _U2> - explicit(!__convertible<const _U1, const _U2>) - constexpr - tuple(const tuple<_U1, _U2>&& __in) - noexcept(__nothrow_constructible<const _U1, const _U2>()) - : _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&&>(__in)) { } -#endif // C++23 + : _Inherited(static_cast<_Tuple_impl<0, _U1, _U2>&&>(__in)) + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template<typename _U1, typename _U2, _ImplicitCtor<true, const _U1&, const _U2&> = true> constexpr tuple(const pair<_U1, _U2>& __in) noexcept(__nothrow_constructible<const _U1&, const _U2&>()) - : _Inherited(__in.first, __in.second) { } + : _Inherited(__in.first, __in.second) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template<typename _U1, typename _U2, _ExplicitCtor<true, const _U1&, const _U2&> = false> explicit constexpr tuple(const pair<_U1, _U2>& __in) noexcept(__nothrow_constructible<const _U1&, const _U2&>()) - : _Inherited(__in.first, __in.second) { } + : _Inherited(__in.first, __in.second) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template<typename _U1, typename _U2, _ImplicitCtor<true, _U1, _U2> = 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<typename _U1, typename _U2, _ExplicitCtor<true, _U1, _U2> = 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<typename _U1, typename _U2> - 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<typename _U1, typename _U2> - requires __constructible<const _U1, const _U2> - explicit(!__convertible<const _U1, const _U2>) - constexpr - tuple(const pair<_U1, _U2>&& __in) - noexcept(__nothrow_constructible<const _U1, const _U2>()) - : _Inherited(std::forward<const _U1>(__in.first), - std::forward<const _U2>(__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<typename _Alloc, typename _U1, typename _U2, _ExplicitCtor<true, _U1, _U2> = 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<typename _Alloc> _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<const _Tuple_impl<0, _U1, _U2>&>(__in)) - { } + static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template<typename _Alloc, typename _U1, typename _U2, _ExplicitCtor<true, const _U1&, const _U2&> = 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<const _Tuple_impl<0, _U1, _U2>&>(__in)) - { } + static_cast<const _Tuple_impl<0, _U1, _U2>&>(__in)) + { __glibcxx_no_dangling_refs(const _U1&, const _U2&); } template<typename _Alloc, typename _U1, typename _U2, _ImplicitCtor<true, _U1, _U2> = 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<typename _Alloc, typename _U1, typename _U2, _ExplicitCtor<true, _U1, _U2> = 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<typename _Alloc, typename _U1, typename _U2> - 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<typename _Alloc, typename _U1, typename _U2> - requires __constructible<const _U1, const _U2> - explicit(!__convertible<const _U1, const _U2>) - constexpr - tuple(allocator_arg_t __tag, const _Alloc& __a, - const tuple<_U1, _U2>&& __in) - : _Inherited(__tag, __a, - static_cast<const _Tuple_impl<0, _U1, _U2>&&>(__in)) - { } -#endif // C++23 + { __glibcxx_no_dangling_refs(_U1&&, _U2&&); } template<typename _Alloc, typename _U1, typename _U2, _ImplicitCtor<true, const _U1&, const _U2&> = 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<typename _Alloc, typename _U1, typename _U2, _ExplicitCtor<true, const _U1&, const _U2&> = 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<typename _Alloc, typename _U1, typename _U2, _ImplicitCtor<true, _U1, _U2> = 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<typename _Alloc, typename _U1, typename _U2, _ExplicitCtor<true, _U1, _U2> = 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<typename _Alloc, typename _U1, typename _U2> - 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<typename _Alloc, typename _U1, typename _U2> - requires __constructible<const _U1, const _U2> - explicit(!__convertible<const _U1, const _U2>) - constexpr - tuple(allocator_arg_t __tag, const _Alloc& __a, const pair<_U1, _U2>&& __in) - : _Inherited(__tag, __a, std::forward<const _U1>(__in.first), - std::forward<const _U2>(__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<const _T1> && is_copy_assignable_v<const _T2> - { - this->_M_assign(__in); - return *this; - } - - constexpr const tuple& - operator=(tuple&& __in) const - requires is_assignable_v<const _T1&, _T1> && is_assignable_v<const _T2, _T2> - { - this->_M_assign(std::move(__in)); - return *this; - } - - template<typename _U1, typename _U2> - constexpr const tuple& - operator=(const tuple<_U1, _U2>& __in) const - requires is_assignable_v<const _T1&, const _U1&> - && is_assignable_v<const _T2&, const _U2&> - { - this->_M_assign(__in); - return *this; - } - - template<typename _U1, typename _U2> - constexpr const tuple& - operator=(tuple<_U1, _U2>&& __in) const - requires is_assignable_v<const _T1&, _U1> - && is_assignable_v<const _T2&, _U2> - { - this->_M_assign(std::move(__in)); - return *this; - } -#endif // C++23 - template<typename _U1, typename _U2> _GLIBCXX20_CONSTEXPR __enable_if_t<__assignable<const _U1&, const _U2&>(), tuple&> @@ -1709,47 +2169,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return *this; } -#if __cplusplus > 202002L - template<typename _U1, typename _U2> - constexpr const tuple& - operator=(const pair<_U1, _U2>& __in) const - requires is_assignable_v<const _T1&, const _U1&> - && is_assignable_v<const _T2&, const _U2&> - { - this->_M_head(*this) = __in.first; - this->_M_tail(*this)._M_head(*this) = __in.second; - return *this; - } - - template<typename _U1, typename _U2> - constexpr const tuple& - operator=(pair<_U1, _U2>&& __in) const - requires is_assignable_v<const _T1&, _U1> - && is_assignable_v<const _T2&, _U2> - { - 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<const _T1>, - __is_nothrow_swappable<const _T2>>) - requires is_swappable_v<const _T1> && is_swappable_v<const _T2> - { _Inherited::_M_swap(__in); } -#endif // C++23 }; - +#endif // concepts && conditional_explicit /// class tuple_size template<typename... _Elements> @@ -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<typename... _Elements> requires (is_swappable_v<const _Elements> && ...) constexpr void @@ -2329,7 +2756,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } #endif -#if __cplusplus > 202002L +#if __cpp_lib_ranges_zip // >= C++23 template<typename... _TTypes, typename... _UTypes, template<typename> class _TQual, template<typename> class _UQual> requires requires { typename tuple<common_reference_t<_TQual<_TTypes>, _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 b6b680a3c58..a9bb2806ca9 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<typename _Tp> + constexpr bool __is_implicitly_default_constructible_v + = requires (void(&__f)(_Tp)) { __f({}); }; + + template<typename _Tp> + struct __is_implicitly_default_constructible + : __bool_constant<__is_implicitly_default_constructible_v<_Tp>> + { }; +#else struct __do_is_implicitly_default_constructible_impl { template <typename _Tp> @@ -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<typename _Tp> 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 00000000000..c6c8e0c3ef4 --- /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 <tuple> +#include <utility> + +#if __cplusplus >= 202002L +// For C++20 and later, constructors are constrained to disallow dangling. +static_assert(!std::is_constructible_v<std::tuple<const int&, int>, long, int>); +static_assert(!std::is_constructible_v<std::tuple<int, const int&>, int, long>); +static_assert(!std::is_constructible_v<std::tuple<const int&, int>, + std::tuple<long, long>>); +static_assert(!std::is_constructible_v<std::tuple<int, const int&>, + std::tuple<long, long>>); +static_assert(!std::is_constructible_v<std::tuple<const int&, int>, + const std::tuple<long, long>&>); +static_assert(!std::is_constructible_v<std::tuple<int, const int&>, + const std::tuple<long, long>&>); +static_assert(!std::is_constructible_v<std::tuple<const int&, int>, + std::pair<long, long>>); +static_assert(!std::is_constructible_v<std::tuple<int, const int&>, + std::pair<long, long>>); +static_assert(!std::is_constructible_v<std::tuple<const int&, int>, + const std::pair<long, long>&>); +static_assert(!std::is_constructible_v<std::tuple<int, const int&>, + const std::pair<long, long>&>); +#endif + +void +test_ary_ctors() +{ + std::tuple<const int&, int> t1(1L, 2); + // { dg-error "here" "" { target { c++17_down && hosted } } 33 } + // { dg-error "use of deleted function" "" { target c++20 } 33 } + + std::tuple<int, const int&> t2(1, 2L); + // { dg-error "here" "" { target { c++17_down && hosted } } 37 } + // { dg-error "use of deleted function" "" { target c++20 } 37 } + + std::tuple<const int&, const int&> t3(1L, 2L); + // { dg-error "here" "" { target { c++17_down && hosted } } 41 } + // { dg-error "use of deleted function" "" { target c++20 } 41 } + + std::tuple<const int&, const int&> t4(std::pair<long, int>{}); + // { dg-error "here" "" { target { c++17_down && hosted } } 45 } + // { dg-error "use of deleted function" "" { target c++20 } 45 } + + std::pair<int, long> p; + std::tuple<const int&, const int&> 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<long, long> t0; + + std::tuple<const int&, int> t1(t0); + // { dg-error "here" "" { target { c++17_down && hosted } } 60 } + // { dg-error "use of deleted function" "" { target c++20 } 60 } + + std::tuple<int, const int&> t2(t0); + // { dg-error "here" "" { target { c++17_down && hosted } } 64 } + // { dg-error "use of deleted function" "" { target c++20 } 64 } + + std::tuple<const int&, const int&> t3(t0); + // { dg-error "here" "" { target { c++17_down && hosted } } 68 } + // { dg-error "use of deleted function" "" { target c++20 } 68 } + + std::tuple<const int&, int> 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<int, const int&> 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<const int&, const int&> 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<long, long> p0; + std::tuple<const int&, int> t7(p0); + // { dg-error "here" "" { target { c++17_down && hosted } } 85 } + // { dg-error "use of deleted function" "" { target c++20 } 85 } + + std::tuple<int, const int&> t8(p0); + // { dg-error "here" "" { target { c++17_down && hosted } } 89 } + // { dg-error "use of deleted function" "" { target c++20 } 89 } + + std::tuple<const int&, int> 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<int, const int&> 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 }