libstdc++: Implement ranges::safe_range for C++20 (P1870R1)

Message ID 20191209173516.GA1888520@redhat.com
State New
Headers show
Series
  • libstdc++: Implement ranges::safe_range for C++20 (P1870R1)
Related show

Commit Message

Jonathan Wakely Dec. 9, 2019, 5:35 p.m.
This change replaces the __forwarding_range implementation detail with
the ranges::safe_range concept and adds the ranges::enable_safe_range
variable template for opt-in in to the concept.

It also adjusts the begin/end/rbegin/rend customization point objects to
match the new rules for accessing rvalue ranges only when safe to do so.

	* include/bits/range_access.h (ranges::enable_safe_range): Define.
	(ranges::begin, ranges::end, ranges::rbegin, ranges::rend): Constrain
	to only accept types satisfying safe_range and treat argument as an
	lvalue when calling a member of performing ADL.
	(ranges::__detail::__range_impl, ranges::__detail::__forwarding_range):
	Remove.
	(ranges::range): Adjust definition.
	(ranges::safe_range): Define.
	(ranges::iterator_t, ranges::range_difference_t): Reorder definitions
	to match the synopsis in the working draft.
	(ranges::disable_sized_range): Remove duplicate definition.
	* include/experimental/string_view (ranges::enable_safe_range): Add
	partial specialization for std::experimental::basic_string_view.
	* include/std/ranges (ranges::viewable_range, ranges::subrange)
	(ranges::empty_view, ranges::iota_view): Use safe_range. Specialize
	enable_safe_range.
	(ranges::safe_iterator_t, ranges::safe_subrange_t): Define.
	* include/std/span (ranges::enable_safe_range): Add partial
	specialization for std::span.
	* include/std/string_view (ranges::enable_safe_range): Likewise for
	std::basic_string_view.
	* testsuite/std/ranges/access/begin.cc: Adjust expected results.
	* testsuite/std/ranges/access/cbegin.cc: Likewise.
	* testsuite/std/ranges/access/cdata.cc: Likewise.
	* testsuite/std/ranges/access/cend.cc: Likewise.
	* testsuite/std/ranges/access/crbegin.cc: Likewise.
	* testsuite/std/ranges/access/crend.cc: Likewise.
	* testsuite/std/ranges/access/data.cc: Likewise.
	* testsuite/std/ranges/access/end.cc: Likewise.
	* testsuite/std/ranges/access/rbegin.cc: Likewise.
	* testsuite/std/ranges/access/rend.cc: Likewise.
	* testsuite/std/ranges/empty_view.cc: Test ranges::begin and
	ranges::end instead of unqualified calls to begin and end.
	* testsuite/std/ranges/safe_range.cc: New test.
	* testsuite/std/ranges/safe_range_types.cc: New test.
	* testsuite/util/testsuite_iterators.h: Add comment about safe_range.

Tested powerpc64le-linux, committed to trunk.
commit b081863b2bb9d2a0b5421556a3e3d7c638278145
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Mon Dec 9 08:50:48 2019 +0000

    libstdc++: Implement ranges::safe_range for C++20 (P1870R1)
    
    This change replaces the __forwarding_range implementation detail with
    the ranges::safe_range concept and adds the ranges::enable_safe_range
    variable template for opt-in in to the concept.
    
    It also adjusts the begin/end/rbegin/rend customization point objects to
    match the new rules for accessing rvalue ranges only when safe to do so.
    
            * include/bits/range_access.h (ranges::enable_safe_range): Define.
            (ranges::begin, ranges::end, ranges::rbegin, ranges::rend): Constrain
            to only accept types satisfying safe_range and treat argument as an
            lvalue when calling a member of performing ADL.
            (ranges::__detail::__range_impl, ranges::__detail::__forwarding_range):
            Remove.
            (ranges::range): Adjust definition.
            (ranges::safe_range): Define.
            (ranges::iterator_t, ranges::range_difference_t): Reorder definitions
            to match the synopsis in the working draft.
            (ranges::disable_sized_range): Remove duplicate definition.
            * include/experimental/string_view (ranges::enable_safe_range): Add
            partial specialization for std::experimental::basic_string_view.
            * include/std/ranges (ranges::viewable_range, ranges::subrange)
            (ranges::empty_view, ranges::iota_view): Use safe_range. Specialize
            enable_safe_range.
            (ranges::safe_iterator_t, ranges::safe_subrange_t): Define.
            * include/std/span (ranges::enable_safe_range): Add partial
            specialization for std::span.
            * include/std/string_view (ranges::enable_safe_range): Likewise for
            std::basic_string_view.
            * testsuite/std/ranges/access/begin.cc: Adjust expected results.
            * testsuite/std/ranges/access/cbegin.cc: Likewise.
            * testsuite/std/ranges/access/cdata.cc: Likewise.
            * testsuite/std/ranges/access/cend.cc: Likewise.
            * testsuite/std/ranges/access/crbegin.cc: Likewise.
            * testsuite/std/ranges/access/crend.cc: Likewise.
            * testsuite/std/ranges/access/data.cc: Likewise.
            * testsuite/std/ranges/access/end.cc: Likewise.
            * testsuite/std/ranges/access/rbegin.cc: Likewise.
            * testsuite/std/ranges/access/rend.cc: Likewise.
            * testsuite/std/ranges/empty_view.cc: Test ranges::begin and
            ranges::end instead of unqualified calls to begin and end.
            * testsuite/std/ranges/safe_range.cc: New test.
            * testsuite/std/ranges/safe_range_types.cc: New test.
            * testsuite/util/testsuite_iterators.h: Add comment about safe_range.

Patch

diff --git a/libstdc++-v3/include/bits/range_access.h b/libstdc++-v3/include/bits/range_access.h
index c94e965afb4..6ebd667f031 100644
--- a/libstdc++-v3/include/bits/range_access.h
+++ b/libstdc++-v3/include/bits/range_access.h
@@ -342,6 +342,9 @@  namespace ranges
   template<typename>
     inline constexpr bool disable_sized_range = false;
 
+  template<typename _Tp>
+    inline constexpr bool enable_safe_range = false;
+
   namespace __detail
   {
     using __max_diff_type = long long;
@@ -359,10 +362,19 @@  namespace ranges
       constexpr make_unsigned_t<_Tp>
       __to_unsigned_like(_Tp __t) noexcept
       { return __t; }
+
+    // Part of the constraints of ranges::safe_range
+    template<typename _Tp>
+      concept __maybe_safe_range
+	= is_lvalue_reference_v<_Tp> || enable_safe_range<remove_cvref_t<_Tp>>;
+
   } // namespace __detail
 
   namespace __cust_access
   {
+    using std::ranges::__detail::__maybe_safe_range;
+    using std::__detail::__class_or_enum;
+
     template<typename _Tp>
       constexpr decay_t<_Tp>
       __decay_copy(_Tp&& __t)
@@ -370,20 +382,19 @@  namespace ranges
       { return std::forward<_Tp>(__t); }
 
     template<typename _Tp>
-      concept __member_begin = is_lvalue_reference_v<_Tp>
-	&& requires(_Tp __t)
-	{ { __decay_copy(__t.begin()) } -> input_or_output_iterator; };
+      concept __member_begin = requires(_Tp& __t)
+	{
+	  { __decay_copy(__t.begin()) } -> input_or_output_iterator;
+	};
 
     template<typename _Tp> void begin(_Tp&&) = delete;
     template<typename _Tp> void begin(initializer_list<_Tp>&&) = delete;
 
     template<typename _Tp>
-      concept __adl_begin
-	= std::__detail::__class_or_enum<remove_reference_t<_Tp>>
-	&& requires(_Tp&& __t)
+      concept __adl_begin = __class_or_enum<remove_reference_t<_Tp>>
+	&& requires(_Tp& __t)
 	{
-	  { __decay_copy(begin(std::forward<_Tp>(__t))) }
-	    -> input_or_output_iterator;
+	  { __decay_copy(begin(__t)) } -> input_or_output_iterator;
 	};
 
     struct _Begin
@@ -396,47 +407,45 @@  namespace ranges
 	  if constexpr (is_array_v<remove_reference_t<_Tp>>)
 	    return true;
 	  else if constexpr (__member_begin<_Tp>)
-	    return noexcept(__decay_copy(std::declval<_Tp>().begin()));
+	    return noexcept(__decay_copy(std::declval<_Tp&>().begin()));
 	  else
-	    return noexcept(__decay_copy(begin(std::declval<_Tp>())));
+	    return noexcept(__decay_copy(begin(std::declval<_Tp&>())));
 	}
 
     public:
