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 <[email protected]> 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
> > >
> >
> >
>