public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
@ 2024-01-11 22:16 Jonathan Wakely
  2024-01-11 22:25 ` Ville Voutilainen
                   ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-11 22:16 UTC (permalink / raw)
  To: libstdc++, gcc-patches; +Cc: Patrick Palka, Ville Voutilainen

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...>;
 
+#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 }
-- 
2.43.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-11 22:16 [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822] Jonathan Wakely
@ 2024-01-11 22:25 ` Ville Voutilainen
  2024-01-12 10:48 ` Jonathan Wakely
  2024-01-12 17:55 ` Patrick Palka
  2 siblings, 0 replies; 13+ messages in thread
From: Ville Voutilainen @ 2024-01-11 22:25 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: libstdc++, gcc-patches, Patrick Palka

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.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-11 22:16 [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822] Jonathan Wakely
  2024-01-11 22:25 ` Ville Voutilainen
@ 2024-01-12 10:48 ` Jonathan Wakely
  2024-01-12 17:55 ` Patrick Palka
  2 siblings, 0 replies; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-12 10:48 UTC (permalink / raw)
  To: libstdc++, gcc-patches; +Cc: Patrick Palka, Ville Voutilainen

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.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-11 22:16 [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822] Jonathan Wakely
  2024-01-11 22:25 ` Ville Voutilainen
  2024-01-12 10:48 ` Jonathan Wakely
@ 2024-01-12 17:55 ` Patrick Palka
  2024-01-12 18:03   ` Jonathan Wakely
  2 siblings, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2024-01-12 17:55 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: libstdc++, gcc-patches, Patrick Palka, Ville Voutilainen

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
> 
> 


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-12 17:55 ` Patrick Palka
@ 2024-01-12 18:03   ` Jonathan Wakely
  2024-01-12 18:33     ` Patrick Palka
  0 siblings, 1 reply; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-12 18:03 UTC (permalink / raw)
  To: Patrick Palka; +Cc: libstdc++, gcc-patches, Ville Voutilainen

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.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-12 18:03   ` Jonathan Wakely
@ 2024-01-12 18:33     ` Patrick Palka
  2024-01-12 21:35       ` Jonathan Wakely
  2024-01-12 23:42       ` Jonathan Wakely
  0 siblings, 2 replies; 13+ messages in thread
From: Patrick Palka @ 2024-01-12 18:33 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Patrick Palka, libstdc++, gcc-patches, Ville Voutilainen

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.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-12 18:33     ` Patrick Palka
@ 2024-01-12 21:35       ` Jonathan Wakely
  2024-01-12 23:42       ` Jonathan Wakely
  1 sibling, 0 replies; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-12 21:35 UTC (permalink / raw)
  To: Patrick Palka; +Cc: libstdc++, gcc-patches, Ville Voutilainen

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.
>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-12 18:33     ` Patrick Palka
  2024-01-12 21:35       ` Jonathan Wakely
@ 2024-01-12 23:42       ` Jonathan Wakely
  2024-01-13  0:06         ` Patrick Palka
  1 sibling, 1 reply; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-12 23:42 UTC (permalink / raw)
  To: Patrick Palka; +Cc: libstdc++, gcc-patches, Ville Voutilainen

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.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-12 23:42       ` Jonathan Wakely
@ 2024-01-13  0:06         ` Patrick Palka
  2024-01-13 11:16           ` Jonathan Wakely
  0 siblings, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2024-01-13  0:06 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Patrick Palka, libstdc++, gcc-patches, Ville Voutilainen

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


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-13  0:06         ` Patrick Palka
@ 2024-01-13 11:16           ` Jonathan Wakely
  2024-01-15 16:27             ` Patrick Palka
  0 siblings, 1 reply; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-13 11:16 UTC (permalink / raw)
  To: Patrick Palka; +Cc: libstdc++, gcc-patches, Ville Voutilainen

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.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-13 11:16           ` Jonathan Wakely
@ 2024-01-15 16:27             ` Patrick Palka
  2024-01-15 16:51               ` Jonathan Wakely
  0 siblings, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2024-01-15 16:27 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Patrick Palka, libstdc++, gcc-patches, Ville Voutilainen

[-- Attachment #1: Type: text/plain, Size: 9874 bytes --]

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()
      |                               ^~~~~~~~~~~~~~~~~~~~

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-15 16:27             ` Patrick Palka
@ 2024-01-15 16:51               ` Jonathan Wakely
  2024-01-15 19:03                 ` Jonathan Wakely
  0 siblings, 1 reply; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-15 16:51 UTC (permalink / raw)
  To: Patrick Palka; +Cc: libstdc++, gcc-patches, Ville Voutilainen

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&...>(),


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822]
  2024-01-15 16:51               ` Jonathan Wakely
@ 2024-01-15 19:03                 ` Jonathan Wakely
  0 siblings, 0 replies; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-15 19:03 UTC (permalink / raw)
  To: Patrick Palka; +Cc: libstdc++, gcc-patches, Ville Voutilainen

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.


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2024-01-15 19:04 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-01-11 22:16 [PATCH] libstdc++: Implement P2255R2 dangling checks for std::tuple [PR108822] Jonathan Wakely
2024-01-11 22:25 ` Ville Voutilainen
2024-01-12 10:48 ` Jonathan Wakely
2024-01-12 17:55 ` Patrick Palka
2024-01-12 18:03   ` Jonathan Wakely
2024-01-12 18:33     ` Patrick Palka
2024-01-12 21:35       ` Jonathan Wakely
2024-01-12 23:42       ` Jonathan Wakely
2024-01-13  0:06         ` Patrick Palka
2024-01-13 11:16           ` Jonathan Wakely
2024-01-15 16:27             ` Patrick Palka
2024-01-15 16:51               ` Jonathan Wakely
2024-01-15 19:03                 ` Jonathan Wakely

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).