-      template<typename _Tp>
+      template<__maybe_safe_range _Tp>
 	requires is_array_v<remove_reference_t<_Tp>> || __member_begin<_Tp>
 	  || __adl_begin<_Tp>
 	constexpr auto
-	operator()(_Tp&& __e) const noexcept(_S_noexcept<_Tp>())
+	operator()(_Tp&& __t) const noexcept(_S_noexcept<_Tp>())
 	{
 	  if constexpr (is_array_v<remove_reference_t<_Tp>>)
 	    {
 	      static_assert(is_lvalue_reference_v<_Tp>);
-	      return __e;
+	      return __t;
 	    }
 	  else if constexpr (__member_begin<_Tp>)
-	    return __e.begin();
+	    return __t.begin();
 	  else
-	    return begin(std::forward<_Tp>(__e));
+	    return begin(__t);
 	}
     };
 
     template<typename _Tp>
-      concept __member_end = is_lvalue_reference_v<_Tp>
-	&& requires(_Tp __t)
+      concept __member_end = requires(_Tp& __t)
 	{
 	  { __decay_copy(__t.end()) }
-	    -> sentinel_for<decltype(_Begin{}(__t))>;
+	    -> sentinel_for<decltype(_Begin{}(std::forward<_Tp>(__t)))>;
 	};
 
     template<typename _Tp> void end(_Tp&&) = delete;
     template<typename _Tp> void end(initializer_list<_Tp>&&) = delete;
 
     template<typename _Tp>
-      concept __adl_end
-	= std::__detail::__class_or_enum<remove_reference_t<_Tp>>
-	&& requires(_Tp&& __t)
+      concept __adl_end = __class_or_enum<remove_reference_t<_Tp>>
+	&& requires(_Tp& __t)
 	{
-	  { __decay_copy(end(std::forward<_Tp>(__t))) }
+	  { __decay_copy(end(__t)) }
 	    -> sentinel_for<decltype(_Begin{}(std::forward<_Tp>(__t)))>;
 	};
 
@@ -450,28 +459,28 @@  namespace ranges
 	  if constexpr (is_array_v<remove_reference_t<_Tp>>)
 	    return true;
 	  else if constexpr (__member_end<_Tp>)
-	    return noexcept(__decay_copy(std::declval<_Tp>().end()));
+	    return noexcept(__decay_copy(std::declval<_Tp&>().end()));
 	  else
-	    return noexcept(__decay_copy(end(std::declval<_Tp>())));
+	    return noexcept(__decay_copy(end(std::declval<_Tp&>())));
 	}
 
     public:
-      template<typename _Tp>
+      template<__maybe_safe_range _Tp>
 	requires is_array_v<remove_reference_t<_Tp>> || __member_end<_Tp>
 	|| __adl_end<_Tp>
 	constexpr auto
-	operator()(_Tp&& __e) const noexcept(_S_noexcept<_Tp>())
+	operator()(_Tp&& __t) const noexcept(_S_noexcept<_Tp>())
 	{
 	  if constexpr (is_array_v<remove_reference_t<_Tp>>)
 	    {
 	      static_assert(is_lvalue_reference_v<_Tp>);
 	      static_assert(is_bounded_array_v<remove_reference_t<_Tp>>);
-	      return __e + extent_v<remove_reference_t<_Tp>>;
+	      return __t + extent_v<remove_reference_t<_Tp>>;
 	    }
 	  else if constexpr (__member_end<_Tp>)
-	    return __e.end();
+	    return __t.end();
 	  else
-	    return end(std::forward<_Tp>(__e));
+	    return end(__t);
 	}
     };
 
@@ -510,27 +519,25 @@  namespace ranges
     };
 
     template<typename _Tp>
-      concept __member_rbegin = is_lvalue_reference_v<_Tp>
-	&& requires(_Tp __t)
-	{ { __decay_copy(__t.rbegin()) } -> input_or_output_iterator; };
+      concept __member_rbegin = requires(_Tp& __t)
+	{
+	  { __decay_copy(__t.rbegin()) } -> input_or_output_iterator;
+	};
 
     template<typename _Tp> void rbegin(_Tp&&) = delete;
 
     template<typename _Tp>
-      concept __adl_rbegin
-	= std::__detail::__class_or_enum<remove_reference_t<_Tp>>
-	&& requires(_Tp&& __t)
+      concept __adl_rbegin = __class_or_enum<remove_reference_t<_Tp>>
+	&& requires(_Tp& __t)
 	{
-	  { __decay_copy(rbegin(std::forward<_Tp>(__t))) }
-	    -> input_or_output_iterator;
+	  { __decay_copy(rbegin(__t)) } -> input_or_output_iterator;
 	};
 
     template<typename _Tp>
-      concept __reversable = requires(_Tp&& __t)
+      concept __reversable = requires(_Tp& __t)
 	{
-	  { _Begin{}(std::forward<_Tp>(__t)) } -> bidirectional_iterator;
-	  { _End{}(std::forward<_Tp>(__t)) }
-	    -> same_as<decltype(_Begin{}(std::forward<_Tp>(__t)))>;
+	  { _Begin{}(__t) } -> bidirectional_iterator;
+	  { _End{}(__t) } -> same_as<decltype(_Begin{}(__t))>;
 	};
 
     struct _RBegin
@@ -541,14 +548,14 @@  namespace ranges
 	_S_noexcept()
 	{
 	  if constexpr (__member_rbegin<_Tp>)
-	    return noexcept(__decay_copy(std::declval<_Tp>().rbegin()));
+	    return noexcept(__decay_copy(std::declval<_Tp&>().rbegin()));
 	  else if constexpr (__adl_rbegin<_Tp>)
-	    return noexcept(__decay_copy(rbegin(std::declval<_Tp>())));
+	    return noexcept(__decay_copy(rbegin(std::declval<_Tp&>())));
 	  else
 	    {
-	      if constexpr (noexcept(_End{}(std::declval<_Tp>())))
+	      if constexpr (noexcept(_End{}(std::declval<_Tp&>())))
 		{
-		  using _It = decltype(_End{}(std::declval<_Tp>()));
+		  using _It = decltype(_End{}(std::declval<_Tp&>()));
 		  // std::reverse_iterator copy-initializes its member.
 		  return is_nothrow_copy_constructible_v<_It>;
 		}
@@ -558,24 +565,23 @@  namespace ranges
 	}
 
     public:
-      template<typename _Tp>
+      template<__maybe_safe_range _Tp>
 	requires __member_rbegin<_Tp> || __adl_rbegin<_Tp> || __reversable<_Tp>
 	constexpr auto
-	operator()(_Tp&& __e) const
+	operator()(_Tp&& __t) const
 	noexcept(_S_noexcept<_Tp>())
 	{
 	  if constexpr (__member_rbegin<_Tp>)
-	    return __e.rbegin();
+	    return __t.rbegin();
 	  else if constexpr (__adl_rbegin<_Tp>)
-	    return rbegin(std::forward<_Tp>(__e));
+	    return rbegin(__t);
 	  else
-	    return std::make_reverse_iterator(_End{}(std::forward<_Tp>(__e)));
+	    return std::make_reverse_iterator(_End{}(__t));
 	}
     };
 
     template<typename _Tp>
