diff mbox series

libstdc++: Implement P3138R5 views::cache_latest

Message ID 20250211055734.2852490-1-ppalka@redhat.com
State New
Headers show
Series libstdc++: Implement P3138R5 views::cache_latest | expand

Commit Message

Patrick Palka Feb. 11, 2025, 5:57 a.m. UTC
Tested on x86_64-pc-linux-gnu, does this look OK for trunk?

-- >8 --

libstdc++-v3/ChangeLog:

	* include/bits/version.def (ranges_cache_latest): Define.
	* include/bits/version.h: Regenerate.
	* include/std/ranges (cache_latest_view): Define for C++26.
	(cache_latest_view::_Iterator): Likewise.
	(cache_latest_view::_Sentinel): Likewise.
	(views::__detail::__can_cache_latest): Likewise.
	(views::_CacheLatest, views::cache_latest): Likewise.
	* testsuite/std/ranges/adaptors/cache_latest/1.cc: New test.
---
 libstdc++-v3/include/bits/version.def |   8 ++
 libstdc++-v3/include/bits/version.h   |  10 ++
 libstdc++-v3/include/std/ranges       | 189 ++++++++++++++++++++++++++
 3 files changed, 207 insertions(+)

Comments

Jonathan Wakely Feb. 13, 2025, 11:12 a.m. UTC | #1
On Tue, 11 Feb 2025 at 05:59, Patrick Palka <ppalka@redhat.com> wrote:
>
> Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
>
> -- >8 --
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/version.def (ranges_cache_latest): Define.
>         * include/bits/version.h: Regenerate.
>         * include/std/ranges (cache_latest_view): Define for C++26.
>         (cache_latest_view::_Iterator): Likewise.
>         (cache_latest_view::_Sentinel): Likewise.
>         (views::__detail::__can_cache_latest): Likewise.
>         (views::_CacheLatest, views::cache_latest): Likewise.
>         * testsuite/std/ranges/adaptors/cache_latest/1.cc: New test.

The test is missing from the patch.

> ---
>  libstdc++-v3/include/bits/version.def |   8 ++
>  libstdc++-v3/include/bits/version.h   |  10 ++
>  libstdc++-v3/include/std/ranges       | 189 ++++++++++++++++++++++++++
>  3 files changed, 207 insertions(+)
>
> diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
> index 002e560dc0d..6fb5db2e1fc 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -1837,6 +1837,14 @@ ftms = {
>    };
>  };
>
> +ftms = {
> +  name = ranges_cache_latest;
> +  values = {
> +    v = 202411;
> +    cxxmin = 26;
> +  };
> +};
> +
>  ftms = {
>    name = ranges_concat;
>    values = {
> diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
> index 70de189b1e0..db61a396c45 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -2035,6 +2035,16 @@
>  #endif /* !defined(__cpp_lib_is_virtual_base_of) && defined(__glibcxx_want_is_virtual_base_of) */
>  #undef __glibcxx_want_is_virtual_base_of
>
> +#if !defined(__cpp_lib_ranges_cache_latest)
> +# if (__cplusplus >  202302L)
> +#  define __glibcxx_ranges_cache_latest 202411L
> +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_cache_latest)
> +#   define __cpp_lib_ranges_cache_latest 202411L
> +#  endif
> +# endif
> +#endif /* !defined(__cpp_lib_ranges_cache_latest) && defined(__glibcxx_want_ranges_cache_latest) */
> +#undef __glibcxx_want_ranges_cache_latest
> +
>  #if !defined(__cpp_lib_ranges_concat)
>  # if (__cplusplus >  202302L)
>  #  define __glibcxx_ranges_concat 202403L
> diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
> index 5c795a90fbc..db9a00be264 100644
> --- a/libstdc++-v3/include/std/ranges
> +++ b/libstdc++-v3/include/std/ranges
> @@ -58,6 +58,7 @@
>  #define __glibcxx_want_ranges_as_const
>  #define __glibcxx_want_ranges_as_rvalue
>  #define __glibcxx_want_ranges_cartesian_product
> +#define __glibcxx_want_ranges_cache_latest
>  #define __glibcxx_want_ranges_concat
>  #define __glibcxx_want_ranges_chunk
>  #define __glibcxx_want_ranges_chunk_by
> @@ -1534,6 +1535,8 @@ namespace views::__adaptor
>             this->_M_payload._M_apply(_Optional_func{__f}, __i);
>             return this->_M_get();
>           }
> +
> +       using _Optional_base<_Tp>::_M_reset;
>        };
>
>      template<range _Range>
> @@ -10203,6 +10206,192 @@ namespace ranges
>  } // namespace ranges
>  #endif // __cpp_lib_ranges_concat
>
> +#if __cpp_lib_ranges_cache_latest // C++ >= 26
> +namespace ranges
> +{
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  class cache_latest_view : public view_interface<cache_latest_view<_Vp>>
> +  {
> +    _Vp _M_base = _Vp();
> +
> +    using __cache_t = conditional_t<is_reference_v<range_reference_t<_Vp>>,
> +                                   add_pointer_t<range_reference_t<_Vp>>,
> +                                   range_reference_t<_Vp>>;

__conditional_t is cheaper to instantiate than conditional_t, so when
it doesn't affect the mangled name of a public symbol we should prefer
__conditional_t.

> +    __detail::__non_propagating_cache<__cache_t> _M_cache;
> +
> +    class _Iterator;
> +    class _Sentinel;
> +
> +  public:
> +    cache_latest_view() requires default_initializable<_Vp> = default;
> +
> +    constexpr explicit
> +    cache_latest_view(_Vp __base)
> +    : _M_base(std::move(__base))
> +    { }
> +
> +    constexpr _Vp
> +    base() const & requires copy_constructible<_Vp>
> +    { return _M_base; }
> +
> +    constexpr _Vp
> +    base() &&
> +    { return std::move(_M_base); }
> +
> +    constexpr auto
> +    begin()
> +    { return _Iterator(*this); }
> +
> +    constexpr auto
> +    end()
> +    { return _Sentinel(*this); }
> +
> +    constexpr auto
> +    size() requires sized_range<_Vp>
> +    { return ranges::size(_M_base); }
> +
> +    constexpr auto
> +    size() const requires sized_range<const _Vp>
> +    { return ranges::size(_M_base); }
> +  };
> +
> +  template<typename _Range>
> +    cache_latest_view(_Range&&) -> cache_latest_view<views::all_t<_Range>>;
> +
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  class cache_latest_view<_Vp>::_Iterator
> +  {
> +    cache_latest_view* _M_parent;
> +    iterator_t<_Vp> _M_current;
> +
> +    constexpr explicit
> +    _Iterator(cache_latest_view& __parent)
> +    : _M_parent(std::__addressof(__parent)),
> +      _M_current(ranges::begin(__parent._M_base))
> +    { }
> +
> +    friend class cache_latest_view;
> +
> +  public:
> +    using difference_type = range_difference_t<_Vp>;
> +    using value_type = range_value_t<_Vp>;
> +    using iterator_concept = input_iterator_tag;
> +
> +    _Iterator(_Iterator&&) = default;
> +
> +    _Iterator&
> +    operator=(_Iterator&&) = default;
> +
> +    constexpr iterator_t<_Vp>
> +    base() &&
> +    { return std::move(_M_current); }
> +
> +    constexpr const iterator_t<_Vp>&
> +    base() const & noexcept
> +    { return _M_current; }
> +
> +    constexpr range_reference_t<_Vp>&
> +    operator*() const
> +    {
> +      if constexpr (is_reference_v<range_reference_t<_Vp>>)
> +       {
> +         if (!_M_parent->_M_cache)
> +           _M_parent->_M_cache = std::__addressof(__detail::__as_lvalue(*_M_current));
> +         return **_M_parent->_M_cache;
> +       }
> +      else
> +       {
> +         if (!_M_parent->_M_cache)
> +           _M_parent->_M_cache._M_emplace_deref(_M_current);
> +         return *_M_parent->_M_cache;
> +       }
> +    }
> +
> +    constexpr _Iterator&
> +    operator++()
> +    {
> +      _M_parent->_M_cache._M_reset();
> +      ++_M_current;
> +      return *this;
> +    }
> +
> +    constexpr void
> +    operator++(int)
> +    { ++*this; }
> +
> +    friend constexpr range_rvalue_reference_t<_Vp>
> +    iter_move(const _Iterator& __i)
> +      noexcept(noexcept(ranges::iter_move(__i._M_current)))
> +    { return ranges::iter_move(__i._M_current); }
> +
> +    friend constexpr void
> +    iter_swap(const _Iterator& __x, const _Iterator& __y)
> +      noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
> +      requires indirectly_swappable<iterator_t<_Vp>>
> +    { ranges::iter_swap(__x._M_current, __y._M_current); }
> +  };
> +
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  class cache_latest_view<_Vp>::_Sentinel
> +  {
> +    sentinel_t<_Vp> _M_end = sentinel_t<_Vp>();
> +
> +    constexpr explicit
> +    _Sentinel(cache_latest_view& parent)
> +    : _M_end(ranges::end(parent._M_base))
> +    { }
> +
> +    friend class cache_latest_view;
> +
> +  public:
> +    _Sentinel() = default;
> +
> +    constexpr sentinel_t<_Vp>
> +    base() const
> +    { return _M_end; }
> +
> +    friend constexpr bool
> +    operator==(const _Iterator& __x, const _Sentinel& __y)
> +    { return __x._M_current == __y._M_end; }
> +
> +    friend constexpr range_difference_t<_Vp>
> +    operator-(const _Iterator& __x, const _Sentinel& __y)
> +      requires sized_sentinel_for<sentinel_t<_Vp>, iterator_t<_Vp>>
> +    { return __x._M_current - __y._M_end; }
> +
> +    friend constexpr range_difference_t<_Vp>
> +    operator-(const _Sentinel& __x, const _Iterator& __y)
> +      requires sized_sentinel_for<sentinel_t<_Vp>, iterator_t<_Vp>>
> +    { return __x._M_end - __y._M_current; }
> +  };
> +
> +  namespace views
> +  {
> +    namespace __detail
> +    {
> +      template<typename _Tp>
> +       concept __can_cache_latest = requires { cache_latest_view(std::declval<_Tp>()); };
> +    }
> +
> +    struct _CacheLatest : __adaptor::_RangeAdaptorClosure<_CacheLatest>
> +    {
> +      template<viewable_range _Range>
> +       requires __detail::__can_cache_latest<_Range>
> +       constexpr auto
> +       operator() [[nodiscard]] (_Range&& __r) const
> +       { return cache_latest_view(std::forward<_Range>(__r)); }
> +
> +      static constexpr bool _S_has_simple_call_op = true;
> +    };
> +
> +    inline constexpr _CacheLatest cache_latest;
> +  }
> +} // namespace ranges
> +#endif // __cpp_lib_ranges_cache_latest
> +
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace std
>  #endif // library concepts
> --
> 2.48.1.291.g388218fac7.dirty
>
Patrick Palka Feb. 14, 2025, 5:12 a.m. UTC | #2
On Thu, 13 Feb 2025, Jonathan Wakely wrote:

