Tested on x86_64-pc-linux-gnu, does this look OK for trunk? libstdc++-v3/ChangeLog:
* include/bits/ranges_base.h (__detail::__is_initializer_list): Define. (viewable_range): Adjust as per P2415R2. * include/std/ranges (owning_view): Define as per P2415R2. (enable_borrowed_range<owning_view>): Likewise. (views::__detail::__can_subrange): Replace with ... (views::__detail::__can_owning_view): ... this. (views::_All::_S_noexcept): Sync with operator(). (views::_All::operator()): Use owning_view instead of subrange as per P2415R2. * testsuite/std/ranges/adaptors/all.cc (test06): Adjust now that views::all uses owning_view instead of subrange. (test08): New test. * testsuite/std/ranges/adaptors/lazy_split.cc (test09): Adjust now that rvalue non-view non-borrowed ranges are viewable. * testsuite/std/ranges/adaptors/split.cc (test06): Likewise. --- libstdc++-v3/include/bits/ranges_base.h | 16 +++- libstdc++-v3/include/std/ranges | 89 ++++++++++++++++++- .../testsuite/std/ranges/adaptors/all.cc | 59 ++++++++---- .../std/ranges/adaptors/lazy_split.cc | 13 ++- .../testsuite/std/ranges/adaptors/split.cc | 13 ++- 5 files changed, 157 insertions(+), 33 deletions(-) diff --git a/libstdc++-v3/include/bits/ranges_base.h b/libstdc++-v3/include/bits/ranges_base.h index 3c5f4b1790a..38db33fd2ce 100644 --- a/libstdc++-v3/include/bits/ranges_base.h +++ b/libstdc++-v3/include/bits/ranges_base.h @@ -634,7 +634,7 @@ namespace ranges template<typename _Tp> concept __is_derived_from_view_interface = requires (_Tp __t) { __is_derived_from_view_interface_fn(__t, __t); }; - } + } // namespace __detail /// [range.view] The ranges::view_base type. struct view_base { }; @@ -689,11 +689,23 @@ namespace ranges concept common_range = range<_Tp> && same_as<iterator_t<_Tp>, sentinel_t<_Tp>>; + namespace __detail + { + template<typename _Tp> + inline constexpr bool __is_initializer_list = false; + + template<typename _Tp> + inline constexpr bool __is_initializer_list<initializer_list<_Tp>> = true; + } // namespace __detail + /// A range which can be safely converted to a view. template<typename _Tp> concept viewable_range = range<_Tp> && ((view<remove_cvref_t<_Tp>> && constructible_from<remove_cvref_t<_Tp>, _Tp>) - || (!view<remove_cvref_t<_Tp>> && borrowed_range<_Tp>)); + || (!view<remove_cvref_t<_Tp>> + && (is_lvalue_reference_v<_Tp> + || (movable<remove_reference_t<_Tp>> + && !__detail::__is_initializer_list<remove_cvref_t<_Tp>>)))); // [range.iter.ops] range iterator operations diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges index ac85907f129..3e71ecb32b7 100644 --- a/libstdc++-v3/include/std/ranges +++ b/libstdc++-v3/include/std/ranges @@ -1144,6 +1144,87 @@ namespace views::__adaptor template<typename _Tp> inline constexpr bool enable_borrowed_range<ref_view<_Tp>> = true; + template<range _Range> + requires movable<_Range> + && (!__detail::__is_initializer_list<remove_cv_t<_Range>>) + class owning_view : public view_interface<owning_view<_Range>> + { + private: + _Range _M_r = _Range(); + + public: + owning_view() requires default_initializable<_Range> = default; + + constexpr + owning_view(_Range&& __t) + noexcept(is_nothrow_move_constructible_v<_Range>) + : _M_r(std::move(__t)) + { } + + owning_view(owning_view&&) = default; + owning_view& operator=(owning_view&&) = default; + + constexpr _Range& + base() & noexcept + { return _M_r; } + + constexpr const _Range& + base() const& noexcept + { return _M_r; } + + constexpr _Range&& + base() && noexcept + { return std::move(_M_r); } + + constexpr const _Range&& + base() const&& noexcept + { return std::move(_M_r); } + + constexpr iterator_t<_Range> + begin() + { return ranges::begin(_M_r); } + + constexpr sentinel_t<_Range> + end() + { return ranges::end(_M_r); } + + constexpr auto + begin() const requires range<const _Range> + { return ranges::begin(_M_r); } + + constexpr auto + end() const requires range<const _Range> + { return ranges::end(_M_r); } + + constexpr bool + empty() requires requires { ranges::empty(_M_r); } + { return ranges::empty(_M_r); } + + constexpr bool + empty() const requires requires { ranges::empty(_M_r); } + { return ranges::empty(_M_r); } + + constexpr auto + size() requires sized_range<_Range> + { return ranges::size(_M_r); } + + constexpr auto + size() const requires sized_range<const _Range> + { return ranges::size(_M_r); } + + constexpr auto + data() requires contiguous_range<_Range> + { return ranges::data(_M_r); } + + constexpr auto + data() const requires contiguous_range<const _Range> + { return ranges::data(_M_r); } + }; + + template<typename _Tp> + inline constexpr bool enable_borrowed_range<owning_view<_Tp>> + = enable_borrowed_range<_Tp>; + namespace views { namespace __detail @@ -1152,7 +1233,7 @@ namespace views::__adaptor concept __can_ref_view = requires { ref_view{std::declval<_Range>()}; }; template<typename _Range> - concept __can_subrange = requires { subrange{std::declval<_Range>()}; }; + concept __can_owning_view = requires { owning_view{std::declval<_Range>()}; }; } // namespace __detail struct _All : __adaptor::_RangeAdaptorClosure @@ -1166,13 +1247,13 @@ namespace views::__adaptor else if constexpr (__detail::__can_ref_view<_Range>) return true; else - return noexcept(subrange{std::declval<_Range>()}); + return noexcept(owning_view{std::declval<_Range>()}); } template<viewable_range _Range> requires view<decay_t<_Range>> || __detail::__can_ref_view<_Range> - || __detail::__can_subrange<_Range> + || __detail::__can_owning_view<_Range> constexpr auto operator() [[nodiscard]] (_Range&& __r) const noexcept(_S_noexcept<_Range>()) @@ -1182,7 +1263,7 @@ namespace views::__adaptor else if constexpr (__detail::__can_ref_view<_Range>) return ref_view{std::forward<_Range>(__r)}; else - return subrange{std::forward<_Range>(__r)}; + return owning_view{std::forward<_Range>(__r)}; } static constexpr bool _S_has_simple_call_op = true; diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/all.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/all.cc index c25d972274f..9d4e714b3e9 100644 --- a/libstdc++-v3/testsuite/std/ranges/adaptors/all.cc +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/all.cc @@ -19,7 +19,9 @@ // { dg-do run { target c++2a } } #include <algorithm> +#include <array> #include <ranges> +#include <vector> #include <testsuite_hooks.h> #include <testsuite_iterators.h> @@ -130,20 +132,6 @@ test05() static_assert(!requires { 0 | all; }); } -template<bool B1, bool B2> -struct BorrowedRange -{ - int* ptr = nullptr; - - BorrowedRange(int (&arr)[3]) noexcept : ptr(arr) { } - - int* begin() const noexcept(B1) { return ptr; } - int* end() const noexcept(B2) { return ptr + 3; } -}; - -template<bool B1, bool B2> -const bool std::ranges::enable_borrowed_range<BorrowedRange<B1, B2>> = true; - void test06() { @@ -152,11 +140,11 @@ test06() // Using ref_view: static_assert(noexcept(views::all(x))); - // Using subrange: - static_assert(noexcept(views::all(BorrowedRange<true, true>(x)))); - static_assert(!noexcept(views::all(BorrowedRange<true, false>(x)))); - static_assert(!noexcept(views::all(BorrowedRange<false, true>(x)))); - static_assert(!noexcept(views::all(BorrowedRange<false, false>(x)))); + // Using owning_view: + struct A { A(); A(const A&); }; + static_assert(noexcept(views::all(std::array<int, 3>{}))); + static_assert(!std::is_nothrow_move_constructible_v<std::array<A, 3>>); + static_assert(!noexcept(views::all(std::array<A, 3>{}))); } void @@ -173,6 +161,38 @@ test07() static_assert(!ranges::viewable_range<view_t&>); } +constexpr bool +test08() +{ + // Verify P2415R2 "What is a view?" changes + // Namely, rvalue non-view non-borrowed ranges are now viewable. + static_assert(ranges::viewable_range<std::vector<int>&&>); + static_assert(!ranges::viewable_range<const std::vector<int>&&>); + + static_assert(ranges::viewable_range<std::initializer_list<int>&>); + static_assert(ranges::viewable_range<const std::initializer_list<int>&>); + static_assert(!ranges::viewable_range<std::initializer_list<int>&&>); + static_assert(!ranges::viewable_range<const std::initializer_list<int>&&>); + + using type = views::all_t<std::vector<int>&&>; + using type = ranges::owning_view<std::vector<int>>; + + std::same_as<type> auto v = std::vector<int>{{1,2,3}} | views::all; + + VERIFY( ranges::equal(v, (int[]){1,2,3}) ); + VERIFY( ranges::size(v) == 3 ); + VERIFY( !ranges::empty(v) ); + VERIFY( ranges::data(v) == &v[0] ); + + const auto w = std::move(v); + VERIFY( ranges::equal(w, (int[]){1,2,3}) ); + VERIFY( ranges::size(w) == 3 ); + VERIFY( !ranges::empty(w) ); + VERIFY( ranges::data(w) == &w[0] ); + + return true; +} + int main() { @@ -183,4 +203,5 @@ main() test05(); test06(); test07(); + static_assert(test08()); } diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/lazy_split.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/lazy_split.cc index a7d3dd6a23e..e0279dc2a4a 100644 --- a/libstdc++-v3/testsuite/std/ranges/adaptors/lazy_split.cc +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/lazy_split.cc @@ -163,10 +163,15 @@ test09() static_assert(!requires { lazy_split(p)(); }); static_assert(!requires { s | lazy_split; }); - static_assert(!requires { s | lazy_split(p); }); - static_assert(!requires { lazy_split(p)(s); }); - static_assert(!requires { s | (lazy_split(p) | views::all); }); - static_assert(!requires { (lazy_split(p) | views::all)(s); }); + // Test the case where the closure object is used as an rvalue and therefore + // the copy of p is forwarded as an rvalue. + // This used to be invalid, but is well-formed after P2415R2 relaxed the + // requirements of viewable_range to permit rvalue non-view non-borrowed + // ranges such as std::string&&. + static_assert(requires { s | lazy_split(p); }); + static_assert(requires { lazy_split(p)(s); }); + static_assert(requires { s | (lazy_split(p) | views::all); }); + static_assert(requires { (lazy_split(p) | views::all)(s); }); static_assert(requires { s | lazy_split(views::all(p)); }); static_assert(requires { lazy_split(views::all(p))(s); }); diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/split.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/split.cc index 44245f5071a..4f8dddeb410 100644 --- a/libstdc++-v3/testsuite/std/ranges/adaptors/split.cc +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/split.cc @@ -145,10 +145,15 @@ test06() static_assert(!requires { split(p)(); }); static_assert(!requires { s | split; }); - static_assert(!requires { s | split(p); }); - static_assert(!requires { split(p)(s); }); - static_assert(!requires { s | (split(p) | views::all); }); - static_assert(!requires { (split(p) | views::all)(s); }); + // Test the case where the closure object is used as an rvalue and therefore + // the copy of p is forwarded as an rvalue. + // This used to be invalid, but is well-formed after P2415R2 relaxed the + // requirements of viewable_range to permit rvalue non-view non-borrowed + // ranges such as std::string&&. + static_assert(requires { s | split(p); }); + static_assert(requires { split(p)(s); }); + static_assert(requires { s | (split(p) | views::all); }); + static_assert(requires { (split(p) | views::all)(s); }); static_assert(requires { s | split(views::all(p)); }); static_assert(requires { split(views::all(p))(s); }); -- 2.35.1.225.ge2ac9141e6