-      concept __member_rend = is_lvalue_reference_v<_Tp>
-	&& requires(_Tp __t)
+      concept __member_rend = requires(_Tp& __t)
 	{
 	  { __decay_copy(__t.rend()) }
 	    -> sentinel_for<decltype(_RBegin{}(__t))>;
@@ -584,11 +590,10 @@  namespace ranges
     template<typename _Tp> void rend(_Tp&&) = delete;
 
     template<typename _Tp>
-      concept __adl_rend
-	= std::__detail::__class_or_enum<remove_reference_t<_Tp>>
-	&& requires(_Tp&& __t)
+      concept __adl_rend = __class_or_enum<remove_reference_t<_Tp>>
+	&& requires(_Tp& __t)
 	{
-	  { __decay_copy(rend(std::forward<_Tp>(__t))) }
+	  { __decay_copy(rend(__t)) }
 	    -> sentinel_for<decltype(_RBegin{}(std::forward<_Tp>(__t)))>;
 	};
 
@@ -600,14 +605,14 @@  namespace ranges
 	_S_noexcept()
 	{
 	  if constexpr (__member_rend<_Tp>)
-	    return noexcept(__decay_copy(std::declval<_Tp>().rend()));
+	    return noexcept(__decay_copy(std::declval<_Tp&>().rend()));
 	  else if constexpr (__adl_rend<_Tp>)
-	    return noexcept(__decay_copy(rend(std::declval<_Tp>())));
+	    return noexcept(__decay_copy(rend(std::declval<_Tp&>())));
 	  else
 	    {
-	      if constexpr (noexcept(_Begin{}(std::declval<_Tp>())))
+	      if constexpr (noexcept(_Begin{}(std::declval<_Tp&>())))
 		{
-		  using _It = decltype(_Begin{}(std::declval<_Tp>()));
+		  using _It = decltype(_Begin{}(std::declval<_Tp&>()));
 		  // std::reverse_iterator copy-initializes its member.
 		  return is_nothrow_copy_constructible_v<_It>;
 		}
@@ -617,18 +622,18 @@  namespace ranges
 	}
 
     public:
-      template<typename _Tp>
+      template<__maybe_safe_range _Tp>
 	requires __member_rend<_Tp> || __adl_rend<_Tp> || __reversable<_Tp>
 	constexpr auto
-	operator()(_Tp&& __e) const
+	operator()(_Tp&& __t) const
 	noexcept(_S_noexcept<_Tp>())
 	{
 	  if constexpr (__member_rend<_Tp>)
-	    return __e.rend();
+	    return __t.rend();
 	  else if constexpr (__adl_rend<_Tp>)
-	    return rend(std::forward<_Tp>(__e));
+	    return rend(__t);
 	  else
-	    return std::make_reverse_iterator(_Begin{}(std::forward<_Tp>(__e)));
+	    return std::make_reverse_iterator(_Begin{}(__t));
 	}
     };
 
@@ -667,8 +672,7 @@  namespace ranges
     template<typename _Tp> void size(_Tp&&) = delete;
 
     template<typename _Tp>
-      concept __adl_size
-	= std::__detail::__class_or_enum<remove_reference_t<_Tp>>
+      concept __adl_size = __class_or_enum<remove_reference_t<_Tp>>
 	&& !disable_sized_range<remove_cvref_t<_Tp>>
 	&& requires(_Tp&& __t)
 	{
@@ -842,25 +846,26 @@  namespace ranges
     inline constexpr __cust_access::_CData cdata{};
   }
 
-  namespace __detail
-  {
-    template<typename _Tp>
-      concept __range_impl = requires(_Tp&& __t) {
-	ranges::begin(std::forward<_Tp>(__t));
-	ranges::end(std::forward<_Tp>(__t));
-      };
-
-  } // namespace __detail
-
   /// [range.range] The range concept.
   template<typename _Tp>
-    concept range = __detail::__range_impl<_Tp&>;
+    concept range = requires(_Tp& __t)
+      {
+	ranges::begin(__t);
+	ranges::end(__t);
+      };
+
+  /// [range.range] The safe_range concept.
+  template<typename _Tp>
+    concept safe_range = range<_Tp> && __detail::__maybe_safe_range<_Tp>;
+
+  template<range _Range>
+    using iterator_t = decltype(ranges::begin(std::declval<_Range&>()));
 
   template<range _Range>
     using sentinel_t = decltype(ranges::end(std::declval<_Range&>()));
 
   template<range _Range>
-    using iterator_t = decltype(ranges::begin(std::declval<_Range&>()));
+    using range_difference_t = iter_difference_t<iterator_t<_Range>>;
 
   template<range _Range>
     using range_value_t = iter_value_t<iterator_t<_Range>>;
@@ -872,43 +877,38 @@  namespace ranges
     using range_rvalue_reference_t
       = iter_rvalue_reference_t<iterator_t<_Range>>;
 
-  template<range _Range>
-    using range_difference_t = iter_difference_t<iterator_t<_Range>>;
-
-  namespace __detail
-  {
-    template<typename _Tp>
-      concept __forwarding_range = range<_Tp> && __range_impl<_Tp>;
-  } // namespace __detail
-
   /// [range.sized] The sized_range concept.
   template<typename _Tp>
     concept sized_range = range<_Tp>
       && requires(_Tp& __t) { ranges::size(__t); };
 
-  template<typename>
-    inline constexpr bool disable_sized_range = false;
-
   // [range.refinements]
+
+  /// A range for which ranges::begin returns an output iterator.
   template<typename _Range, typename _Tp>
     concept output_range
       = range<_Range> && output_iterator<iterator_t<_Range>, _Tp>;
 
+  /// A range for which ranges::begin returns an input iterator.
   template<typename _Tp>
     concept input_range = range<_Tp> && input_iterator<iterator_t<_Tp>>;
 
+  /// A range for which ranges::begin returns a forward iterator.
   template<typename _Tp>
     concept forward_range
       = input_range<_Tp> && forward_iterator<iterator_t<_Tp>>;
 
+  /// A range for which ranges::begin returns a bidirectional iterator.
   template<typename _Tp>
     concept bidirectional_range
       = forward_range<_Tp> && bidirectional_iterator<iterator_t<_Tp>>;
 
+  /// A range for which ranges::begin returns a random access iterator.
   template<typename _Tp>
     concept random_access_range
       = bidirectional_range<_Tp> && random_access_iterator<iterator_t<_Tp>>;
 
+  /// A range for which ranges::begin returns a contiguous iterator.
   template<typename _Tp>
     concept contiguous_range
       = random_access_range<_Tp> && contiguous_iterator<iterator_t<_Tp>>
@@ -917,11 +917,12 @@  namespace ranges
 	{ ranges::data(__t) } -> same_as<add_pointer_t<range_reference_t<_Tp>>>;
       };
 