> On Tue, 11 Feb 2025 at 05:59, Patrick Palka <ppalka@redhat.com> wrote:
> >
> > Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
> >
> > -- >8 --
> >
> > libstdc++-v3/ChangeLog:
> >
> >         * include/bits/version.def (ranges_cache_latest): Define.
> >         * include/bits/version.h: Regenerate.
> >         * include/std/ranges (cache_latest_view): Define for C++26.
> >         (cache_latest_view::_Iterator): Likewise.
> >         (cache_latest_view::_Sentinel): Likewise.
> >         (views::__detail::__can_cache_latest): Likewise.
> >         (views::_CacheLatest, views::cache_latest): Likewise.
> >         * testsuite/std/ranges/adaptors/cache_latest/1.cc: New test.
> 
> The test is missing from the patch.

Whoops, below is the complete patch.

> 
> > ---
> >  libstdc++-v3/include/bits/version.def |   8 ++
> >  libstdc++-v3/include/bits/version.h   |  10 ++
> >  libstdc++-v3/include/std/ranges       | 189 ++++++++++++++++++++++++++
> >  3 files changed, 207 insertions(+)
> >
> > diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
> > index 002e560dc0d..6fb5db2e1fc 100644
> > --- a/libstdc++-v3/include/bits/version.def
> > +++ b/libstdc++-v3/include/bits/version.def
> > @@ -1837,6 +1837,14 @@ ftms = {
> >    };
> >  };
> >
> > +ftms = {
> > +  name = ranges_cache_latest;
> > +  values = {
> > +    v = 202411;
> > +    cxxmin = 26;
> > +  };
> > +};
> > +
> >  ftms = {
> >    name = ranges_concat;
> >    values = {
> > diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
> > index 70de189b1e0..db61a396c45 100644
> > --- a/libstdc++-v3/include/bits/version.h
> > +++ b/libstdc++-v3/include/bits/version.h
> > @@ -2035,6 +2035,16 @@
> >  #endif /* !defined(__cpp_lib_is_virtual_base_of) && defined(__glibcxx_want_is_virtual_base_of) */
> >  #undef __glibcxx_want_is_virtual_base_of
> >
> > +#if !defined(__cpp_lib_ranges_cache_latest)
> > +# if (__cplusplus >  202302L)
> > +#  define __glibcxx_ranges_cache_latest 202411L
> > +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_cache_latest)
> > +#   define __cpp_lib_ranges_cache_latest 202411L
> > +#  endif
> > +# endif
> > +#endif /* !defined(__cpp_lib_ranges_cache_latest) && defined(__glibcxx_want_ranges_cache_latest) */
> > +#undef __glibcxx_want_ranges_cache_latest
> > +
> >  #if !defined(__cpp_lib_ranges_concat)
> >  # if (__cplusplus >  202302L)
> >  #  define __glibcxx_ranges_concat 202403L
> > diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
> > index 5c795a90fbc..db9a00be264 100644
> > --- a/libstdc++-v3/include/std/ranges
> > +++ b/libstdc++-v3/include/std/ranges
> > @@ -58,6 +58,7 @@
> >  #define __glibcxx_want_ranges_as_const
> >  #define __glibcxx_want_ranges_as_rvalue
> >  #define __glibcxx_want_ranges_cartesian_product
> > +#define __glibcxx_want_ranges_cache_latest
> >  #define __glibcxx_want_ranges_concat
> >  #define __glibcxx_want_ranges_chunk
> >  #define __glibcxx_want_ranges_chunk_by
> > @@ -1534,6 +1535,8 @@ namespace views::__adaptor
> >             this->_M_payload._M_apply(_Optional_func{__f}, __i);
> >             return this->_M_get();
> >           }
> > +
> > +       using _Optional_base<_Tp>::_M_reset;

I also forgot to mention this change in the ChangeLog.

