This fixes two issues with our iterator caching as described in detail in the PR. Since r12-336 added the __non_propagating_cache class template as part of P2328, this patch just rewrites the _CachedPosition partial specialization in terms of this class template.
Tested on x86_64-pc-linux-gnu, does this look OK for trunk? Shall we also backport this? libstdc++-v3/ChangeLog: PR libstdc++/100479 * include/std/ranges (__detail::__non_propagating_cache): Move definition up to before that of _CachedPosition. Make base class _Optional_base protected instead of private. Add const overload for operator*. (__detail::_CachedPosition): Rewrite the partial specialization for forward ranges as a derived class of __non_propagating_cache. Remove the size constraint on the partial specialization for random access ranges. * testsuite/std/ranges/adaptors/100479.cc: New test. --- libstdc++-v3/include/std/ranges | 133 +++++++++--------- .../testsuite/std/ranges/adaptors/100479.cc | 82 +++++++++++ 2 files changed, 148 insertions(+), 67 deletions(-) create mode 100644 libstdc++-v3/testsuite/std/ranges/adaptors/100479.cc diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges index 1707aeaebcd..fe6379fb858 100644 --- a/libstdc++-v3/include/std/ranges +++ b/libstdc++-v3/include/std/ranges @@ -1139,6 +1139,67 @@ namespace views::__adaptor namespace __detail { + template<typename _Tp> + struct __non_propagating_cache + { + // When _Tp is not an object type (e.g. is a reference type), we make + // __non_propagating_cache<_Tp> empty rather than ill-formed so that + // users can easily conditionally declare data members with this type + // (such as join_view::_M_inner). + }; + + template<typename _Tp> + requires is_object_v<_Tp> + struct __non_propagating_cache<_Tp> : protected _Optional_base<_Tp> + { + __non_propagating_cache() = default; + + constexpr + __non_propagating_cache(const __non_propagating_cache&) noexcept + { } + + constexpr + __non_propagating_cache(__non_propagating_cache&& __other) noexcept + { __other._M_reset(); } + + constexpr __non_propagating_cache& + operator=(const __non_propagating_cache& __other) noexcept + { + if (std::__addressof(__other) != this) + this->_M_reset(); + return *this; + } + + constexpr __non_propagating_cache& + operator=(__non_propagating_cache&& __other) noexcept + { + this->_M_reset(); + __other._M_reset(); + return *this; + } + + constexpr _Tp& + operator*() noexcept + { return this->_M_get(); } + + constexpr const _Tp& + operator*() const noexcept + { return this->_M_get(); } + + template<typename _Iter> + _Tp& + _M_emplace_deref(const _Iter& __i) + { + this->_M_reset(); + // Using _Optional_base::_M_construct to initialize from '*__i' + // would incur an extra move due to the indirection, so we instead + // use placement new directly. + ::new ((void *) std::__addressof(this->_M_payload._M_payload)) _Tp(*__i); + this->_M_payload._M_engaged = true; + return this->_M_get(); + } + }; + template<range _Range> struct _CachedPosition { @@ -1160,27 +1221,25 @@ namespace views::__adaptor template<forward_range _Range> struct _CachedPosition<_Range> + : protected __non_propagating_cache<iterator_t<_Range>> { - private: - iterator_t<_Range> _M_iter{}; - - public: constexpr bool _M_has_value() const - { return _M_iter != iterator_t<_Range>{}; } + { return this->_M_is_engaged(); } constexpr iterator_t<_Range> _M_get(const _Range&) const { __glibcxx_assert(_M_has_value()); - return _M_iter; + return **this; } constexpr void _M_set(const _Range&, const iterator_t<_Range>& __it) { __glibcxx_assert(!_M_has_value()); - _M_iter = __it; + this->_M_payload._M_payload._M_value = __it; + this->_M_payload._M_engaged = true; } }; @@ -2339,66 +2398,6 @@ namespace views::__adaptor inline constexpr _DropWhile drop_while; } // namespace views - namespace __detail - { - template<typename _Tp> - struct __non_propagating_cache - { - // When _Tp is not an object type (e.g. is a reference type), we make - // __non_propagating_cache<_Tp> empty rather than ill-formed so that - // users can easily conditionally declare data members with this type - // (such as join_view::_M_inner). - }; - - template<typename _Tp> - requires is_object_v<_Tp> - struct __non_propagating_cache<_Tp> : private _Optional_base<_Tp> - { - __non_propagating_cache() = default; - - constexpr - __non_propagating_cache(const __non_propagating_cache&) noexcept - { } - - constexpr - __non_propagating_cache(__non_propagating_cache&& __other) noexcept - { __other._M_reset(); } - - constexpr __non_propagating_cache& - operator=(const __non_propagating_cache& __other) noexcept - { - if (std::__addressof(__other) != this) - this->_M_reset(); - return *this; - } - - constexpr __non_propagating_cache& - operator=(__non_propagating_cache&& __other) noexcept - { - this->_M_reset(); - __other._M_reset(); - return *this; - } - - constexpr _Tp& - operator*() noexcept - { return this->_M_get(); } - - template<typename _Iter> - _Tp& - _M_emplace_deref(const _Iter& __i) - { - this->_M_reset(); - // Using _Optional_base::_M_construct to initialize from '*__i' - // would incur an extra move due to the indirection, so we instead - // use placement new directly. - ::new ((void *) std::__addressof(this->_M_payload._M_payload)) _Tp(*__i); - this->_M_payload._M_engaged = true; - return this->_M_get(); - } - }; - } - template<input_range _Vp> requires view<_Vp> && input_range<range_reference_t<_Vp>> class join_view : public view_interface<join_view<_Vp>> diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/100479.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/100479.cc new file mode 100644 index 00000000000..744d4607512 --- /dev/null +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/100479.cc @@ -0,0 +1,82 @@ +// Copyright (C) 2021 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 run { target c++2a } } + +#include <ranges> +#include <testsuite_hooks.h> +#include <testsuite_iterators.h> + +using __gnu_test::test_forward_range; + +namespace ranges = std::ranges; +namespace views = std::views; + +void +test01() +{ + // Verify we don't propagate cached iterators when copying/moving a forward + // range that memoizes its begin(). + static int pred_counter; + int x[] = {1,2,3,4,5}; + test_forward_range rx(x); + auto is_odd = [](auto n) { ++pred_counter; return n%2 != 0; }; + + auto v = rx | views::filter(is_odd); + v.begin(); v.begin(); + VERIFY( pred_counter == 1 ); // filter_view caches iterator for begin() + + auto w = v; + v.begin(); v.begin(); + VERIFY( pred_counter == 1 ); // cached iterator not invalidated during copy + w.begin(); w.begin(); + VERIFY( pred_counter == 2 ); // cached iterator not propagated during copy + + auto z = std::move(w); + w.begin(); w.begin(); + VERIFY( pred_counter == 3 ); // cached iterator invalidated during move + z.begin(); z.begin(); + VERIFY( pred_counter == 4 ); // cached iterator not propagated during move +} + +constexpr bool +test02() +{ + // Propagating cached iterators during copy/move would cause the assertions + // to fail here. + auto v = views::single(1) + | views::split(1) + | views::drop(0) + | views::drop_while([](auto) { return false; }) + | views::filter([](auto) { return true; }); + static_assert(ranges::forward_range<decltype(v)>); + VERIFY( ranges::next(v.begin()) == v.end() ); + auto w = v; + VERIFY( ranges::next(w.begin()) == w.end() ); + auto z = std::move(w); + VERIFY( ranges::next(z.begin()) == z.end() ); + return true; +} + +int +main() +{ + test01(); + test02(); + static_assert(test02()); +} -- 2.31.1.621.g97eea85a0a