+  /// A range for which ranges::begin and ranges::end return the same type.
   template<typename _Tp>
     concept common_range
       = range<_Tp> && same_as<iterator_t<_Tp>, sentinel_t<_Tp>>;
 
-    // [range.iter.ops] range iterator operations
+  // [range.iter.ops] range iterator operations
 
   template<input_or_output_iterator _It>
     constexpr void
diff --git a/libstdc++-v3/include/experimental/string_view b/libstdc++-v3/include/experimental/string_view
index 84b2a3eb402..cc9cb93ad5e 100644
--- a/libstdc++-v3/include/experimental/string_view
+++ b/libstdc++-v3/include/experimental/string_view
@@ -691,6 +691,18 @@  namespace experimental
   } // namespace literals
 } // namespace experimental
 
+#if __cpp_lib_concepts
+  namespace ranges
+  {
+    template<typename> extern inline const bool enable_safe_range;
+    // Opt-in to safe_range concept
+    template<typename _CharT, typename _Traits>
+      inline constexpr bool
+	enable_safe_range<experimental::basic_string_view<_CharT, _Traits>>
+	  = true;
+  }
+#endif
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index e1bf6eec5d0..c2567818cb5 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -89,9 +89,10 @@  namespace ranges
       = range<_Tp> && movable<_Tp> && default_initializable<_Tp>
 	&& enable_view<_Tp>;
 
+  /// A range which can be safely converted to a view.
   template<typename _Tp>
     concept viewable_range = range<_Tp>
-      && (__detail::__forwarding_range<_Tp> || view<decay_t<_Tp>>);
+      && (safe_range<_Tp> || view<decay_t<_Tp>>);
 
   namespace __detail
   {
@@ -295,7 +296,7 @@  namespace ranges
       }
 
       template<__detail::__not_same_as<subrange> _Rng>
-	requires __detail::__forwarding_range<_Rng>
+	requires safe_range<_Rng>
 	  && convertible_to<iterator_t<_Rng>, _It>
 	  && convertible_to<sentinel_t<_Rng>, _Sent>
 	constexpr
@@ -306,7 +307,7 @@  namespace ranges
 	    _M_size._M_size = ranges::size(__r);
 	}
 
-      template<__detail::__forwarding_range _Rng>
+      template<safe_range _Rng>
 	requires convertible_to<iterator_t<_Rng>, _It>
 	  && convertible_to<sentinel_t<_Rng>, _Sent>
 	constexpr
@@ -401,12 +402,6 @@  namespace ranges
 	  ranges::advance(_M_begin, __n, _M_end);
 	return *this;
       }
-
-      friend constexpr _It
-      begin(subrange&& __r) { return __r.begin(); }
-
-      friend constexpr _Sent
-      end(subrange&& __r) { return __r.end(); }
     };
 
   template<input_or_output_iterator _It, sentinel_for<_It> _Sent>
@@ -424,14 +419,14 @@  namespace ranges
       -> subrange<tuple_element_t<0, _Pr>, tuple_element_t<1, _Pr>,
 		  subrange_kind::sized>;
 
-  template<__detail::__forwarding_range _Rng>
+  template<safe_range _Rng>
     subrange(_Rng&&)
       -> subrange<iterator_t<_Rng>, sentinel_t<_Rng>,
 		 (sized_range<_Rng>
 		  || sized_sentinel_for<sentinel_t<_Rng>, iterator_t<_Rng>>)
 		 ? subrange_kind::sized : subrange_kind::unsized>;
 
-  template<__detail::__forwarding_range _Rng>
+  template<safe_range _Rng>
     subrange(_Rng&&,
 	     __detail::__make_unsigned_like_t<range_difference_t<_Rng>>)
       -> subrange<iterator_t<_Rng>, sentinel_t<_Rng>, subrange_kind::sized>;
@@ -457,6 +452,12 @@  namespace ranges
       else
 	return __r.end();
     }
+
+  template<input_or_output_iterator _It, sentinel_for<_It> _Sent,
+	   subrange_kind _Kind>
+    inline constexpr bool
+      enable_safe_range<subrange<_It, _Sent, _Kind>> = true;
+
 } // namespace ranges
 
   using ranges::get;
@@ -471,8 +472,19 @@  namespace ranges
       constexpr dangling(_Args&&...) noexcept { }
   };
 
+  template<range _Range>
+    using safe_iterator_t = conditional_t<safe_range<_Range>,
+					  iterator_t<_Range>,
+					  dangling>;
+
+  template<range _Range>
+    using safe_subrange_t = conditional_t<safe_range<_Range>,
+					  subrange<iterator_t<_Range>>,
+					  dangling>;
+
   template<typename _Tp> requires is_object_v<_Tp>
-    class empty_view : public view_interface<empty_view<_Tp>>
+    class empty_view
+    : public view_interface<empty_view<_Tp>>
     {
     public:
       static constexpr _Tp* begin() noexcept { return nullptr; }
@@ -480,11 +492,11 @@  namespace ranges
       static constexpr _Tp* data() noexcept { return nullptr; }
       static constexpr size_t size() noexcept { return 0; }
       static constexpr bool empty() noexcept { return true; }
-
-      friend constexpr _Tp* begin(empty_view) noexcept { return nullptr; }
-      friend constexpr _Tp* end(empty_view) noexcept { return nullptr; }
     };
 
+  template<typename _Tp>
+    inline constexpr bool enable_safe_range<empty_view<_Tp>> = true;
+
   namespace __detail
   {
     template<copy_constructible _Tp> requires is_object_v<_Tp>
@@ -899,6 +911,9 @@  namespace ranges
 	    == __detail::__is_signed_integer_like<_Bound>))
     iota_view(_Winc, _Bound) -> iota_view<_Winc, _Bound>;
 
+  template<weakly_incrementable _Winc, semiregular _Bound>
+    inline constexpr bool enable_safe_range<iota_view<_Winc, _Bound>> = true;
+
 namespace views
 {
   template<typename _Tp>
diff --git a/libstdc++-v3/include/std/span b/libstdc++-v3/include/std/span
index c71f8bc3f89..f215decb453 100644
--- a/libstdc++-v3/include/std/span
+++ b/libstdc++-v3/include/std/span
@@ -399,16 +399,6 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	return {this->data() + __offset, __count};
       }
 
-      // observers: range helpers
-
-      friend constexpr iterator
-      begin(span __sp) noexcept
-      { return __sp.begin(); }
-
-      friend constexpr iterator
-      end(span __sp) noexcept
-      { return __sp.end(); }
-
     private:
       [[no_unique_address]] __detail::__extent_storage<extent> _M_extent;
       pointer _M_ptr;
@@ -478,6 +468,14 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       using type = _Type;
     };
 
+  namespace ranges
+  {
+    template<typename> extern inline const bool enable_safe_range;
+    // Opt-in to safe_range concept
+    template<typename _ElementType, size_t _Extent>
+      inline constexpr bool
+	enable_safe_range<span<_ElementType, _Extent>> = true;
+  }
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 #endif // concepts
diff --git a/libstdc++-v3/include/std/string_view b/libstdc++-v3/include/std/string_view
index 9d2a8e8e0c2..add432bbb09 100644
--- a/libstdc++-v3/include/std/string_view
+++ b/libstdc++-v3/include/std/string_view
@@ -724,6 +724,16 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
   } // namespace string_literals
   } // namespace literals
 