> >        };
> >
> >      template<range _Range>
> > @@ -10203,6 +10206,192 @@ namespace ranges
> >  } // namespace ranges
> >  #endif // __cpp_lib_ranges_concat
> >
> > +#if __cpp_lib_ranges_cache_latest // C++ >= 26
> > +namespace ranges
> > +{
> > +  template<input_range _Vp>
> > +    requires view<_Vp>
> > +  class cache_latest_view : public view_interface<cache_latest_view<_Vp>>
> > +  {
> > +    _Vp _M_base = _Vp();
> > +
> > +    using __cache_t = conditional_t<is_reference_v<range_reference_t<_Vp>>,
> > +                                   add_pointer_t<range_reference_t<_Vp>>,
> > +                                   range_reference_t<_Vp>>;
> 
> __conditional_t is cheaper to instantiate than conditional_t, so when
> it doesn't affect the mangled name of a public symbol we should prefer
> __conditional_t.

Ack, fixed below.

-- >8 --

libstdc++-v3/ChangeLog:

	* include/bits/version.def (ranges_cache_latest): Define.
	* include/bits/version.h: Regenerate.
	* include/std/ranges (__detail::__non_propagating_cache::_M_reset):
	Export from base class _Optional_base.
	(cache_latest_view): Define for C++26.
	(cache_latest_view::_Iterator): Likewise.
	(cache_latest_view::_Sentinel): Likewise.
	(views::__detail::__can_cache_latest): Likewise.
	(views::_CacheLatest, views::cache_latest): Likewise.
	* testsuite/std/ranges/adaptors/cache_latest/1.cc: New test.
---
 libstdc++-v3/include/bits/version.def         |   8 +
 libstdc++-v3/include/bits/version.h           |  10 +
 libstdc++-v3/include/std/ranges               | 189 ++++++++++++++++++
 .../std/ranges/adaptors/cache_latest/1.cc     |  72 +++++++
 4 files changed, 279 insertions(+)
 create mode 100644 libstdc++-v3/testsuite/std/ranges/adaptors/cache_latest/1.cc

diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 002e560dc0d..6fb5db2e1fc 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1837,6 +1837,14 @@ ftms = {
   };
 };
 
+ftms = {
+  name = ranges_cache_latest;
+  values = {
+    v = 202411;
+    cxxmin = 26;
+  };
+};
+
 ftms = {
   name = ranges_concat;
   values = {
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 70de189b1e0..db61a396c45 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -2035,6 +2035,16 @@
 #endif /* !defined(__cpp_lib_is_virtual_base_of) && defined(__glibcxx_want_is_virtual_base_of) */
 #undef __glibcxx_want_is_virtual_base_of
 
+#if !defined(__cpp_lib_ranges_cache_latest)
+# if (__cplusplus >  202302L)
+#  define __glibcxx_ranges_cache_latest 202411L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_cache_latest)
+#   define __cpp_lib_ranges_cache_latest 202411L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_ranges_cache_latest) && defined(__glibcxx_want_ranges_cache_latest) */
+#undef __glibcxx_want_ranges_cache_latest
+
 #if !defined(__cpp_lib_ranges_concat)
 # if (__cplusplus >  202302L)
 #  define __glibcxx_ranges_concat 202403L
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index 5c795a90fbc..07ec90248a6 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -57,6 +57,7 @@
 #define __glibcxx_want_ranges
 #define __glibcxx_want_ranges_as_const
 #define __glibcxx_want_ranges_as_rvalue
+#define __glibcxx_want_ranges_cache_latest
 #define __glibcxx_want_ranges_cartesian_product
 #define __glibcxx_want_ranges_concat
 #define __glibcxx_want_ranges_chunk
@@ -1534,6 +1535,8 @@ namespace views::__adaptor
 	    this->_M_payload._M_apply(_Optional_func{__f}, __i);
 	    return this->_M_get();
 	  }
+
+	using _Optional_base<_Tp>::_M_reset;
       };
 
     template<range _Range>
@@ -10203,6 +10206,192 @@ namespace ranges
 } // namespace ranges
 #endif // __cpp_lib_ranges_concat
 
