... 'subrange-y' view adaptors This implements an interpretation of P1739R4. It's only an "interpretation" becuase AFAICT there is an issue with the paper's wording as-is. So this patch deviates from paper when it comes to the problematic wording.
The issue is that the paper's wording for views::take requires that the views::take of a specialization of subrange is not just another subrange, but specifically is the same specialization as the input subrange. But if, say, the input subrange does not model common_range, then the expression in section 6.1 T{begin(E), begin(E) + min(size(E), F)} is ill-formed because T -- a specialization of subrange which does not model common_range -- must be constructed with an iterator-sentinel pair and not an iterator-iterator pair. A similar issue arises with the views::take of an iota_view whose value type differs from the type of its bound. So in light of this issue, this patch deviates from the paper's wording here by allowing the views::take of a subrange/iota_view input to be a different specialization than that of the input. Moreover, I found myself needing to define an extra constrained constructor iota_view(iterator_, iterator_) alongside the newly added iota_view(iterator_, sentinel_) constructor, so that the expression in views::take iota_view<ValueType,ValueType>{begin(E), begin(E) + min(size(E), F)} is well-formed in general. Despite these deviations, the intended end result remains the same AFAICT. libstdc++-v3/ChangeLog: P1739R4 Avoid template bloat for safe_ranges in combination with 'subrange-y' view adaptors * include/std/ranges (iota_view): Forward declare class _Sentinel. (iota_view::_Iterator): Befriend _Sentinel. (iota_view::_Sentinel): Befriend iota_view. (iota_view::_Sentinel::_M_equal): New member function. (iota_view::_Sentinel::operator==): Define in terms of _M_equal. (iota_view::iota_view): Two new constrained constructors, one taking an _Iterator and a _Sentinel, and another taking two _Iterators. (views::__detail::__is_empty_view, views::__detail::is_dynamic_span, views::__detail::is_basic_string_view, views::__detail::__is_iota_view, views::__detail::__is_subrange): New helper type traits. (views::take): Rewrite in accordance with P1739R4. (views::drop): Likewise. (views::counted): When _Iter models contiguous_iterator, return a dynamic-extend span, in according with P1739R4. * testsuite/std/range/adaptors/counted.cc: Adjust and augment tests to verify behavior in accordance with P1739R4. * testsuite/std/range/adaptors/drop.cc: Likewise. * testsuite/std/range/adaptors/take.cc: Likewise. --- libstdc++-v3/include/std/ranges | 117 +++++++++++++++++- .../testsuite/std/ranges/adaptors/counted.cc | 16 ++- .../testsuite/std/ranges/adaptors/drop.cc | 96 ++++++++++++++ .../testsuite/std/ranges/adaptors/take.cc | 83 ++++++++++++- 4 files changed, 299 insertions(+), 13 deletions(-) diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges index 1b2bf448e9c..dfd143b23e1 100644 --- a/libstdc++-v3/include/std/ranges +++ b/libstdc++-v3/include/std/ranges @@ -43,6 +43,8 @@ #include <initializer_list> #include <iterator> #include <optional> +#include <span> +#include <string_view> #include <tuple> /** @@ -635,6 +637,8 @@ namespace ranges class iota_view : public view_interface<iota_view<_Winc, _Bound>> { private: + struct _Sentinel; + struct _Iterator { private: @@ -811,11 +815,19 @@ namespace ranges private: _Winc _M_value = _Winc(); + + friend _Sentinel; }; struct _Sentinel { private: + constexpr bool + _M_equal(const _Iterator& __x) const + { return __x._M_value == _M_bound; } + + friend iota_view; + _Bound _M_bound = _Bound(); public: @@ -827,7 +839,7 @@ namespace ranges friend constexpr bool operator==(const _Iterator& __x, const _Sentinel& __y) - { return __x._M_value == __y._M_bound; } + { return __y._M_equal(__x); } friend constexpr iter_difference_t<_Winc> operator-(const _Iterator& __x, const _Sentinel& __y) @@ -862,6 +874,22 @@ namespace ranges } } + constexpr + iota_view(_Iterator __first, _Sentinel __last) + // XXX: this constraint does not appear in P1739R4, but it seems + // sensible. + requires (!same_as<_Winc, _Bound>) + : iota_view(*__first, __last._M_bound) + { } + + // XXX: this constructor does not appear in P1739R4, but it's necessary + // for the iota_view folding in views::take to work. + constexpr + iota_view(_Iterator __first, _Iterator __last) + requires same_as<_Winc, _Bound> + : iota_view(*__first, *__last) + { } + constexpr _Iterator begin() const { return _Iterator{_M_value}; } @@ -1965,10 +1993,70 @@ namespace views namespace views { + namespace __detail + { + template<typename _Tp> + inline constexpr bool __is_empty_view = false; + + template<typename _Tp> + inline constexpr bool __is_empty_view<empty_view<_Tp>> = true; + + template<typename _Tp> + inline constexpr bool __is_dynamic_span = false; + + template<typename _Tp> + inline constexpr bool __is_dynamic_span<span<_Tp, dynamic_extent>> + = true; + + template<typename _Tp> + inline constexpr bool __is_basic_string_view = false; + + template<typename _CharT, typename _Traits> + inline constexpr bool + __is_basic_string_view<basic_string_view<_CharT, _Traits>> = true; + + template<typename _Tp> + inline constexpr bool __is_iota_view = false; + + template<weakly_incrementable _Winc, semiregular _Bound> + inline constexpr bool __is_iota_view<iota_view<_Winc, _Bound>> = true; + + template<typename _Tp> + inline constexpr bool __is_subrange = false; + + template<typename _It, typename _Sent, subrange_kind _Kind> + inline constexpr bool + __is_subrange<subrange<_It, _Sent, _Kind>> = true; + } + inline constexpr __adaptor::_RangeAdaptor take - = [] <viewable_range _Range, typename _Tp> (_Range&& __r, _Tp&& __n) + = [] <viewable_range _Range> (_Range&& __r, range_difference_t<_Range> __n) { - return take_view{std::forward<_Range>(__r), std::forward<_Tp>(__n)}; + using _Tp = remove_cvref_t<_Range>; + if constexpr (__detail::__is_empty_view<_Tp>) + return std::forward<_Range>(__r); + else if constexpr (random_access_range<_Tp> && sized_range<_Tp>) + { + // XXX: we diverge from P1739R4 here in the way we fold iota_view + // and subrange. + auto __begin = ranges::begin(__r); + auto __size = std::min<decltype(__n)>(ranges::size(__r), __n); + if constexpr (__detail::__is_dynamic_span<_Tp> + || __detail::__is_basic_string_view<_Tp>) + return _Tp{__begin, __begin + __size}; + else if constexpr (__detail::__is_iota_view<_Tp>) + { + using _ValueType = range_value_t<_Tp>; + return iota_view<_ValueType, _ValueType>{__begin, + __begin + __size}; + } + else if constexpr (__detail::__is_subrange<_Tp>) + return subrange{__begin, __begin + __size}; + else + return take_view{std::forward<_Range>(__r), __n}; + } + else + return take_view{std::forward<_Range>(__r), __n}; }; } // namespace views @@ -2135,9 +2223,24 @@ namespace views namespace views { inline constexpr __adaptor::_RangeAdaptor drop - = [] <viewable_range _Range, typename _Tp> (_Range&& __r, _Tp&& __n) + = [] <viewable_range _Range> (_Range&& __r, range_difference_t<_Range> __n) { - return drop_view{std::forward<_Range>(__r), std::forward<_Tp>(__n)}; + using _Tp = remove_cvref_t<_Range>; + if constexpr (__detail::__is_empty_view<_Tp>) + return std::forward<_Range>(__r); + else if constexpr (random_access_range<_Tp> && sized_range<_Tp> + && (__detail::__is_dynamic_span<_Tp> + || __detail::__is_basic_string_view<_Tp> + || __detail::__is_iota_view<_Tp> + || __detail::__is_subrange<_Tp>)) + { + auto __begin = ranges::begin(__r); + auto __size = std::min<decltype(__n)>(ranges::size(__r), __n); + auto __end = ranges::end(__r); + return _Tp{__begin + __size, __end}; + } + else + return drop_view{std::forward<_Range>(__r), __n}; }; } // namespace views @@ -2903,7 +3006,9 @@ namespace views constexpr auto operator()(_Iter __i, iter_difference_t<_Iter> __n) const { - if constexpr (random_access_iterator<_Iter>) + if constexpr (contiguous_iterator<_Iter>) + return span{to_address(std::move(__i)), __n}; + else if constexpr (random_access_iterator<_Iter>) return subrange{__i, __i + __n}; else return subrange{counted_iterator{std::move(__i), __n}, diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/counted.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/counted.cc index e81f9062d39..0b6415e44df 100644 --- a/libstdc++-v3/testsuite/std/ranges/adaptors/counted.cc +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/counted.cc @@ -25,6 +25,7 @@ using __gnu_test::test_range; using __gnu_test::forward_iterator_wrapper; +using __gnu_test::random_access_iterator_wrapper; namespace ranges = std::ranges; namespace views = ranges::views; @@ -33,7 +34,8 @@ void test01() { int x[] = {0,1,2,3,4,5,0,1,2,3,4,5}; - auto v = views::counted(x, 5); + test_range<int, random_access_iterator_wrapper> rx(x); + auto v = views::counted(rx.begin(), 5); VERIFY( ranges::equal(v, (int[]){0,1,2,3,4}) ); using R = decltype(v); static_assert(ranges::view<R>); @@ -56,9 +58,21 @@ test02() static_assert(!ranges::bidirectional_range<R>); } +void +test03() +{ + int x[] = {0,1,2,3,4,5,0,1,2,3,4,5}; + auto v = views::counted(x, 5); + VERIFY( ranges::equal(v, (int[]){0,1,2,3,4}) ); + using R = decltype(v); + // P1739R4: The views::take of a contiguous_iterator is a span. + static_assert(std::same_as<R, std::span<int>>); +} + int main() { test01(); test02(); + test03(); } diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/drop.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/drop.cc index 93fbafcf5a3..786acf668ff 100644 --- a/libstdc++-v3/testsuite/std/ranges/adaptors/drop.cc +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/drop.cc @@ -24,7 +24,9 @@ #include <testsuite_iterators.h> using __gnu_test::test_range; +using __gnu_test::test_sized_range; using __gnu_test::bidirectional_iterator_wrapper; +using __gnu_test::random_access_iterator_wrapper; namespace ranges = std::ranges; namespace views = ranges::views; @@ -95,6 +97,93 @@ test06() VERIFY( ranges::empty(x | views::drop(10)) ); } +void +test07() +{ + auto v = views::iota(0,5) | views::drop(3); + using R = decltype(v); + // P1739R4: The views::drop of an iota_view is an iota_view. + static_assert(std::same_as<R, ranges::iota_view<int, int>>); + VERIFY( ranges::equal(v, (int[]){3,4}) ); +} + +void +test08() +{ + int x[] = {1,2,3,4,5}; + std::span rx{x, 5}; + auto v = rx | views::drop(2); + using R = decltype(v); + // P1739R4: The views::drop of a dynamic-extent span is a dynamic-extent span. + static_assert(std::same_as<R, std::span<int>>); + VERIFY( ranges::equal(v, (int[]){3,4,5}) ); + VERIFY( ranges::empty(rx | views::drop(100)) ); +} + +void +test09() +{ + using namespace std::literals; + std::string_view rx = "hello world!"; + auto v = rx | views::drop(6); + using R = decltype(v); + // P1739R4: The views::drop of a string_view is a string_view. + static_assert(std::same_as<R, std::string_view>); + VERIFY( ranges::equal(v, "world!"sv) ); + VERIFY( ranges::empty(rx | views::drop(100)) ); +} + +void +test10() +{ + int x[] = {1,2,3,4,5}; + ranges::subrange<int*> rx{x, x+4}; + auto v = rx | views::drop(2); + using R = decltype(v); + // P1739R4: The views::drop of a subrange is a subrange. + static_assert(std::same_as<R, ranges::subrange<int*>>); + VERIFY( ranges::equal(v, (int[]){3,4}) ); + VERIFY( ranges::empty(rx | views::drop(100)) ); +} + +void +test11() +{ + int x[] = {1,2,3,4,5}; + test_sized_range<int, random_access_iterator_wrapper> y(x); + ranges::subrange rx{y.begin(), y.end()}; + auto v = rx | views::drop(2); + using R = decltype(v); + // P1739R4: The views::drop of a subrange is a subrange. + static_assert(std::same_as<R, decltype(rx)>); + VERIFY( ranges::equal(v, (int[]){3,4,5}) ); + VERIFY( ranges::empty(rx | views::drop(100)) ); +} + +void +test12() +{ + ranges::empty_view<int> rx; + auto v = rx | views::drop(1); + using R = decltype(v); + // P1739R4: The views::drop of an empty_view is an empty_view. + static_assert(std::same_as<R, ranges::empty_view<int>>); +} + +void +test13() +{ + int x[] = {1,2,3,4,5}; + auto it = std::counted_iterator(x, 4); + auto v = views::iota(it, std::default_sentinel) | views::drop(2); + using R = decltype(v); + static_assert(std::same_as<R, ranges::iota_view<std::counted_iterator<int*>, + std::default_sentinel_t>>); + VERIFY( ranges::size(v) == 2 ); + VERIFY( (*ranges::begin(v)).base() == x+2 ); + VERIFY( (*(ranges::begin(v)+1)).base() == x+3 ); +} + int main() { @@ -104,4 +193,11 @@ main() test04(); test05(); test06(); + test07(); + test08(); + test09(); + test10(); + test11(); + test12(); + test13(); } diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/take.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/take.cc index e2d2edbe0a8..3663f12aa68 100644 --- a/libstdc++-v3/testsuite/std/ranges/adaptors/take.cc +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/take.cc @@ -20,11 +20,15 @@ #include <algorithm> #include <ranges> +#include <span> +#include <string_view> #include <testsuite_hooks.h> #include <testsuite_iterators.h> using __gnu_test::test_range; +using __gnu_test::test_sized_range; using __gnu_test::bidirectional_iterator_wrapper; +using __gnu_test::random_access_iterator_wrapper; namespace ranges = std::ranges; namespace views = ranges::views; @@ -46,12 +50,9 @@ void test02() { auto v = views::take(views::iota(0, 20), 5); - static_assert(ranges::view<decltype(v)>); - static_assert(ranges::sized_range<decltype(v)>); - static_assert(ranges::common_range<decltype(v)>); - static_assert(ranges::random_access_range<decltype(v)>); - static_assert(!ranges::contiguous_range<decltype(v)>); - static_assert(ranges::range<const decltype(v)>); + using R = decltype(v); + // P1739R4: The views::take of an iota_view is an iota_view. + static_assert(std::same_as<R, ranges::iota_view<int, int>>); VERIFY( ranges::equal(v, (int[]){0,1,2,3,4}) ); } @@ -85,6 +86,71 @@ test04() VERIFY( ranges::equal(v | views::take(5), (int[]){1,2,3}) ); } +void +test05() +{ + int x[] = {1,2,3,4,5}; + std::span rx{x, 5}; + auto v = rx | views::take(3); + using R = decltype(v); + // P1739R4: The views::take of a dynamic-extent span is a dynamic-extent span. + static_assert(std::same_as<R, std::span<int>>); + VERIFY( ranges::equal(v, (int[]){1,2,3}) ); + VERIFY( ranges::equal(rx | views::take(100), rx) ); +} + +void +test06() +{ + using namespace std::literals; + std::string_view rx = "hello world!"; + auto v = rx | views::take(5); + using R = decltype(v); + // P1739R4: The views::take of a string_view is a string_view. + static_assert(std::same_as<R, std::string_view>); + VERIFY( ranges::equal(v, "hello"sv) ); + VERIFY( ranges::equal(rx | views::take(100), rx) ); +} + +void +test07() +{ + int x[] = {1,2,3,4,5}; + ranges::subrange rx{x, x+4}; + auto v = rx | views::take(3); + using R = decltype(v); + // P1739R4: The views::take of a subrange is a subrange. + static_assert(std::same_as<R, ranges::subrange<int*>>); + VERIFY( ranges::equal(v, (int[]){1,2,3}) ); + VERIFY( ranges::equal(rx | views::take(100), rx) ); +} + +void +test08() +{ + int x[] = {1,2,3,4,5}; + test_sized_range<int, random_access_iterator_wrapper> y(x); + ranges::subrange rx{y.begin(), y.end()}; + auto v = rx | views::take(3); + using R = decltype(v); + // P1739R4: The views::take of a subrange is a subrange. + static_assert(std::same_as<R, ranges::subrange<random_access_iterator_wrapper<int>>>); + // ... but not necessarily the same specialization as the input subrange + static_assert(!std::same_as<R, decltype(rx)>); + VERIFY( ranges::equal(v, (int[]){1,2,3}) ); + VERIFY( ranges::equal(rx | views::take(100), rx) ); +} + +void +test09() +{ + ranges::empty_view<int> rx; + auto v = rx | views::take(1); + using R = decltype(v); + // P1739R4: The views::take of an empty_view is an empty_view. + static_assert(std::same_as<R, ranges::empty_view<int>>); +} + int main() { @@ -92,4 +158,9 @@ main() test02(); test03(); test04(); + test05(); + test06(); + test07(); + test08(); + test09(); } -- 2.25.1.291.ge68e29171c