+#if __cpp_lib_concepts
+  namespace ranges
+  {
+    template<typename> extern inline const bool enable_safe_range;
+    // Opt-in to safe_range concept
+    template<typename _CharT, typename _Traits>
+      inline constexpr bool
+	enable_safe_range<basic_string_view<_CharT, _Traits>> = true;
+  }
+#endif
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 
diff --git a/libstdc++-v3/testsuite/std/ranges/access/begin.cc b/libstdc++-v3/testsuite/std/ranges/access/begin.cc
index e4c245a76bb..8439a2175e7 100644
--- a/libstdc++-v3/testsuite/std/ranges/access/begin.cc
+++ b/libstdc++-v3/testsuite/std/ranges/access/begin.cc
@@ -71,11 +71,22 @@  struct R
   int a[4] = { 0, 1, 2, 3 };
 
   friend int* begin(R& r) { return r.a + 0; }
-  friend int* begin(R&& r) { return r.a + 1; }
+  friend int* begin(R&& r); // this overload is not defined
   friend const int* begin(const R& r) noexcept { return r.a + 2; }
-  friend const int* begin(const R&& r) noexcept { return r.a + 3; }
+  friend const int* begin(const R&& r) noexcept; // not defined
 };
 
+struct RV // view on an R
+{
+  R& r;
+
+  friend int* begin(RV& rv) { return begin(rv.r); }
+  friend const int* begin(const RV& rv) noexcept { return begin(rv.r); }
+};
+
+// Allow ranges::begin to work with RV&&
+template<> constexpr bool std::ranges::enable_safe_range<RV> = true;
+
 void
 test03()
 {
@@ -86,20 +97,23 @@  test03()
   static_assert(!noexcept(std::ranges::begin(r)));
   VERIFY( std::ranges::begin(r) == begin(r) );
 
-  static_assert(same_as<decltype(std::ranges::begin(std::move(r))),
-		decltype(begin(std::move(r)))>);
-  static_assert(!noexcept(std::ranges::begin(std::move(r))));
-  VERIFY( std::ranges::begin(std::move(r)) == begin(std::move(r)) );
-
-
   static_assert(same_as<decltype(std::ranges::begin(c)), decltype(begin(c))>);
   static_assert(noexcept(std::ranges::begin(c)));
   VERIFY( std::ranges::begin(c) == begin(c) );
 
-  static_assert(same_as<decltype(std::ranges::begin(std::move(c))),
-		decltype(begin(std::move(c)))>);
-  static_assert(noexcept(std::ranges::begin(std::move(c))));
-  VERIFY( std::ranges::begin(std::move(c)) == begin(std::move(c)) );
+  RV v{r};
+  // enable_safe_range<RV> allows ranges::begin to work for rvalues,
+  // but it will call v.begin() or begin(v) on an lvalue:
+  static_assert(same_as<decltype(std::ranges::begin(std::move(v))),
+		decltype(begin(v))>);
+  static_assert(!noexcept(std::ranges::begin(std::move(v))));
+  VERIFY( std::ranges::begin(std::move(v)) == begin(v) );
+
+  const RV cv{r};
+  static_assert(same_as<decltype(std::ranges::begin(std::move(cv))),
+		decltype(begin(cv))>);
+  static_assert(noexcept(std::ranges::begin(std::move(cv))));
+  VERIFY( std::ranges::begin(std::move(cv)) == begin(cv) );
 }
 
 struct RR
@@ -111,12 +125,15 @@  struct RR
   short* begin() noexcept { return &s; }
   const long* begin() const { return &l; }
 
-  friend int* begin(RR& r) { return r.a + 0; }
-  friend int* begin(RR&& r) { return r.a + 1; }
+  friend int* begin(RR& r) noexcept { return r.a + 0; }
+  friend int* begin(RR&& r); // not defined
   friend const int* begin(const RR& r) { return r.a + 2; }
-  friend const int* begin(const RR&& r) noexcept { return r.a + 3; }
+  friend const int* begin(const RR&& r) noexcept; // not defined
 };
 
+// N.B. this is a lie, begin on an RR rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<RR> = true;
+
 void
 test04()
 {
@@ -125,14 +142,16 @@  test04()
   VERIFY( std::ranges::begin(r) == &r.s );
   static_assert(noexcept(std::ranges::begin(r)));
 
-  VERIFY( std::ranges::begin(std::move(r)) == r.a + 1 );
-  static_assert(!noexcept(std::ranges::begin(std::move(r))));
+  // calls r.begin() on an lvalue, not rvalue
+  VERIFY( std::ranges::begin(std::move(r)) == std::ranges::begin(r) );
+  static_assert(noexcept(std::ranges::begin(std::move(r))));
 
   VERIFY( std::ranges::begin(c) == &r.l );
   static_assert(!noexcept(std::ranges::begin(c)));
 
-  VERIFY( std::ranges::begin(std::move(c)) == r.a + 3 );
-  static_assert(noexcept(std::ranges::begin(std::move(c))));
+  // calls r.begin() on a const lvalue, not rvalue
+  VERIFY( std::ranges::begin(std::move(c)) == std::ranges::begin(c) );
+  static_assert(!noexcept(std::ranges::begin(std::move(c))));
 }
 
 int
diff --git a/libstdc++-v3/testsuite/std/ranges/access/cbegin.cc b/libstdc++-v3/testsuite/std/ranges/access/cbegin.cc
index 54db3658896..709df0d1588 100644
--- a/libstdc++-v3/testsuite/std/ranges/access/cbegin.cc
+++ b/libstdc++-v3/testsuite/std/ranges/access/cbegin.cc
@@ -40,20 +40,34 @@  struct R
   int a[4] = { 0, 1, 2, 3 };
 
   friend int* begin(R& r) { return r.a + 0; }
-  friend int* begin(R&& r) { return r.a + 1; }
+  friend int* begin(R&&); // this function is not defined
   friend const int* begin(const R& r) noexcept { return r.a + 2; }
-  friend const int* begin(const R&& r) noexcept { return r.a + 3; }
+  friend const int* begin(const R&&); // this function is not defined
 };
 
+struct RV // view on an R
+{
+  R& r;
+
+  friend int* begin(RV&); // this function is not defined
+  friend const int* begin(const RV& rv) noexcept { return begin(std::as_const(rv.r)); }
+};
+
+// Allow ranges::begin to work with RV&&
+template<> constexpr bool std::ranges::enable_safe_range<RV> = true;
+
 void
 test03()
 {
   R r;
   const R& c = r;
   VERIFY(std::ranges::cbegin(r) == std::ranges::begin(c));
-  VERIFY(std::ranges::cbegin(std::move(r)) == std::ranges::begin(std::move(c)));
   VERIFY(std::ranges::cbegin(c) == std::ranges::begin(c));
-  VERIFY(std::ranges::cbegin(std::move(c)) == std::ranges::begin(std::move(c)));
+
+  RV v{r};
+  VERIFY(std::ranges::cbegin(std::move(v)) == std::ranges::begin(c));
+  const RV cv{r};
+  VERIFY(std::ranges::cbegin(std::move(cv)) == std::ranges::begin(c));
 }
 
 struct RR
@@ -71,15 +85,18 @@  struct RR
   friend const int* begin(const RR&& r) noexcept { return r.a + 3; }
 };
 