+#if __cpp_lib_ranges_cache_latest // C++ >= 26
+namespace ranges
+{
+  template<input_range _Vp>
+    requires view<_Vp>
+  class cache_latest_view : public view_interface<cache_latest_view<_Vp>>
+  {
+    _Vp _M_base = _Vp();
+
+    using __cache_t = __conditional_t<is_reference_v<range_reference_t<_Vp>>,
+				      add_pointer_t<range_reference_t<_Vp>>,
+				      range_reference_t<_Vp>>;
+    __detail::__non_propagating_cache<__cache_t> _M_cache;
+
+    class _Iterator;
+    class _Sentinel;
+
+  public:
+    cache_latest_view() requires default_initializable<_Vp> = default;
+
+    constexpr explicit
+    cache_latest_view(_Vp __base)
+    : _M_base(std::move(__base))
+    { }
+
+    constexpr _Vp
+    base() const & requires copy_constructible<_Vp>
+    { return _M_base; }
+
+    constexpr _Vp
+    base() &&
+    { return std::move(_M_base); }
+
+    constexpr auto
+    begin()
+    { return _Iterator(*this); }
+
+    constexpr auto
+    end()
+    { return _Sentinel(*this); }
+
+    constexpr auto
+    size() requires sized_range<_Vp>
+    { return ranges::size(_M_base); }
+
+    constexpr auto
+    size() const requires sized_range<const _Vp>
+    { return ranges::size(_M_base); }
+  };
+
+  template<typename _Range>
+    cache_latest_view(_Range&&) -> cache_latest_view<views::all_t<_Range>>;
+
+  template<input_range _Vp>
+    requires view<_Vp>
+  class cache_latest_view<_Vp>::_Iterator
+  {
+    cache_latest_view* _M_parent;
+    iterator_t<_Vp> _M_current;
+
+    constexpr explicit
+    _Iterator(cache_latest_view& __parent)
+    : _M_parent(std::__addressof(__parent)),
+      _M_current(ranges::begin(__parent._M_base))
+    { }
+
+    friend class cache_latest_view;
+
+  public:
+    using difference_type = range_difference_t<_Vp>;
+    using value_type = range_value_t<_Vp>;
+    using iterator_concept = input_iterator_tag;
+
+    _Iterator(_Iterator&&) = default;
+
+    _Iterator&
+    operator=(_Iterator&&) = default;
+
+    constexpr iterator_t<_Vp>
+    base() &&
+    { return std::move(_M_current); }
+
+    constexpr const iterator_t<_Vp>&
+    base() const & noexcept
+    { return _M_current; }
+
+    constexpr range_reference_t<_Vp>&
+    operator*() const
+    {
+      if constexpr (is_reference_v<range_reference_t<_Vp>>)
+	{
+	  if (!_M_parent->_M_cache)
+	    _M_parent->_M_cache = std::__addressof(__detail::__as_lvalue(*_M_current));
+	  return **_M_parent->_M_cache;
+	}
+      else
+	{
+	  if (!_M_parent->_M_cache)
+	    _M_parent->_M_cache._M_emplace_deref(_M_current);
+	  return *_M_parent->_M_cache;
+	}
+    }
+
+    constexpr _Iterator&
+    operator++()
+    {
+      _M_parent->_M_cache._M_reset();
+      ++_M_current;
+      return *this;
+    }
+
+    constexpr void
+    operator++(int)
+    { ++*this; }
+
+    friend constexpr range_rvalue_reference_t<_Vp>
+    iter_move(const _Iterator& __i)
+      noexcept(noexcept(ranges::iter_move(__i._M_current)))
+    { return ranges::iter_move(__i._M_current); }
+
+    friend constexpr void
+    iter_swap(const _Iterator& __x, const _Iterator& __y)
+      noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
+      requires indirectly_swappable<iterator_t<_Vp>>
+    { ranges::iter_swap(__x._M_current, __y._M_current); }
+  };
+
+  template<input_range _Vp>
+    requires view<_Vp>
+  class cache_latest_view<_Vp>::_Sentinel
+  {
+    sentinel_t<_Vp> _M_end = sentinel_t<_Vp>();
+
+    constexpr explicit
+    _Sentinel(cache_latest_view& __parent)
+    : _M_end(ranges::end(__parent._M_base))
+    { }
+
+    friend class cache_latest_view;
+
+  public:
+    _Sentinel() = default;
+
+    constexpr sentinel_t<_Vp>
+    base() const
+    { return _M_end; }
+
+    friend constexpr bool
+    operator==(const _Iterator& __x, const _Sentinel& __y)
+    { return __x._M_current == __y._M_end; }
+
+    friend constexpr range_difference_t<_Vp>
+    operator-(const _Iterator& __x, const _Sentinel& __y)
+      requires sized_sentinel_for<sentinel_t<_Vp>, iterator_t<_Vp>>
+    { return __x._M_current - __y._M_end; }
+
+    friend constexpr range_difference_t<_Vp>
+    operator-(const _Sentinel& __x, const _Iterator& __y)
+      requires sized_sentinel_for<sentinel_t<_Vp>, iterator_t<_Vp>>
+    { return __x._M_end - __y._M_current; }
+  };
+
+  namespace views
+  {
+    namespace __detail
+    {
+      template<typename _Tp>
+	concept __can_cache_latest = requires { cache_latest_view(std::declval<_Tp>()); };
+    }
+
+    struct _CacheLatest : __adaptor::_RangeAdaptorClosure<_CacheLatest>
+    {
+      template<viewable_range _Range>
+	requires __detail::__can_cache_latest<_Range>
+	constexpr auto
+	operator() [[nodiscard]] (_Range&& __r) const
+	{ return cache_latest_view(std::forward<_Range>(__r)); }
+
+      static constexpr bool _S_has_simple_call_op = true;
+    };
+
+    inline constexpr _CacheLatest cache_latest;
+  }
+} // namespace ranges
+#endif // __cpp_lib_ranges_cache_latest
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 #endif // library concepts
diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/cache_latest/1.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/cache_latest/1.cc
new file mode 100644
index 00000000000..5904831c4e0
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/ranges/adaptors/cache_latest/1.cc
@@ -0,0 +1,72 @@
+// { dg-do run { target c++26 } }
+
+#include <ranges>
+
+#if __cpp_lib_ranges_cache_latest != 202411L
+# error "Feature-test macro __cpp_lib_ranges_cache_latest has wrong value in <ranges>"
+#endif
+
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+namespace ranges = std::ranges;
+namespace views = std::views;
+
+constexpr bool
+test01()
+{
+  int xs[] = {1,2,3,4,5};
+  auto v = xs | views::cache_latest;
+  VERIFY( ranges::equal(v, xs) );
+  VERIFY( ranges::size(v) == 5 );
+
+  auto it = v.begin();
+  auto st = v.end();
+  VERIFY( st - it == 5 );
+  VERIFY( it - st == -5 );
+  it++;
+  VERIFY( st - it == 4 );
+  VERIFY( it - st == -4 );
+
+  auto jt = v.begin();
+  ranges::iter_swap(it, jt);
+  VERIFY( ranges::equal(xs, (int[]){2,1,3,4,5}) );
+  int n = ranges::iter_move(it);
+  VERIFY( n == 1 );
+  ranges::iter_swap(it, jt);
+
+  auto w = views::iota(1, 6) | views::cache_latest;
+  VERIFY( ranges::equal(w, xs) );
+
+  return true;
+}
+
+constexpr bool
+test02()
+{
+  // Motivating example from P3138R5
+  int xs[] = {1, 2, 3, 4, 5};
+  int transform_count = 0;
+  auto v = xs | views::transform([&](int i){ ++transform_count; return i * i; })
+	      | views::filter([](int i){ return i % 2 == 0; });
+  VERIFY( ranges::equal(v, (int[]){4, 16}) );
+  VERIFY( transform_count == 7 );
+
+  transform_count = 0;
+  auto w = xs | views::transform([&](int i){ ++transform_count; return i * i; })
+	      | views::cache_latest
+	      | views::filter([](int i){ return i % 2 == 0; });
+  VERIFY( ranges::equal(w, (int[]){4, 16}) );
+  VERIFY( transform_count == 5 );
+
+  return true;
+}
+
+int
+main()
+{
+  static_assert(test01());
+  static_assert(test02());
+  test01();
+  test02();
+}
Jonathan Wakely March 5, 2025, 3:47 p.m. UTC | #3
On Fri, 14 Feb 2025 at 05:12, Patrick Palka wrote:
>
> On Thu, 13 Feb 2025, Jonathan Wakely wrote:
>
> > On Tue, 11 Feb 2025 at 05:59, Patrick Palka <ppalka@redhat.com> wrote:
> > >
> > > Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
> > >
> > > -- >8 --
> > >
> > > libstdc++-v3/ChangeLog:
> > >
> > >         * include/bits/version.def (ranges_cache_latest): Define.
> > >         * include/bits/version.h: Regenerate.
> > >         * include/std/ranges (cache_latest_view): Define for C++26.
> > >         (cache_latest_view::_Iterator): Likewise.
> > >         (cache_latest_view::_Sentinel): Likewise.
> > >         (views::__detail::__can_cache_latest): Likewise.
> > >         (views::_CacheLatest, views::cache_latest): Likewise.
> > >         * testsuite/std/ranges/adaptors/cache_latest/1.cc: New test.
> >
> > The test is missing from the patch.
>
> Whoops, below is the complete patch.

OK for trunk, thanks.

