From 66d2a76dcf625f8dbe43d3c512e9c43f588fdc25 Mon Sep 17 00:00:00 2001 From: Patrick Palka Date: Thu, 23 May 2024 18:03:56 -0400 Subject: [PATCH] libstdc++: Implement ranges::concat_view from P2542R7 libstdc++-v3/ChangeLog: * include/bits/version.def (ranges_concat): Define. * include/bits/version.h: Regenerate. * include/std/ranges (__detail::__concat_reference_t): Define for C++26. (__detail::__concat_value_t): Likewise. (__detail::__concat_rvalue_reference_t): Likewise. (__detail::__concat_indirectly_readable_impl): Likewise. (__detail::__concat_indirectly_readable): Likewise. (__detail::__concatable): Likewise. (__detail::__all_but_last_common): Likewise. (__detail::__concat_is_random_access): Likewise. (__detail::__concat_is_bidirectional): Likewise. (__detail::__last_is_common): Likewise. (concat_view): Likewise. (__detail::__concat_view_iter_cat): Likewise. (concat_view::iterator): Likewise. (views::__detail::__can_concat_view): Likewise. (views::_Concat, views::concat): Likewise. * testsuite/std/ranges/concat/1.cc: New test. Reviewed-by: Jonathan Wakely --- libstdc++-v3/include/bits/version.def | 8 + libstdc++-v3/include/bits/version.h | 10 + libstdc++-v3/include/std/ranges | 580 ++++++++++++++++++ libstdc++-v3/testsuite/std/ranges/concat/1.cc | 74 +++ 4 files changed, 672 insertions(+) create mode 100644 libstdc++-v3/testsuite/std/ranges/concat/1.cc diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 5cbc9d1a8d85..683b967d54b1 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -1805,6 +1805,14 @@ ftms = { }; }; +ftms = { + name = ranges_concat; + values = { + v = 202403; + cxxmin = 26; + }; +}; + // Standard test specifications. stds[97] = ">= 199711L"; stds[03] = ">= 199711L"; diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index 164ebed49834..4850041c0a3e 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -2013,4 +2013,14 @@ #endif /* !defined(__cpp_lib_to_string) && defined(__glibcxx_want_to_string) */ #undef __glibcxx_want_to_string +#if !defined(__cpp_lib_ranges_concat) +# if (__cplusplus > 202302L) +# define __glibcxx_ranges_concat 202403L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_concat) +# define __cpp_lib_ranges_concat 202403L +# endif +# endif +#endif /* !defined(__cpp_lib_ranges_concat) && defined(__glibcxx_want_ranges_concat) */ +#undef __glibcxx_want_ranges_concat + #undef __glibcxx_want_all diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges index afce818376bb..b1e827c9a724 100644 --- a/libstdc++-v3/include/std/ranges +++ b/libstdc++-v3/include/std/ranges @@ -55,6 +55,7 @@ #define __glibcxx_want_ranges_as_const #define __glibcxx_want_ranges_as_rvalue #define __glibcxx_want_ranges_cartesian_product +#define __glibcxx_want_ranges_concat #define __glibcxx_want_ranges_chunk #define __glibcxx_want_ranges_chunk_by #define __glibcxx_want_ranges_enumerate @@ -9514,6 +9515,585 @@ namespace __detail } // namespace ranges #endif // __cpp_lib_ranges_to_container +#if __cpp_lib_ranges_concat // C++ >= C++26 +namespace ranges +{ + namespace __detail + { + template + using __concat_reference_t = common_reference_t...>; + + template + using __concat_value_t = common_type_t...>; + + template + using __concat_rvalue_reference_t + = common_reference_t...>; + + template + concept __concat_indirectly_readable_impl = requires(const _It __it) { + { *__it } -> convertible_to<_Ref>; + { ranges::iter_move(__it) } -> convertible_to<_RRef>; + }; + + template + concept __concat_indirectly_readable + = common_reference_with<__concat_reference_t<_Rs...>&&, __concat_value_t<_Rs...>&> + && common_reference_with<__concat_reference_t<_Rs...>&&, + __concat_rvalue_reference_t<_Rs...>&&> + && common_reference_with<__concat_rvalue_reference_t<_Rs...>&&, + __concat_value_t<_Rs...> const&> + && (__concat_indirectly_readable_impl<__concat_reference_t<_Rs...>, + __concat_rvalue_reference_t<_Rs...>, + iterator_t<_Rs>> + && ...); + + template + concept __concatable = requires { + typename __concat_reference_t<_Rs...>; + typename __concat_value_t<_Rs...>; + typename __concat_rvalue_reference_t<_Rs...>; + } && __concat_indirectly_readable<_Rs...>; + + template + struct __all_but_last_common + { + static inline constexpr bool value + = requires { requires (common_range<__maybe_const_t<_Const, _Range>> + && __all_but_last_common<_Const, _Rs...>::value); }; + }; + + template + struct __all_but_last_common<_Const, _Range> + { static inline constexpr bool value = true; }; + + template + concept __concat_is_random_access = __all_random_access<_Const, _Rs...> + && __all_but_last_common<_Const, _Rs...>::value; + + template + concept __concat_is_bidirectional = __all_bidirectional<_Const, _Rs...> + && __all_but_last_common<_Const, _Rs...>::value; + + template + struct __last_is_common + { static inline constexpr bool value = __last_is_common<_Rs...>::value; }; + + template + struct __last_is_common<_Range> + { static inline constexpr bool value = common_range<_Range>; }; + } // namespace __detail + + template + requires (view<_Vs> && ...) && (sizeof...(_Vs) > 0) && __detail::__concatable<_Vs...> + class concat_view : public view_interface> + { + tuple<_Vs...> _M_views; + + template class iterator; + + public: + constexpr concat_view() = default; + + constexpr explicit + concat_view(_Vs... __views) + : _M_views(std::move(__views)...) + { } + + constexpr iterator + begin() requires(!(__detail::__simple_view<_Vs> && ...)) + { + iterator __it(this, in_place_index<0>, ranges::begin(std::get<0>(_M_views))); + __it.template _M_satisfy<0>(); + return __it; + } + + constexpr iterator + begin() const requires (range && ...) && __detail::__concatable + { + iterator __it(this, in_place_index<0>, ranges::begin(std::get<0>(_M_views))); + __it.template _M_satisfy<0>(); + return __it; + } + + constexpr auto + end() requires(!(__detail::__simple_view<_Vs> && ...)) + { + if constexpr (__detail::__last_is_common<_Vs...>::value) + { + constexpr auto __n = sizeof...(_Vs); + return iterator(this, in_place_index<__n - 1>, + ranges::end(std::get<__n - 1>(_M_views))); + } + else + return default_sentinel; + } + + constexpr auto + end() const requires (range && ...) && __detail::__concatable + { + if constexpr (__detail::__last_is_common::value) + { + constexpr auto __n = sizeof...(_Vs); + return iterator(this, in_place_index<__n - 1>, + ranges::end(std::get<__n - 1>(_M_views))); + } + else + return default_sentinel; + } + + constexpr auto + size() requires (sized_range<_Vs>&&...) + { + return std::apply([](auto... __sizes) { + using _CT = __detail::__make_unsigned_like_t>; + return (_CT(__sizes) + ...); + }, __detail::__tuple_transform(ranges::size, _M_views)); + } + + constexpr auto + size() const requires (sized_range&&...) + { + return std::apply([](auto... __sizes) { + using _CT = __detail::__make_unsigned_like_t>; + return (_CT(__sizes) + ...); + }, __detail::__tuple_transform(ranges::size, _M_views)); + } + }; + + template + concat_view(_Rs&&...) -> concat_view...>; + + namespace __detail + { + template + struct __concat_view_iter_cat + { }; + + template + requires __detail::__all_forward<_Const, _Vs...> + struct __concat_view_iter_cat<_Const, _Vs...> + { + static auto + _S_iter_cat() + { + if constexpr (!is_reference_v<__concat_reference_t<__maybe_const_t<_Const, _Vs>...>>) + return input_iterator_tag{}; + else + return [](_Cats... __cats) { + if constexpr ((derived_from<_Cats, random_access_iterator_tag> && ...) + && __concat_is_random_access<_Const, _Vs...>) + return random_access_iterator_tag{}; + else if constexpr ((derived_from<_Cats, bidirectional_iterator_tag> && ...) + && __concat_is_bidirectional<_Const, _Vs...>) + return bidirectional_iterator_tag{}; + else if constexpr ((derived_from<_Cats, forward_iterator_tag> && ...)) + return forward_iterator_tag{}; + else + return input_iterator_tag{}; + }(typename iterator_traits>> + ::iterator_category{}...); + } + }; + } + + template + requires (view<_Vs> && ...) && (sizeof...(_Vs) > 0) && __detail::__concatable<_Vs...> + template + class concat_view<_Vs...>::iterator + : public __detail::__concat_view_iter_cat<_Const, _Vs...> + { + static auto + _S_iter_concept() + { + if constexpr (__detail::__concat_is_random_access<_Const, _Vs...>) + return random_access_iterator_tag{}; + else if constexpr (__detail::__concat_is_bidirectional<_Const, _Vs...>) + return bidirectional_iterator_tag{}; + else if constexpr (__detail::__all_forward<_Const, _Vs...>) + return forward_iterator_tag{}; + else + return input_iterator_tag{}; + } + + friend concat_view; + friend iterator; + + public: + // iterator_category defined in __concat_view_iter_cat + using iterator_concept = decltype(_S_iter_concept()); + using value_type = __detail::__concat_value_t<__maybe_const_t<_Const, _Vs>...>; + using difference_type = common_type_t>...>; + + private: + using __base_iter = variant>...>; + + __maybe_const_t<_Const, concat_view>* _M_parent = nullptr; + __base_iter _M_it; + + template + constexpr void + _M_satisfy() + { + if constexpr (_Nm < (sizeof...(_Vs) - 1)) + { + if (std::get<_Nm>(_M_it) == ranges::end(std::get<_Nm>(_M_parent->_M_views))) + { + _M_it.template emplace<_Nm + 1>(ranges::begin + (std::get<_Nm + 1>(_M_parent->_M_views))); + _M_satisfy<_Nm + 1>(); + } + } + } + + template + constexpr void + _M_prev() + { + if constexpr (_Nm == 0) + --std::get<0>(_M_it); + else + { + if (std::get<_Nm>(_M_it) == ranges::begin(std::get<_Nm>(_M_parent->_M_views))) + { + _M_it.template emplace<_Nm - 1>(ranges::end + (std::get<_Nm - 1>(_M_parent->_M_views))); + _M_prev<_Nm - 1>(); + } + else + --std::get<_Nm>(_M_it); + } + } + + template + constexpr void + _M_advance_fwd(difference_type __offset, difference_type __steps) + { + using _Dt = iter_difference_t>; + if constexpr (_Nm == sizeof...(_Vs) - 1) + std::get<_Nm>(_M_it) += static_cast<_Dt>(__steps); + else + { + auto __n_size = ranges::distance(std::get<_Nm>(_M_parent->_M_views)); + if (__offset + __steps < __n_size) + std::get<_Nm>(_M_it) += static_cast<_Dt>(__steps); + else + { + _M_it.template emplace<_Nm + 1>(ranges::begin + (std::get<_Nm + 1>(_M_parent->_M_views))); + _M_advance_fwd<_Nm + 1>(0, __offset + __steps - __n_size); + } + } + } + + template + constexpr void + _M_advance_bwd(difference_type __offset, difference_type __steps) + { + using _Dt = iter_difference_t>; + if constexpr (_Nm == 0) + std::get<_Nm>(_M_it) -= static_cast<_Dt>(__steps); + else { + if (__offset >= __steps) + std::get<_Nm>(_M_it) -= static_cast<_Dt>(__steps); + else + { + auto __prev_size = ranges::distance(std::get<_Nm - 1>(_M_parent->_M_views)); + _M_it.template emplace<_Nm - 1>(ranges::end + (std::get<_Nm - 1>(_M_parent->_M_views))); + _M_advance_bwd<_Nm - 1>(__prev_size, __steps - __offset); + } + } + } + + // Invoke the function object __f, which has a call operator with a size_t + // template parameter (corresponding to an index into the pack of views), + // using the runtime value of __index as the template argument. + template + static constexpr auto + _S_invoke_with_runtime_index(_Fp&& __f, size_t __index) + { + return [&__f, __index](this auto&& __self) { + if (_Idx == __index) + return __f.template operator()<_Idx>(); + if constexpr (_Idx + 1 < sizeof...(_Vs)) + return __self.template operator()<_Idx + 1>(); + }.template operator()<0>(); + } + + template + constexpr auto + _M_invoke_with_runtime_index(_Fp&& __f) + { return _S_invoke_with_runtime_index(std::forward<_Fp>(__f), _M_it.index()); } + + template + explicit constexpr + iterator(__maybe_const_t<_Const, concat_view>* __parent, _Args&&... __args) + requires constructible_from<__base_iter, _Args&&...> + : _M_parent(__parent), _M_it(std::forward<_Args>(__args)...) + { } + + public: + iterator() = default; + + constexpr + iterator(iterator __it) + requires _Const && (convertible_to, iterator_t> && ...) + : _M_parent(__it._M_parent) + { + _M_invoke_with_runtime_index([this, &__it]() { + _M_it.template emplace<_Idx>(std::get<_Idx>(std::move(__it._M_it))); + }); + } + + constexpr decltype(auto) + operator*() const + { + __glibcxx_assert(!_M_it.valueless_by_exception()); + using reference = __detail::__concat_reference_t<__maybe_const_t<_Const, _Vs>...>; + return std::visit([](auto&& __it) -> reference { return *__it; }, _M_it); + } + + constexpr iterator& + operator++() + { + _M_invoke_with_runtime_index([this]() { + ++std::get<_Idx>(_M_it); + _M_satisfy<_Idx>(); + }); + return *this; + } + + constexpr void + operator++(int) + { ++*this; } + + constexpr iterator + operator++(int) + requires __detail::__all_forward<_Const, _Vs...> + { + auto __tmp = *this; + ++*this; + return __tmp; + } + + constexpr iterator& + operator--() + requires __detail::__concat_is_bidirectional<_Const, _Vs...> + { + __glibcxx_assert(!_M_it.valueless_by_exception()); + _M_invoke_with_runtime_index([this]() { + _M_prev<_Idx>(); + }); + return *this; + } + + constexpr iterator + operator--(int) + requires __detail::__concat_is_bidirectional<_Const, _Vs...> + { + auto __tmp = *this; + --*this; + return __tmp; + } + + constexpr iterator& + operator+=(difference_type __n) + requires __detail::__concat_is_random_access<_Const, _Vs...> + { + __glibcxx_assert(!_M_it.valueless_by_exception()); + _M_invoke_with_runtime_index([this, __n]() { + auto __begin = ranges::begin(std::get<_Idx>(_M_parent->_M_views)); + if (__n > 0) + _M_advance_fwd<_Idx>(std::get<_Idx>(_M_it) - __begin, __n); + else if (__n < 0) + _M_advance_bwd<_Idx>(std::get<_Idx>(_M_it) - __begin, -__n); + }); + return *this; + } + + constexpr iterator& + operator-=(difference_type __n) + requires __detail::__concat_is_random_access<_Const, _Vs...> + { + *this += -__n; + return *this; + } + + constexpr decltype(auto) + operator[](difference_type __n) const + requires __detail::__concat_is_random_access<_Const, _Vs...> + { return *((*this) + __n); } + + friend constexpr bool + operator==(const iterator& __x, const iterator& __y) + requires (equality_comparable>> && ...) + { + __glibcxx_assert(!__x._M_it.valueless_by_exception()); + __glibcxx_assert(!__y._M_it.valueless_by_exception()); + return __x._M_it == __y._M_it; + } + + friend constexpr bool + operator==(const iterator& __it, default_sentinel_t) + { + __glibcxx_assert(!__it._M_it.valueless_by_exception()); + constexpr auto __last_idx = sizeof...(_Vs) - 1; + return (__it._M_it.index() == __last_idx + && (std::get<__last_idx>(__it._M_it) + == ranges::end(std::get<__last_idx>(__it._M_parent->_M_views)))); + } + + friend constexpr bool + operator<(const iterator& __x, const iterator& __y) + requires __detail::__all_random_access<_Const, _Vs...> + { return __x._M_it < __y._M_it; } + + friend constexpr bool + operator>(const iterator& __x, const iterator& __y) + requires __detail::__all_random_access<_Const, _Vs...> + { return __x._M_it > __y._M_it; } + + friend constexpr bool + operator<=(const iterator& __x, const iterator& __y) + requires __detail::__all_random_access<_Const, _Vs...> + { return __x._M_it <= __y._M_it; } + + friend constexpr bool + operator>=(const iterator& __x, const iterator& __y) + requires __detail::__all_random_access<_Const, _Vs...> + { return __x._M_it >= __y._M_it; } + + friend constexpr auto + operator<=>(const iterator& __x, const iterator& __y) + requires __detail::__all_random_access<_Const, _Vs...> + && (three_way_comparable>> && ...) + { return __x._M_it <=> __y._M_it; } + + friend constexpr iterator + operator+(const iterator& __it, difference_type __n) + requires __detail::__concat_is_random_access<_Const, _Vs...> + { return auto(__it) += __n; } + + friend constexpr iterator + operator+(difference_type __n, const iterator& __it) + requires __detail::__concat_is_random_access<_Const, _Vs...> + { return __it + __n; } + + friend constexpr iterator + operator-(const iterator& __it, difference_type __n) + requires __detail::__concat_is_random_access<_Const, _Vs...> + { return auto(__it) -= __n; } + + friend constexpr difference_type + operator-(const iterator& __x, const iterator& __y) + requires __detail::__concat_is_random_access<_Const, _Vs...> + { + return _S_invoke_with_runtime_index([&]() -> difference_type { + return _S_invoke_with_runtime_index([&]() -> difference_type { + if constexpr (_Ix > _Iy) + { + auto __dy = ranges::distance(std::get<_Iy>(__y._M_it), + ranges::end(std::get<_Iy>(__y._M_parent + ->_M_views))); + auto __dx = ranges::distance(ranges::begin(std::get<_Ix>(__x._M_parent + ->_M_views)), + std::get<_Ix>(__x._M_it)); + difference_type __s = 0; + [&](this auto&& __self) { + if constexpr (_Idx < _Ix) + { + __s += ranges::size(std::get<_Idx>(__x._M_parent->_M_views)); + __self.template operator()<_Idx + 1>(); + } + }(); + return __dy + __s + __dx; + } + else if constexpr (_Ix < _Iy) + return -(__y - __x); + else + return std::get<_Ix>(__x._M_it) - std::get<_Iy>(__y._M_it); + }, __y._M_it.index()); + }, __x._M_it.index()); + } + + friend constexpr difference_type + operator-(const iterator& __x, default_sentinel_t) + requires __detail::__concat_is_random_access<_Const, _Vs...> + && __detail::__last_is_common<__maybe_const_t<_Const, _Vs>...>::value + { + return _S_invoke_with_runtime_index([&]() -> difference_type { + auto __dx = ranges::distance(std::get<_Ix>(__x._M_it), + ranges::end(std::get<_Ix>(__x._M_parent->_M_views))); + difference_type __s = 0; + [&](this auto&& __self) { + if constexpr (_Idx < sizeof...(_Vs)) + { + __s += ranges::size(std::get<_Idx>(__x._M_parent->_M_views)); + __self.template operator()<_Idx + 1>(); + } + }(); + return -(__dx + __s); + }, __x._M_it.index()); + } + + friend constexpr difference_type + operator-(default_sentinel_t, const iterator& __x) + requires __detail::__concat_is_random_access<_Const, _Vs...> + && __detail::__last_is_common<__maybe_const_t<_Const, _Vs>...>::value + { return -(__x - default_sentinel); } + + friend constexpr decltype(auto) + iter_move(const iterator& __it) + { + using _Res = __detail::__concat_rvalue_reference_t<__maybe_const_t<_Const, _Vs>...>; + return std::visit([](const auto& __i) -> _Res { + return ranges::iter_move(__i); + }, __it._M_it); + } + + friend constexpr void + iter_swap(const iterator& __x, const iterator& __y) + requires swappable_with, iter_reference_t> + && (... && indirectly_swappable>>) + { + std::visit([&](const _Tp& __it1, const _Up& __it2) { + if constexpr (is_same_v<_Tp, _Up>) + ranges::iter_swap(__it1, __it2); + else + ranges::swap(*__it1, *__it2); + }, __x._M_it, __y._M_it); + } + }; + + namespace views + { + namespace __detail + { + template + concept __can_concat_view = requires { concat_view(std::declval<_Ts>()...); }; + } + + struct _Concat + { + template + requires __detail::__can_concat_view<_Ts...> + constexpr auto + operator() [[nodiscard]] (_Ts&&... __ts) const + { + if constexpr (sizeof...(_Ts) == 1) + return views::all(std::forward<_Ts>(__ts)...); + else + return concat_view(std::forward<_Ts>(__ts)...); + } + }; + + inline constexpr _Concat concat; + } + +} // namespace ranges +#endif // __cpp_lib_ranges_concat + _GLIBCXX_END_NAMESPACE_VERSION } // namespace std #endif // library concepts diff --git a/libstdc++-v3/testsuite/std/ranges/concat/1.cc b/libstdc++-v3/testsuite/std/ranges/concat/1.cc new file mode 100644 index 000000000000..6ffe28ece511 --- /dev/null +++ b/libstdc++-v3/testsuite/std/ranges/concat/1.cc @@ -0,0 +1,74 @@ +// { dg-do run { target c++26 } } +// { dg-add-options no_pch } + +#include + +#if __cpp_lib_ranges_concat != 202403L +# error "Feature-test macro __cpp_lib_ranges_concat has wrong value in " +#endif + +#include +#include +#include +#include +#include +#include + +namespace ranges = std::ranges; +namespace views = std::views; + +constexpr bool +test01() +{ + std::vector v1{1, 2, 3}, v2{4, 5}, v3{}; + std::array a{6, 7, 8}; + auto s = views::single(9); + + auto v = views::concat(v1, v2, v3, a, s); + VERIFY( ranges::size(v) == 9 ); + VERIFY( ranges::size(std::as_const(v)) == 9 ); + VERIFY( ranges::equal(v, views::iota(1, 10)) ); + VERIFY( ranges::equal(v | views::reverse, + views::iota(1, 10) | views::reverse) ); + + auto it0 = v.begin(); + auto cit = std::as_const(v).begin(); + VERIFY( it0 == it0 ); + VERIFY( cit == cit ); + VERIFY( it0 == cit ); + for (int i = 0; i < 10; i++) + { + VERIFY( it0 + i - it0 == i ); + VERIFY( it0 + i - (it0 + 1) == i - 1 ); + VERIFY( it0 + i - (it0 + 3) == i - 3 ); + VERIFY( it0 + i - (it0 + 5) == i - 5 ); + VERIFY( it0 + i - i + i == it0 + i ); + VERIFY( it0 + i - (it0 + i) == 0 ); + } + VERIFY( std::default_sentinel - it0 == 9 ); + VERIFY( it0 + 9 == std::default_sentinel ); + + auto it5 = it0+5; + ranges::iter_swap(it0, it5); + VERIFY( *it0 == 6 && *it5 == 1 ); + ranges::iter_swap(it0, it5); + *it0 = ranges::iter_move(it0); + return true; +} + +void +test02() +{ + int x[] = {1, 2, 3, 4, 5}; + __gnu_test::test_input_range rx(x); + auto v = views::concat(views::single(0), rx, views::empty); + static_assert(!ranges::forward_range); + VERIFY( ranges::equal(v | views::drop(1), x) ); +} + +int +main() +{ + static_assert(test01()); + test02(); +} -- 2.47.2