+// N.B. this is a lie, cbegin on an RR rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<RR> = true;
+
 void
 test04()
 {
   RR r;
   const RR& c = r;
   VERIFY(std::ranges::cbegin(r) == std::ranges::begin(c));
-  VERIFY(std::ranges::cbegin(std::move(r)) == std::ranges::begin(std::move(c)));
+  VERIFY(std::ranges::cbegin(std::move(r)) == std::ranges::begin(c));
   VERIFY(std::ranges::cbegin(c) == std::ranges::begin(c));
-  VERIFY(std::ranges::cbegin(std::move(c)) == std::ranges::begin(std::move(c)));
+  VERIFY(std::ranges::cbegin(std::move(c)) == std::ranges::begin(c));
 }
 
 int
diff --git a/libstdc++-v3/testsuite/std/ranges/access/cdata.cc b/libstdc++-v3/testsuite/std/ranges/access/cdata.cc
index b16c99607a5..f928bb795c2 100644
--- a/libstdc++-v3/testsuite/std/ranges/access/cdata.cc
+++ b/libstdc++-v3/testsuite/std/ranges/access/cdata.cc
@@ -51,17 +51,22 @@  struct R
   long l = 0;
 
   int* data() const { return nullptr; }
-  friend long* begin(R&& r) { return &r.l; }
-  friend const long* begin(const R&& r) { return &r.l + 1; }
+  friend long* begin(R&& r); // this function is not defined
+  friend const long* begin(const R& r) { return &r.l; }
+  friend const short* begin(const R&&); // not defined
 };
 
+// This is a lie, ranges::begin(R&&) returns a dangling iterator.
+template<> constexpr bool std::ranges::enable_safe_range<R> = true;
+
 void
 test03()
 {
   R r;
   const R& c = r;
-  VERIFY( std::ranges::cdata(std::move(r)) == std::ranges::data(std::move(c)) );
-  VERIFY( std::ranges::cdata(std::move(c)) == std::ranges::data(std::move(c)) );
+  VERIFY( std::ranges::cdata(r) == std::ranges::data(c) );
+  VERIFY( std::ranges::cdata(std::move(r)) == std::ranges::begin(c) );
+  VERIFY( std::ranges::cdata(std::move(c)) == std::ranges::begin(c) );
 }
 
 int
diff --git a/libstdc++-v3/testsuite/std/ranges/access/cend.cc b/libstdc++-v3/testsuite/std/ranges/access/cend.cc
index 3b57b3dbcaf..59053f75d67 100644
--- a/libstdc++-v3/testsuite/std/ranges/access/cend.cc
+++ b/libstdc++-v3/testsuite/std/ranges/access/cend.cc
@@ -49,15 +49,31 @@  struct R
   friend const int* end(const R&& r) noexcept { return r.a + 3; }
 };
 
+struct RV // view on an R
+{
+  R& r;
+
+  friend const int* begin(RV& rv) { return rv.r.begin(); }
+  friend int* end(RV& rv) { return end(rv.r); }
+  friend const int* begin(const RV& rv) noexcept { return rv.r.begin(); }
+  friend const int* end(const RV& rv) noexcept { return end(std::as_const(rv.r)); }
+};
+
+// Allow ranges::end to work with RV&&
+template<> constexpr bool std::ranges::enable_safe_range<RV> = true;
+
 void
 test03()
 {
   R r;
   const R& c = r;
   VERIFY( std::ranges::cend(r) == std::ranges::end(c) );
-  VERIFY( std::ranges::cend(std::move(r)) == std::ranges::end(std::move(c)) );
   VERIFY( std::ranges::cend(c) == std::ranges::end(c) );
-  VERIFY( std::ranges::cend(std::move(c)) == std::ranges::end(std::move(c)) );
+
+  RV v{r};
+  const RV cv{r};
+  VERIFY( std::ranges::cend(std::move(v)) == std::ranges::end(c) );
+  VERIFY( std::ranges::cend(std::move(cv)) == std::ranges::end(c) );
 }
 
 struct RR
@@ -81,15 +97,19 @@  struct RR
   friend const int* end(const RR&& r) noexcept { return r.a + 3; }
 };
 
+// N.B. this is a lie, begin/end on an RR rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<RR> = true;
+
 void
 test04()
 {
   RR r;
   const RR& c = r;
   VERIFY( std::ranges::cend(r) == std::ranges::end(c) );
-  VERIFY( std::ranges::cend(std::move(r)) == std::ranges::end(std::move(c)) );
   VERIFY( std::ranges::cend(c) == std::ranges::end(c) );
-  VERIFY( std::ranges::cend(std::move(c)) == std::ranges::end(std::move(c)) );
+
+  VERIFY( std::ranges::cend(std::move(r)) == std::ranges::end(c) );
+  VERIFY( std::ranges::cend(std::move(c)) == std::ranges::end(c) );
 }
 
 int
diff --git a/libstdc++-v3/testsuite/std/ranges/access/crbegin.cc b/libstdc++-v3/testsuite/std/ranges/access/crbegin.cc
index d9e5b0cbef7..17ea7983470 100644
--- a/libstdc++-v3/testsuite/std/ranges/access/crbegin.cc
+++ b/libstdc++-v3/testsuite/std/ranges/access/crbegin.cc
@@ -31,15 +31,29 @@  struct R1
   friend const int* rbegin(const R1&& r) { return &r.j; }
 };
 
+struct R1V // view on an R1
+{
+  R1& r;
+
+  friend const long* rbegin(R1V&); // this is not defined
+  friend const int* rbegin(const R1V& rv) noexcept { return rv.r.rbegin(); }
+};
+
+// Allow ranges::end to work with R1V&&
+template<> constexpr bool std::ranges::enable_safe_range<R1V> = true;
+
 void
 test01()
 {
   R1 r;
   const R1& c = r;
   VERIFY( std::ranges::crbegin(r) == std::ranges::rbegin(c) );
-  VERIFY( std::ranges::crbegin(std::move(r)) == std::ranges::rbegin(std::move(c)) );
   VERIFY( std::ranges::crbegin(c) == std::ranges::rbegin(c) );
-  VERIFY( std::ranges::crbegin(std::move(c)) == std::ranges::rbegin(std::move(c)) );
+
+  R1V v{r};
+  const R1V cv{r};
+  VERIFY( std::ranges::crbegin(std::move(v)) == std::ranges::rbegin(c) );
+  VERIFY( std::ranges::crbegin(std::move(cv)) == std::ranges::rbegin(c) );
 }
 
 struct R2
@@ -50,19 +64,23 @@  struct R2
   const int* begin() const { return a; }
   const int* end() const { return a + 2; }
 
-  friend const long* begin(const R2&& r) { return r.l; }
-  friend const long* end(const R2&& r) { return r.l + 2; }
+  friend const long* begin(const R2&&); // not defined
+  friend const long* end(const R2&&); // not defined
 };
 
+// N.B. this is a lie, rbegin on an R2 rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<R2> = true;
+
 void
 test02()
 {
   R2 r;
   const R2& c = r;
   VERIFY( std::ranges::crbegin(r) == std::ranges::rbegin(c) );
-  VERIFY( std::ranges::crbegin(std::move(r)) == std::ranges::rbegin(std::move(c)) );
   VERIFY( std::ranges::crbegin(c) == std::ranges::rbegin(c) );
-  VERIFY( std::ranges::crbegin(std::move(c)) == std::ranges::rbegin(std::move(c)) );
+
+  VERIFY( std::ranges::crbegin(std::move(r)) == std::ranges::rbegin(c) );
+  VERIFY( std::ranges::crbegin(std::move(c)) == std::ranges::rbegin(c) );
 }
 
 int