>
> >
> > > ---
> > >  libstdc++-v3/include/bits/version.def |   8 ++
> > >  libstdc++-v3/include/bits/version.h   |  10 ++
> > >  libstdc++-v3/include/std/ranges       | 189 ++++++++++++++++++++++++++
> > >  3 files changed, 207 insertions(+)
> > >
> > > diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
> > > index 002e560dc0d..6fb5db2e1fc 100644
> > > --- a/libstdc++-v3/include/bits/version.def
> > > +++ b/libstdc++-v3/include/bits/version.def
> > > @@ -1837,6 +1837,14 @@ ftms = {
> > >    };
> > >  };
> > >
> > > +ftms = {
> > > +  name = ranges_cache_latest;
> > > +  values = {
> > > +    v = 202411;
> > > +    cxxmin = 26;
> > > +  };
> > > +};
> > > +
> > >  ftms = {
> > >    name = ranges_concat;
> > >    values = {
> > > diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
> > > index 70de189b1e0..db61a396c45 100644
> > > --- a/libstdc++-v3/include/bits/version.h
> > > +++ b/libstdc++-v3/include/bits/version.h
> > > @@ -2035,6 +2035,16 @@
> > >  #endif /* !defined(__cpp_lib_is_virtual_base_of) && defined(__glibcxx_want_is_virtual_base_of) */
> > >  #undef __glibcxx_want_is_virtual_base_of
> > >
> > > +#if !defined(__cpp_lib_ranges_cache_latest)
> > > +# if (__cplusplus >  202302L)
> > > +#  define __glibcxx_ranges_cache_latest 202411L
> > > +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_cache_latest)
> > > +#   define __cpp_lib_ranges_cache_latest 202411L
> > > +#  endif
> > > +# endif
> > > +#endif /* !defined(__cpp_lib_ranges_cache_latest) && defined(__glibcxx_want_ranges_cache_latest) */
> > > +#undef __glibcxx_want_ranges_cache_latest
> > > +
> > >  #if !defined(__cpp_lib_ranges_concat)
> > >  # if (__cplusplus >  202302L)
> > >  #  define __glibcxx_ranges_concat 202403L
> > > diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
> > > index 5c795a90fbc..db9a00be264 100644
> > > --- a/libstdc++-v3/include/std/ranges
> > > +++ b/libstdc++-v3/include/std/ranges
> > > @@ -58,6 +58,7 @@
> > >  #define __glibcxx_want_ranges_as_const
> > >  #define __glibcxx_want_ranges_as_rvalue
> > >  #define __glibcxx_want_ranges_cartesian_product
> > > +#define __glibcxx_want_ranges_cache_latest
> > >  #define __glibcxx_want_ranges_concat
> > >  #define __glibcxx_want_ranges_chunk
> > >  #define __glibcxx_want_ranges_chunk_by
> > > @@ -1534,6 +1535,8 @@ namespace views::__adaptor
> > >             this->_M_payload._M_apply(_Optional_func{__f}, __i);
> > >             return this->_M_get();
> > >           }
> > > +
> > > +       using _Optional_base<_Tp>::_M_reset;
>
> I also forgot to mention this change in the ChangeLog.
>
> > >        };
> > >
> > >      template<range _Range>
> > > @@ -10203,6 +10206,192 @@ namespace ranges
> > >  } // namespace ranges
> > >  #endif // __cpp_lib_ranges_concat
> > >
> > > +#if __cpp_lib_ranges_cache_latest // C++ >= 26
> > > +namespace ranges
> > > +{
> > > +  template<input_range _Vp>
> > > +    requires view<_Vp>
> > > +  class cache_latest_view : public view_interface<cache_latest_view<_Vp>>
> > > +  {
> > > +    _Vp _M_base = _Vp();
> > > +
> > > +    using __cache_t = conditional_t<is_reference_v<range_reference_t<_Vp>>,
> > > +                                   add_pointer_t<range_reference_t<_Vp>>,
> > > +                                   range_reference_t<_Vp>>;
> >
> > __conditional_t is cheaper to instantiate than conditional_t, so when
> > it doesn't affect the mangled name of a public symbol we should prefer
> > __conditional_t.
>
> Ack, fixed below.
>
> -- >8 --
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/version.def (ranges_cache_latest): Define.
>         * include/bits/version.h: Regenerate.
>         * include/std/ranges (__detail::__non_propagating_cache::_M_reset):
>         Export from base class _Optional_base.
>         (cache_latest_view): Define for C++26.
>         (cache_latest_view::_Iterator): Likewise.
>         (cache_latest_view::_Sentinel): Likewise.
>         (views::__detail::__can_cache_latest): Likewise.
>         (views::_CacheLatest, views::cache_latest): Likewise.
>         * testsuite/std/ranges/adaptors/cache_latest/1.cc: New test.
> ---
>  libstdc++-v3/include/bits/version.def         |   8 +
>  libstdc++-v3/include/bits/version.h           |  10 +
>  libstdc++-v3/include/std/ranges               | 189 ++++++++++++++++++
>  .../std/ranges/adaptors/cache_latest/1.cc     |  72 +++++++
>  4 files changed, 279 insertions(+)
>  create mode 100644 libstdc++-v3/testsuite/std/ranges/adaptors/cache_latest/1.cc
>
> diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
> index 002e560dc0d..6fb5db2e1fc 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -1837,6 +1837,14 @@ ftms = {
>    };
>  };
>
> +ftms = {
> +  name = ranges_cache_latest;
> +  values = {
> +    v = 202411;
> +    cxxmin = 26;
> +  };
> +};
> +
>  ftms = {
>    name = ranges_concat;
>    values = {
> diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
> index 70de189b1e0..db61a396c45 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -2035,6 +2035,16 @@
>  #endif /* !defined(__cpp_lib_is_virtual_base_of) && defined(__glibcxx_want_is_virtual_base_of) */
>  #undef __glibcxx_want_is_virtual_base_of
>
> +#if !defined(__cpp_lib_ranges_cache_latest)
> +# if (__cplusplus >  202302L)
> +#  define __glibcxx_ranges_cache_latest 202411L
> +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_cache_latest)
> +#   define __cpp_lib_ranges_cache_latest 202411L
> +#  endif
> +# endif
> +#endif /* !defined(__cpp_lib_ranges_cache_latest) && defined(__glibcxx_want_ranges_cache_latest) */
> +#undef __glibcxx_want_ranges_cache_latest
> +
>  #if !defined(__cpp_lib_ranges_concat)
>  # if (__cplusplus >  202302L)
>  #  define __glibcxx_ranges_concat 202403L
> diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
> index 5c795a90fbc..07ec90248a6 100644
> --- a/libstdc++-v3/include/std/ranges
> +++ b/libstdc++-v3/include/std/ranges
> @@ -57,6 +57,7 @@
>  #define __glibcxx_want_ranges
>  #define __glibcxx_want_ranges_as_const
>  #define __glibcxx_want_ranges_as_rvalue
> +#define __glibcxx_want_ranges_cache_latest
>  #define __glibcxx_want_ranges_cartesian_product
>  #define __glibcxx_want_ranges_concat
>  #define __glibcxx_want_ranges_chunk
> @@ -1534,6 +1535,8 @@ namespace views::__adaptor
>             this->_M_payload._M_apply(_Optional_func{__f}, __i);
>             return this->_M_get();
>           }
> +
> +       using _Optional_base<_Tp>::_M_reset;
>        };
>
>      template<range _Range>
> @@ -10203,6 +10206,192 @@ namespace ranges
>  } // namespace ranges
>  #endif // __cpp_lib_ranges_concat
>
> +#if __cpp_lib_ranges_cache_latest // C++ >= 26
> +namespace ranges
> +{
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  class cache_latest_view : public view_interface<cache_latest_view<_Vp>>
> +  {
> +    _Vp _M_base = _Vp();
> +
> +    using __cache_t = __conditional_t<is_reference_v<range_reference_t<_Vp>>,
> +                                     add_pointer_t<range_reference_t<_Vp>>,
> +                                     range_reference_t<_Vp>>;
> +    __detail::__non_propagating_cache<__cache_t> _M_cache;
> +
> +    class _Iterator;
> +    class _Sentinel;
> +
> +  public:
> +    cache_latest_view() requires default_initializable<_Vp> = default;
> +
> +    constexpr explicit
> +    cache_latest_view(_Vp __base)
> +    : _M_base(std::move(__base))
> +    { }
> +
> +    constexpr _Vp
> +    base() const & requires copy_constructible<_Vp>
> +    { return _M_base; }
> +
> +    constexpr _Vp
> +    base() &&
> +    { return std::move(_M_base); }
> +
> +    constexpr auto
> +    begin()
> +    { return _Iterator(*this); }
> +
> +    constexpr auto
> +    end()
> +    { return _Sentinel(*this); }
> +
> +    constexpr auto
> +    size() requires sized_range<_Vp>
> +    { return ranges::size(_M_base); }
> +
> +    constexpr auto
> +    size() const requires sized_range<const _Vp>
> +    { return ranges::size(_M_base); }
> +  };
> +
> +  template<typename _Range>
> +    cache_latest_view(_Range&&) -> cache_latest_view<views::all_t<_Range>>;
> +
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  class cache_latest_view<_Vp>::_Iterator
> +  {
> +    cache_latest_view* _M_parent;
> +    iterator_t<_Vp> _M_current;
> +
> +    constexpr explicit
> +    _Iterator(cache_latest_view& __parent)
> +    : _M_parent(std::__addressof(__parent)),
> +      _M_current(ranges::begin(__parent._M_base))
> +    { }
> +
> +    friend class cache_latest_view;
> +
> +  public:
> +    using difference_type = range_difference_t<_Vp>;
> +    using value_type = range_value_t<_Vp>;
> +    using iterator_concept = input_iterator_tag;
> +
> +    _Iterator(_Iterator&&) = default;
> +
> +    _Iterator&
> +    operator=(_Iterator&&) = default;
> +
> +    constexpr iterator_t<_Vp>
> +    base() &&
> +    { return std::move(_M_current); }
> +
> +    constexpr const iterator_t<_Vp>&
> +    base() const & noexcept
> +    { return _M_current; }
> +
> +    constexpr range_reference_t<_Vp>&
> +    operator*() const
> +    {
> +      if constexpr (is_reference_v<range_reference_t<_Vp>>)
> +       {
> +         if (!_M_parent->_M_cache)
> +           _M_parent->_M_cache = std::__addressof(__detail::__as_lvalue(*_M_current));
> +         return **_M_parent->_M_cache;
> +       }
> +      else
> +       {
> +         if (!_M_parent->_M_cache)
> +           _M_parent->_M_cache._M_emplace_deref(_M_current);
> +         return *_M_parent->_M_cache;
> +       }
> +    }
> +
> +    constexpr _Iterator&
> +    operator++()
> +    {
> +      _M_parent->_M_cache._M_reset();
> +      ++_M_current;
> +      return *this;
> +    }
> +
> +    constexpr void
> +    operator++(int)
> +    { ++*this; }
> +
> +    friend constexpr range_rvalue_reference_t<_Vp>
> +    iter_move(const _Iterator& __i)
> +      noexcept(noexcept(ranges::iter_move(__i._M_current)))
> +    { return ranges::iter_move(__i._M_current); }
> +
> +    friend constexpr void
> +    iter_swap(const _Iterator& __x, const _Iterator& __y)
> +      noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
> +      requires indirectly_swappable<iterator_t<_Vp>>
> +    { ranges::iter_swap(__x._M_current, __y._M_current); }
> +  };
> +
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  class cache_latest_view<_Vp>::_Sentinel
> +  {
> +    sentinel_t<_Vp> _M_end = sentinel_t<_Vp>();
> +
> +    constexpr explicit
> +    _Sentinel(cache_latest_view& __parent)
> +    : _M_end(ranges::end(__parent._M_base))
> +    { }
> +
> +    friend class cache_latest_view;
> +
> +  public:
> +    _Sentinel() = default;
> +
> +    constexpr sentinel_t<_Vp>
> +    base() const
> +    { return _M_end; }
> +
> +    friend constexpr bool
> +    operator==(const _Iterator& __x, const _Sentinel& __y)
> +    { return __x._M_current == __y._M_end; }
> +
> +    friend constexpr range_difference_t<_Vp>
> +    operator-(const _Iterator& __x, const _Sentinel& __y)
> +      requires sized_sentinel_for<sentinel_t<_Vp>, iterator_t<_Vp>>
> +    { return __x._M_current - __y._M_end; }
> +
> +    friend constexpr range_difference_t<_Vp>
> +    operator-(const _Sentinel& __x, const _Iterator& __y)
> +      requires sized_sentinel_for<sentinel_t<_Vp>, iterator_t<_Vp>>
> +    { return __x._M_end - __y._M_current; }
> +  };
> +
> +  namespace views
> +  {
> +    namespace __detail
> +    {
> +      template<typename _Tp>
> +       concept __can_cache_latest = requires { cache_latest_view(std::declval<_Tp>()); };
> +    }
> +
> +    struct _CacheLatest : __adaptor::_RangeAdaptorClosure<_CacheLatest>
> +    {
> +      template<viewable_range _Range>
> +       requires __detail::__can_cache_latest<_Range>
> +       constexpr auto
> +       operator() [[nodiscard]] (_Range&& __r) const
> +       { return cache_latest_view(std::forward<_Range>(__r)); }
> +
> +      static constexpr bool _S_has_simple_call_op = true;
> +    };
> +
> +    inline constexpr _CacheLatest cache_latest;
> +  }
> +} // namespace ranges
> +#endif // __cpp_lib_ranges_cache_latest
> +
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace std
>  #endif // library concepts
> diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/cache_latest/1.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/cache_latest/1.cc
> new file mode 100644
> index 00000000000..5904831c4e0
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/cache_latest/1.cc
> @@ -0,0 +1,72 @@
> +// { dg-do run { target c++26 } }
> +
> +#include <ranges>
> +
> +#if __cpp_lib_ranges_cache_latest != 202411L
> +# error "Feature-test macro __cpp_lib_ranges_cache_latest has wrong value in <ranges>"
> +#endif
> +
> +#include <algorithm>
> +#include <testsuite_hooks.h>
> +
> +namespace ranges = std::ranges;
> +namespace views = std::views;
> +
> +constexpr bool
> +test01()
> +{
> +  int xs[] = {1,2,3,4,5};
> +  auto v = xs | views::cache_latest;
> +  VERIFY( ranges::equal(v, xs) );
> +  VERIFY( ranges::size(v) == 5 );
> +
> +  auto it = v.begin();
> +  auto st = v.end();
> +  VERIFY( st - it == 5 );
> +  VERIFY( it - st == -5 );
> +  it++;
> +  VERIFY( st - it == 4 );
> +  VERIFY( it - st == -4 );
> +
> +  auto jt = v.begin();
> +  ranges::iter_swap(it, jt);
> +  VERIFY( ranges::equal(xs, (int[]){2,1,3,4,5}) );
> +  int n = ranges::iter_move(it);
> +  VERIFY( n == 1 );
> +  ranges::iter_swap(it, jt);
> +
> +  auto w = views::iota(1, 6) | views::cache_latest;
> +  VERIFY( ranges::equal(w, xs) );
> +
> +  return true;
> +}
> +
> +constexpr bool
> +test02()
> +{
> +  // Motivating example from P3138R5
> +  int xs[] = {1, 2, 3, 4, 5};
> +  int transform_count = 0;
> +  auto v = xs | views::transform([&](int i){ ++transform_count; return i * i; })
> +             | views::filter([](int i){ return i % 2 == 0; });
> +  VERIFY( ranges::equal(v, (int[]){4, 16}) );
> +  VERIFY( transform_count == 7 );
> +
> +  transform_count = 0;
> +  auto w = xs | views::transform([&](int i){ ++transform_count; return i * i; })
> +             | views::cache_latest
> +             | views::filter([](int i){ return i % 2 == 0; });
> +  VERIFY( ranges::equal(w, (int[]){4, 16}) );
> +  VERIFY( transform_count == 5 );
> +
> +  return true;
> +}
> +
> +int
> +main()
> +{
> +  static_assert(test01());
> +  static_assert(test02());
> +  test01();
> +  test02();
> +}
> --
> 2.48.1.329.ge2067b49ec.dirty
>
>
> >
> > > +    __detail::__non_propagating_cache<__cache_t> _M_cache;
> > > +
> > > +    class _Iterator;
> > > +    class _Sentinel;
> > > +
> > > +  public:
> > > +    cache_latest_view() requires default_initializable<_Vp> = default;
> > > +
> > > +    constexpr explicit
> > > +    cache_latest_view(_Vp __base)
> > > +    : _M_base(std::move(__base))
> > > +    { }
> > > +
> > > +    constexpr _Vp
> > > +    base() const & requires copy_constructible<_Vp>
> > > +    { return _M_base; }
> > > +
> > > +    constexpr _Vp
> > > +    base() &&
> > > +    { return std::move(_M_base); }
> > > +
> > > +    constexpr auto
> > > +    begin()
> > > +    { return _Iterator(*this); }
> > > +
> > > +    constexpr auto
> > > +    end()
> > > +    { return _Sentinel(*this); }
> > > +
> > > +    constexpr auto
> > > +    size() requires sized_range<_Vp>
> > > +    { return ranges::size(_M_base); }
> > > +
> > > +    constexpr auto
> > > +    size() const requires sized_range<const _Vp>
> > > +    { return ranges::size(_M_base); }
> > > +  };
> > > +
> > > +  template<typename _Range>
> > > +    cache_latest_view(_Range&&) -> cache_latest_view<views::all_t<_Range>>;
> > > +
> > > +  template<input_range _Vp>
> > > +    requires view<_Vp>
> > > +  class cache_latest_view<_Vp>::_Iterator
> > > +  {
> > > +    cache_latest_view* _M_parent;
> > > +    iterator_t<_Vp> _M_current;
> > > +
> > > +    constexpr explicit
> > > +    _Iterator(cache_latest_view& __parent)
> > > +    : _M_parent(std::__addressof(__parent)),
> > > +      _M_current(ranges::begin(__parent._M_base))
> > > +    { }
> > > +
> > > +    friend class cache_latest_view;
> > > +
> > > +  public:
> > > +    using difference_type = range_difference_t<_Vp>;
> > > +    using value_type = range_value_t<_Vp>;
> > > +    using iterator_concept = input_iterator_tag;
> > > +
> > > +    _Iterator(_Iterator&&) = default;
> > > +
> > > +    _Iterator&
> > > +    operator=(_Iterator&&) = default;
> > > +
> > > +    constexpr iterator_t<_Vp>
> > > +    base() &&
> > > +    { return std::move(_M_current); }
> > > +
> > > +    constexpr const iterator_t<_Vp>&
> > > +    base() const & noexcept
> > > +    { return _M_current; }
> > > +
> > > +    constexpr range_reference_t<_Vp>&
> > > +    operator*() const
> > > +    {
> > > +      if constexpr (is_reference_v<range_reference_t<_Vp>>)
> > > +       {
> > > +         if (!_M_parent->_M_cache)
> > > +           _M_parent->_M_cache = std::__addressof(__detail::__as_lvalue(*_M_current));
> > > +         return **_M_parent->_M_cache;
> > > +       }
> > > +      else
> > > +       {
> > > +         if (!_M_parent->_M_cache)
> > > +           _M_parent->_M_cache._M_emplace_deref(_M_current);
> > > +         return *_M_parent->_M_cache;
> > > +       }
> > > +    }
> > > +
> > > +    constexpr _Iterator&
> > > +    operator++()
> > > +    {
> > > +      _M_parent->_M_cache._M_reset();
> > > +      ++_M_current;
> > > +      return *this;
> > > +    }
> > > +
> > > +    constexpr void
> > > +    operator++(int)
> > > +    { ++*this; }
> > > +
> > > +    friend constexpr range_rvalue_reference_t<_Vp>
> > > +    iter_move(const _Iterator& __i)
> > > +      noexcept(noexcept(ranges::iter_move(__i._M_current)))
> > > +    { return ranges::iter_move(__i._M_current); }
> > > +
> > > +    friend constexpr void
> > > +    iter_swap(const _Iterator& __x, const _Iterator& __y)
> > > +      noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
> > > +      requires indirectly_swappable<iterator_t<_Vp>>
> > > +    { ranges::iter_swap(__x._M_current, __y._M_current); }
> > > +  };
> > > +
> > > +  template<input_range _Vp>
> > > +    requires view<_Vp>
> > > +  class cache_latest_view<_Vp>::_Sentinel
> > > +  {
> > > +    sentinel_t<_Vp> _M_end = sentinel_t<_Vp>();
> > > +
> > > +    constexpr explicit
> > > +    _Sentinel(cache_latest_view& parent)
> > > +    : _M_end(ranges::end(parent._M_base))
> > > +    { }
> > > +
> > > +    friend class cache_latest_view;
> > > +
> > > +  public:
> > > +    _Sentinel() = default;
> > > +
> > > +    constexpr sentinel_t<_Vp>
> > > +    base() const
> > > +    { return _M_end; }
> > > +
> > > +    friend constexpr bool
> > > +    operator==(const _Iterator& __x, const _Sentinel& __y)
> > > +    { return __x._M_current == __y._M_end; }
> > > +
> > > +    friend constexpr range_difference_t<_Vp>
> > > +    operator-(const _Iterator& __x, const _Sentinel& __y)
> > > +      requires sized_sentinel_for<sentinel_t<_Vp>, iterator_t<_Vp>>
> > > +    { return __x._M_current - __y._M_end; }
> > > +
> > > +    friend constexpr range_difference_t<_Vp>
> > > +    operator-(const _Sentinel& __x, const _Iterator& __y)
> > > +      requires sized_sentinel_for<sentinel_t<_Vp>, iterator_t<_Vp>>
> > > +    { return __x._M_end - __y._M_current; }
> > > +  };
> > > +
> > > +  namespace views
> > > +  {
> > > +    namespace __detail
> > > +    {
> > > +      template<typename _Tp>
> > > +       concept __can_cache_latest = requires { cache_latest_view(std::declval<_Tp>()); };
> > > +    }
> > > +
> > > +    struct _CacheLatest : __adaptor::_RangeAdaptorClosure<_CacheLatest>
> > > +    {
> > > +      template<viewable_range _Range>
> > > +       requires __detail::__can_cache_latest<_Range>
> > > +       constexpr auto
> > > +       operator() [[nodiscard]] (_Range&& __r) const
> > > +       { return cache_latest_view(std::forward<_Range>(__r)); }
> > > +
> > > +      static constexpr bool _S_has_simple_call_op = true;
> > > +    };
> > > +
> > > +    inline constexpr _CacheLatest cache_latest;
> > > +  }
> > > +} // namespace ranges
> > > +#endif // __cpp_lib_ranges_cache_latest
> > > +
> > >  _GLIBCXX_END_NAMESPACE_VERSION
> > >  } // namespace std
> > >  #endif // library concepts
> > > --
> > > 2.48.1.291.g388218fac7.dirty
> > >
> >
> >
>
diff mbox series

Patch

diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 002e560dc0d..6fb5db2e1fc 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1837,6 +1837,14 @@  ftms = {
   };
 };
 
+ftms = {
+  name = ranges_cache_latest;
+  values = {
+    v = 202411;
+    cxxmin = 26;
+  };
+};
+
 ftms = {
   name = ranges_concat;
   values = {
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 70de189b1e0..db61a396c45 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -2035,6 +2035,16 @@ 
 #endif /* !defined(__cpp_lib_is_virtual_base_of) && defined(__glibcxx_want_is_virtual_base_of) */
 #undef __glibcxx_want_is_virtual_base_of
 
+#if !defined(__cpp_lib_ranges_cache_latest)
+# if (__cplusplus >  202302L)
+#  define __glibcxx_ranges_cache_latest 202411L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_cache_latest)
+#   define __cpp_lib_ranges_cache_latest 202411L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_ranges_cache_latest) && defined(__glibcxx_want_ranges_cache_latest) */
+#undef __glibcxx_want_ranges_cache_latest
+
 #if !defined(__cpp_lib_ranges_concat)
 # if (__cplusplus >  202302L)
 #  define __glibcxx_ranges_concat 202403L
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index 5c795a90fbc..db9a00be264 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -58,6 +58,7 @@ 
 #define __glibcxx_want_ranges_as_const
 #define __glibcxx_want_ranges_as_rvalue
 #define __glibcxx_want_ranges_cartesian_product
+#define __glibcxx_want_ranges_cache_latest
 #define __glibcxx_want_ranges_concat
 #define __glibcxx_want_ranges_chunk
 #define __glibcxx_want_ranges_chunk_by
@@ -1534,6 +1535,8 @@  namespace views::__adaptor
 	    this->_M_payload._M_apply(_Optional_func{__f}, __i);
 	    return this->_M_get();
 	  }
+
+	using _Optional_base<_Tp>::_M_reset;
       };
 
     template<range _Range>