diff --git a/libstdc++-v3/testsuite/std/ranges/access/crend.cc b/libstdc++-v3/testsuite/std/ranges/access/crend.cc
index e56491973b2..bc4d0d2949c 100644
--- a/libstdc++-v3/testsuite/std/ranges/access/crend.cc
+++ b/libstdc++-v3/testsuite/std/ranges/access/crend.cc
@@ -33,15 +33,18 @@  struct R1
   friend constexpr const int* rend(const R1&& r) { return &r.j + 1; }
 };
 
+// N.B. this is a lie, rend on an R1 rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<R1> = true;
+
 void
 test01()
 {
   R1 r;
   const R1& c = r;
   VERIFY( std::ranges::crend(r) == std::ranges::rend(c) );
-  VERIFY( std::ranges::crend(std::move(r)) == std::ranges::rend(std::move(c)) );
   VERIFY( std::ranges::crend(c) == std::ranges::rend(c) );
-  VERIFY( std::ranges::crend(std::move(c)) == std::ranges::rend(std::move(c)) );
+  VERIFY( std::ranges::crend(std::move(r)) == std::ranges::rend(c) );
+  VERIFY( std::ranges::crend(std::move(c)) == std::ranges::rend(c) );
 }
 
 struct R2
@@ -56,14 +59,17 @@  struct R2
   friend const long* end(const R2&& r) { return r.l + 2; }
 };
 
+// N.B. this is a lie, rend on an R2 rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<R2> = true;
+
 void
 test02()
 {
   R2 r;
   const R2& c = r;
   VERIFY( std::ranges::crend(r) == std::ranges::rend(c) );
-  VERIFY( std::ranges::crend(std::move(r)) == std::ranges::rend(std::move(c)) );
   VERIFY( std::ranges::crend(c) == std::ranges::rend(c) );
+  VERIFY( std::ranges::crend(std::move(r)) == std::ranges::rend(std::move(c)) );
   VERIFY( std::ranges::crend(std::move(c)) == std::ranges::rend(std::move(c)) );
 }
 
@@ -78,6 +84,9 @@  struct R3
   friend const int* rend(const R3& r) { return &r.i; }
 };
 
+// N.B. this is a lie, rend on an R3 rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<R3> = true;
+
 void
 test03()
 {
diff --git a/libstdc++-v3/testsuite/std/ranges/access/data.cc b/libstdc++-v3/testsuite/std/ranges/access/data.cc
index 49321640182..1bd4e06dd0e 100644
--- a/libstdc++-v3/testsuite/std/ranges/access/data.cc
+++ b/libstdc++-v3/testsuite/std/ranges/access/data.cc
@@ -56,15 +56,20 @@  struct R3
   long l = 0;
 
   int* data() const { return nullptr; }
-  friend long* begin(R3&& r) { return &r.l; }
-  friend const long* begin(const R3&& r) { return &r.l + 1; }
+  friend long* begin(R3& r) { return &r.l; }
+  friend const long* begin(const R3& r) { return &r.l + 1; }
 };
 
+// N.B. this is a lie, begin on an R3 rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<R3> = true;
+
 void
 test03()
 {
   R3 r;
   const R3& c = r;
+  // r.data() can only be used on an lvalue, but ranges::begin(R3&&) is OK
+  // because R3 satisfies ranges::safe_range.
   VERIFY( std::ranges::data(std::move(r)) == std::to_address(std::ranges::begin(std::move(r))) );
   VERIFY( std::ranges::data(std::move(c)) == std::to_address(std::ranges::begin(std::move(c))) );
 }
diff --git a/libstdc++-v3/testsuite/std/ranges/access/end.cc b/libstdc++-v3/testsuite/std/ranges/access/end.cc
index ed269c5433f..eaa13d031b3 100644
--- a/libstdc++-v3/testsuite/std/ranges/access/end.cc
+++ b/libstdc++-v3/testsuite/std/ranges/access/end.cc
@@ -74,6 +74,19 @@  struct R
   friend const int* end(const R&& r) noexcept { return r.a + 3; }
 };
 
+struct RV // view on an R
+{
+  R& r;
+
+  const int* begin() const;
+
+  friend int* end(RV& v) noexcept { return end(v.r); }
+  friend const int* end(const RV& v) { return end(std::as_const(v.r)); }
+};
+
+// Allow ranges::begin to work with RV&&
+template<> constexpr bool std::ranges::enable_safe_range<RV> = true;
+
 void
 test03()
 {
@@ -84,20 +97,21 @@  test03()
   static_assert(!noexcept(std::ranges::end(r)));
   VERIFY( std::ranges::end(r) == end(r) );
 
-  static_assert(same_as<decltype(std::ranges::end(std::move(r))),
-		decltype(end(std::move(r)))>);
-  static_assert(!noexcept(std::ranges::end(std::move(r))));
-  VERIFY( std::ranges::end(std::move(r)) == end(std::move(r)) );
-
-
   static_assert(same_as<decltype(std::ranges::end(c)), decltype(end(c))>);
   static_assert(noexcept(std::ranges::end(c)));
   VERIFY( std::ranges::end(c) == end(c) );
 
-  static_assert(same_as<decltype(std::ranges::end(std::move(c))),
-		decltype(end(std::move(c)))>);
-  static_assert(noexcept(std::ranges::end(std::move(c))));
-  VERIFY( std::ranges::end(std::move(c)) == end(std::move(c)) );
+  RV v{r};
+  static_assert(same_as<decltype(std::ranges::end(std::move(v))),
+		decltype(end(r))>);
+  static_assert(noexcept(std::ranges::end(std::move(v))));
+  VERIFY( std::ranges::end(std::move(v)) == end(r) );
+
+  const RV cv{r};
+  static_assert(same_as<decltype(std::ranges::end(std::move(cv))),
+		decltype(end(c))>);
+  static_assert(!noexcept(std::ranges::end(std::move(cv))));
+  VERIFY( std::ranges::end(std::move(cv)) == end(c) );
 }
 
 struct RR
@@ -123,6 +137,9 @@  struct RR
   friend const int* end(const RR&& r) noexcept { return r.a + 3; }
 };
 
+// N.B. this is a lie, end on an RR rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<RR> = true;
+
 void
 test04()
 {
@@ -131,14 +148,14 @@  test04()
   VERIFY( std::ranges::end(r) == &r.s );
   static_assert(noexcept(std::ranges::end(r)));
 
-  VERIFY( std::ranges::end(std::move(r)) == r.a + 1 );
-  static_assert(!noexcept(std::ranges::end(std::move(r))));
+  VERIFY( std::ranges::end(std::move(r)) == &r.s );
+  static_assert(noexcept(std::ranges::end(std::move(r))));
 
   VERIFY( std::ranges::end(c) == &r.l );
   static_assert(!noexcept(std::ranges::end(c)));
 
-  VERIFY( std::ranges::end(std::move(c)) == r.a + 3 );
-  static_assert(noexcept(std::ranges::end(std::move(c))));
+  VERIFY( std::ranges::end(std::move(c)) == &r.l );
+  static_assert(!noexcept(std::ranges::end(std::move(c))));
 }
 
 int
diff --git a/libstdc++-v3/testsuite/std/ranges/access/rbegin.cc b/libstdc++-v3/testsuite/std/ranges/access/rbegin.cc
index 067ddd7ced6..aecbc2bb8af 100644
--- a/libstdc++-v3/testsuite/std/ranges/access/rbegin.cc
+++ b/libstdc++-v3/testsuite/std/ranges/access/rbegin.cc
@@ -31,26 +31,31 @@  struct R1
   friend constexpr const int* rbegin(const R1&& r) { return &r.j; }
 };
 