@@ -10203,6 +10206,192 @@  namespace ranges
 } // namespace ranges
 #endif // __cpp_lib_ranges_concat
 
+#if __cpp_lib_ranges_cache_latest // C++ >= 26
+namespace ranges
+{
+  template<input_range _Vp>
+    requires view<_Vp>
+  class cache_latest_view : public view_interface<cache_latest_view<_Vp>>
+  {
+    _Vp _M_base = _Vp();
+
+    using __cache_t = conditional_t<is_reference_v<range_reference_t<_Vp>>,
+				    add_pointer_t<range_reference_t<_Vp>>,
+				    range_reference_t<_Vp>>;
+    __detail::__non_propagating_cache<__cache_t> _M_cache;
+
+    class _Iterator;
+    class _Sentinel;
+
+  public:
+    cache_latest_view() requires default_initializable<_Vp> = default;
+
+    constexpr explicit
+    cache_latest_view(_Vp __base)
+    : _M_base(std::move(__base))
+    { }
+
+    constexpr _Vp
+    base() const & requires copy_constructible<_Vp>
+    { return _M_base; }
+
+    constexpr _Vp
+    base() &&
+    { return std::move(_M_base); }
+
+    constexpr auto
+    begin()
+    { return _Iterator(*this); }
+
+    constexpr auto
+    end()
+    { return _Sentinel(*this); }
+
+    constexpr auto
+    size() requires sized_range<_Vp>
+    { return ranges::size(_M_base); }
+
+    constexpr auto
+    size() const requires sized_range<const _Vp>
+    { return ranges::size(_M_base); }
+  };
+
+  template<typename _Range>
+    cache_latest_view(_Range&&) -> cache_latest_view<views::all_t<_Range>>;
+
+  template<input_range _Vp>
+    requires view<_Vp>
+  class cache_latest_view<_Vp>::_Iterator
+  {
+    cache_latest_view* _M_parent;
+    iterator_t<_Vp> _M_current;
+
+    constexpr explicit
+    _Iterator(cache_latest_view& __parent)
+    : _M_parent(std::__addressof(__parent)),
+      _M_current(ranges::begin(__parent._M_base))
+    { }
+
+    friend class cache_latest_view;
+
+  public:
+    using difference_type = range_difference_t<_Vp>;
+    using value_type = range_value_t<_Vp>;
+    using iterator_concept = input_iterator_tag;
+
+    _Iterator(_Iterator&&) = default;
+
+    _Iterator&
+    operator=(_Iterator&&) = default;
+
+    constexpr iterator_t<_Vp>
+    base() &&
+    { return std::move(_M_current); }
+
+    constexpr const iterator_t<_Vp>&
+    base() const & noexcept
+    { return _M_current; }
+
+    constexpr range_reference_t<_Vp>&
+    operator*() const
+    {
+      if constexpr (is_reference_v<range_reference_t<_Vp>>)
+	{
+	  if (!_M_parent->_M_cache)
+	    _M_parent->_M_cache = std::__addressof(__detail::__as_lvalue(*_M_current));
+	  return **_M_parent->_M_cache;
+	}
+      else
+	{
+	  if (!_M_parent->_M_cache)
+	    _M_parent->_M_cache._M_emplace_deref(_M_current);
+	  return *_M_parent->_M_cache;
+	}
+    }
+
+    constexpr _Iterator&
+    operator++()
+    {
+      _M_parent->_M_cache._M_reset();
+      ++_M_current;
+      return *this;
+    }
+
+    constexpr void
+    operator++(int)
+    { ++*this; }
+
+    friend constexpr range_rvalue_reference_t<_Vp>
+    iter_move(const _Iterator& __i)
+      noexcept(noexcept(ranges::iter_move(__i._M_current)))
+    { return ranges::iter_move(__i._M_current); }
+
+    friend constexpr void
+    iter_swap(const _Iterator& __x, const _Iterator& __y)
+      noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
+      requires indirectly_swappable<iterator_t<_Vp>>
+    { ranges::iter_swap(__x._M_current, __y._M_current); }
+  };
+
+  template<input_range _Vp>
+    requires view<_Vp>
+  class cache_latest_view<_Vp>::_Sentinel
+  {
+    sentinel_t<_Vp> _M_end = sentinel_t<_Vp>();
+
+    constexpr explicit
+    _Sentinel(cache_latest_view& parent)
+    : _M_end(ranges::end(parent._M_base))
+    { }
+
+    friend class cache_latest_view;
+
+  public:
+    _Sentinel() = default;
+
+    constexpr sentinel_t<_Vp>
+    base() const
+    { return _M_end; }
+
+    friend constexpr bool
+    operator==(const _Iterator& __x, const _Sentinel& __y)
+    { return __x._M_current == __y._M_end; }
+
+    friend constexpr range_difference_t<_Vp>
+    operator-(const _Iterator& __x, const _Sentinel& __y)
+      requires sized_sentinel_for<sentinel_t<_Vp>, iterator_t<_Vp>>
+    { return __x._M_current - __y._M_end; }
+
+    friend constexpr range_difference_t<_Vp>
+    operator-(const _Sentinel& __x, const _Iterator& __y)
+      requires sized_sentinel_for<sentinel_t<_Vp>, iterator_t<_Vp>>
+    { return __x._M_end - __y._M_current; }
+  };
+
+  namespace views
+  {
+    namespace __detail
+    {
+      template<typename _Tp>
+	concept __can_cache_latest = requires { cache_latest_view(std::declval<_Tp>()); };
+    }
+
+    struct _CacheLatest : __adaptor::_RangeAdaptorClosure<_CacheLatest>
+    {
+      template<viewable_range _Range>
+	requires __detail::__can_cache_latest<_Range>
+	constexpr auto
+	operator() [[nodiscard]] (_Range&& __r) const
+	{ return cache_latest_view(std::forward<_Range>(__r)); }
+
+      static constexpr bool _S_has_simple_call_op = true;
+    };
+
+    inline constexpr _CacheLatest cache_latest;
+  }
+} // namespace ranges
+#endif // __cpp_lib_ranges_cache_latest
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 #endif // library concepts