+// N.B. this is a lie, rbegin on an R1 rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<R1> = true;
+
 void
 test01()
 {
   constexpr R1 r;
   static_assert( std::ranges::rbegin(r) == &r.i );
-  static_assert( std::ranges::rbegin(std::move(r)) == &r.j );
+  static_assert( std::ranges::rbegin(std::move(r)) == &r.i );
 }
 
 struct R2
 {
   int a[2] = { };
-  long l[2] = { };
 
   constexpr const int* begin() const { return a; }
   constexpr const int* end() const { return a + 2; }
 
-  friend constexpr const long* begin(const R2&& r) { return r.l; }
-  friend constexpr const long* end(const R2&& r) { return r.l + 2; }
+  friend constexpr const long* begin(const R2&&); // not defined
+  friend constexpr const long* end(const R2&&); // not defined
 };
 
+// N.B. this is a lie, begin/end on an R2 rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<R2> = true;
+
 void
 test02()
 {
diff --git a/libstdc++-v3/testsuite/std/ranges/access/rend.cc b/libstdc++-v3/testsuite/std/ranges/access/rend.cc
index 17caa9fb31a..b447e97b27c 100644
--- a/libstdc++-v3/testsuite/std/ranges/access/rend.cc
+++ b/libstdc++-v3/testsuite/std/ranges/access/rend.cc
@@ -29,16 +29,19 @@  struct R1
 
   constexpr const int* rbegin() const { return &i; }
   constexpr const int* rend() const { return &i + 1; }
-  friend constexpr const int* rbegin(const R1&& r) { return &r.j; }
-  friend constexpr const int* rend(const R1&& r) { return &r.j + 1; }
+  friend constexpr const int* rbegin(const R1&&); // not defined
+  friend constexpr const int* rend(const R1&&); // not defined
 };
 
+// N.B. this is a lie, rend on an R1 rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<R1> = true;
+
 void
 test01()
 {
   constexpr R1 r;
   static_assert( std::ranges::rend(r) == &r.i + 1 );
-  static_assert( std::ranges::rend(std::move(r)) == &r.j + 1 );
+  static_assert( std::ranges::rend(std::move(r)) == &r.i + 1 );
 }
 
 struct R2
@@ -53,6 +56,9 @@  struct R2
   friend constexpr const long* end(const R2&& r) { return r.l + 2; }
 };
 
+// N.B. this is a lie, begin/end on an R2 rvalue will return a dangling pointer.
+template<> constexpr bool std::ranges::enable_safe_range<R2> = true;
+
 void
 test02()
 {
diff --git a/libstdc++-v3/testsuite/std/ranges/empty_view.cc b/libstdc++-v3/testsuite/std/ranges/empty_view.cc
index 6524b37d699..b23e51fde55 100644
--- a/libstdc++-v3/testsuite/std/ranges/empty_view.cc
+++ b/libstdc++-v3/testsuite/std/ranges/empty_view.cc
@@ -31,5 +31,5 @@  static_assert(e.end() == nullptr);
 static_assert(e.data() == nullptr);
 static_assert(e.empty());
 
-static_assert(begin(e) == nullptr);
-static_assert(end(e) == nullptr);
+static_assert(std::ranges::begin(e) == nullptr);
+static_assert(std::ranges::end(e) == nullptr);
diff --git a/libstdc++-v3/testsuite/std/ranges/safe_range.cc b/libstdc++-v3/testsuite/std/ranges/safe_range.cc
new file mode 100644
index 00000000000..0cf30856d15
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/ranges/safe_range.cc
@@ -0,0 +1,41 @@ 
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++2a" }
+// { dg-do compile { target c++2a } }
+
+#include <ranges>
+#include <testsuite_iterators.h>
+
+static_assert( std::ranges::safe_range<int(&)[1]> );
+static_assert( std::ranges::safe_range<const int(&)[1]> );
+static_assert( !std::ranges::safe_range<int[1]> );
+static_assert( !std::ranges::safe_range<int*> );
+
+using __gnu_test::test_contiguous_range;
+
+static_assert( !std::ranges::safe_range<test_contiguous_range<int>> );
+static_assert( std::ranges::safe_range<test_contiguous_range<int>&> );
+static_assert( !std::ranges::safe_range<test_contiguous_range<int>&&> );
+
+template<>
+constexpr bool
+  std::ranges::enable_safe_range<test_contiguous_range<long>> = true;
+
+static_assert( std::ranges::safe_range<test_contiguous_range<long>> );
+static_assert( std::ranges::safe_range<test_contiguous_range<long>&> );
+static_assert( std::ranges::safe_range<test_contiguous_range<long>&&> );
diff --git a/libstdc++-v3/testsuite/std/ranges/safe_range_types.cc b/libstdc++-v3/testsuite/std/ranges/safe_range_types.cc
new file mode 100644
index 00000000000..e730ec0a530
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/ranges/safe_range_types.cc
@@ -0,0 +1,59 @@ 
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++2a" }
+// { dg-do compile { target c++2a } }
+
+#include <ranges>
+#include <span>
+#include <string>
+#include <experimental/string_view>
+
+template<typename T>
+constexpr bool
+rvalue_is_safe_range()
+{
+  using std::ranges::safe_range;
+
+  // An lvalue range always models safe_range
+  static_assert( safe_range<T&> );
+  static_assert( safe_range<const T&> );
+
+  // Result should not depend on addition of const or rvalue-reference.
+  static_assert( safe_range<T&&> == safe_range<T> );
+  static_assert( safe_range<const T> == safe_range<T> );
+  static_assert( safe_range<const T&&> == safe_range<T> );
+
+  return std::ranges::safe_range<T>;
+}
+
+static_assert( rvalue_is_safe_range<std::ranges::subrange<int*, int*>>() );
+static_assert( rvalue_is_safe_range<std::ranges::empty_view<int>>() );
+static_assert( rvalue_is_safe_range<std::ranges::iota_view<int>>() );
+static_assert( rvalue_is_safe_range<std::ranges::iota_view<int, int>>() );
+
+static_assert( rvalue_is_safe_range<std::span<int>>() );
+static_assert( rvalue_is_safe_range<std::span<int, 99>>() );
+
+static_assert( ! rvalue_is_safe_range<std::string>() );
+static_assert( ! rvalue_is_safe_range<std::wstring>() );
+
+static_assert( rvalue_is_safe_range<std::string_view>() );
+static_assert( rvalue_is_safe_range<std::wstring_view>() );
+
+static_assert( rvalue_is_safe_range<std::experimental::string_view>() );
+static_assert( rvalue_is_safe_range<std::experimental::wstring_view>() );
diff --git a/libstdc++-v3/testsuite/util/testsuite_iterators.h b/libstdc++-v3/testsuite/util/testsuite_iterators.h
index 13993a4209b..3dac911129a 100644
--- a/libstdc++-v3/testsuite/util/testsuite_iterators.h
+++ b/libstdc++-v3/testsuite/util/testsuite_iterators.h
@@ -758,6 +758,11 @@  namespace __gnu_test
   template<typename T>
     using test_output_sized_range
       = test_sized_range<T, output_iterator_wrapper>;
+
+// test_container, test_range and test_sized_range do not own their elements,
+// so they all model std::ranges::safe_range. This file does not define
+// specializations of std::ranges::enable_safe_range, so that individual
+// test can decide whether or not to do so.
 #endif // C++20
 } // namespace __gnu_test
 #endif // _TESTSUITE_ITERATORS