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 <jwakely@redhat.com>
This commit is contained in:
Patrick Palka 2024-05-23 18:03:56 -04:00
parent 83bb9ad465
commit 66d2a76dcf
4 changed files with 672 additions and 0 deletions

View file

@ -1805,6 +1805,14 @@ ftms = {
};
};
ftms = {
name = ranges_concat;
values = {
v = 202403;
cxxmin = 26;
};
};
// Standard test specifications.
stds[97] = ">= 199711L";
stds[03] = ">= 199711L";

View file

@ -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

View file

@ -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<typename... _Rs>
using __concat_reference_t = common_reference_t<range_reference_t<_Rs>...>;
template<typename... _Rs>
using __concat_value_t = common_type_t<range_value_t<_Rs>...>;
template<typename... _Rs>
using __concat_rvalue_reference_t
= common_reference_t<range_rvalue_reference_t<_Rs>...>;
template<typename _Ref, typename _RRef, typename _It>
concept __concat_indirectly_readable_impl = requires(const _It __it) {
{ *__it } -> convertible_to<_Ref>;
{ ranges::iter_move(__it) } -> convertible_to<_RRef>;
};
template<typename... _Rs>
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<typename... _Rs>
concept __concatable = requires {
typename __concat_reference_t<_Rs...>;
typename __concat_value_t<_Rs...>;
typename __concat_rvalue_reference_t<_Rs...>;
} && __concat_indirectly_readable<_Rs...>;
template<bool _Const, typename _Range, typename... _Rs>
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<bool _Const, typename _Range>
struct __all_but_last_common<_Const, _Range>
{ static inline constexpr bool value = true; };
template<bool _Const, typename... _Rs>
concept __concat_is_random_access = __all_random_access<_Const, _Rs...>
&& __all_but_last_common<_Const, _Rs...>::value;
template<bool _Const, typename... _Rs>
concept __concat_is_bidirectional = __all_bidirectional<_Const, _Rs...>
&& __all_but_last_common<_Const, _Rs...>::value;
template<typename _Range, typename... _Rs>
struct __last_is_common
{ static inline constexpr bool value = __last_is_common<_Rs...>::value; };
template<typename _Range>
struct __last_is_common<_Range>
{ static inline constexpr bool value = common_range<_Range>; };
} // namespace __detail
template<input_range... _Vs>
requires (view<_Vs> && ...) && (sizeof...(_Vs) > 0) && __detail::__concatable<_Vs...>
class concat_view : public view_interface<concat_view<_Vs...>>
{
tuple<_Vs...> _M_views;
template<bool _Const> class iterator;
public:
constexpr concat_view() = default;
constexpr explicit
concat_view(_Vs... __views)
: _M_views(std::move(__views)...)
{ }
constexpr iterator<false>
begin() requires(!(__detail::__simple_view<_Vs> && ...))
{
iterator<false> __it(this, in_place_index<0>, ranges::begin(std::get<0>(_M_views)));
__it.template _M_satisfy<0>();
return __it;
}
constexpr iterator<true>
begin() const requires (range<const _Vs> && ...) && __detail::__concatable<const _Vs...>
{
iterator<true> __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<false>(this, in_place_index<__n - 1>,
ranges::end(std::get<__n - 1>(_M_views)));
}
else
return default_sentinel;
}
constexpr auto
end() const requires (range<const _Vs> && ...) && __detail::__concatable<const _Vs...>
{
if constexpr (__detail::__last_is_common<const _Vs...>::value)
{
constexpr auto __n = sizeof...(_Vs);
return iterator<true>(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<common_type_t<decltype(__sizes)...>>;
return (_CT(__sizes) + ...);
}, __detail::__tuple_transform(ranges::size, _M_views));
}
constexpr auto
size() const requires (sized_range<const _Vs>&&...)
{
return std::apply([](auto... __sizes) {
using _CT = __detail::__make_unsigned_like_t<common_type_t<decltype(__sizes)...>>;
return (_CT(__sizes) + ...);
}, __detail::__tuple_transform(ranges::size, _M_views));
}
};
template<typename... _Rs>
concat_view(_Rs&&...) -> concat_view<views::all_t<_Rs>...>;
namespace __detail
{
template<bool _Const, typename... _Vs>
struct __concat_view_iter_cat
{ };
template<bool _Const, typename... _Vs>
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 []<typename... _Cats>(_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_t<__maybe_const_t<_Const, _Vs>>>
::iterator_category{}...);
}
};
}
template<input_range... _Vs>
requires (view<_Vs> && ...) && (sizeof...(_Vs) > 0) && __detail::__concatable<_Vs...>
template<bool _Const>
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<!_Const>;
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<range_difference_t<__maybe_const_t<_Const, _Vs>>...>;
private:
using __base_iter = variant<iterator_t<__maybe_const_t<_Const, _Vs>>...>;
__maybe_const_t<_Const, concat_view>* _M_parent = nullptr;
__base_iter _M_it;
template<size_t _Nm>
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<size_t _Nm>
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<size_t _Nm>
constexpr void
_M_advance_fwd(difference_type __offset, difference_type __steps)
{
using _Dt = iter_difference_t<variant_alternative_t<_Nm, __base_iter>>;
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<size_t _Nm>
constexpr void
_M_advance_bwd(difference_type __offset, difference_type __steps)
{
using _Dt = iter_difference_t<variant_alternative_t<_Nm, __base_iter>>;
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<typename _Fp>
static constexpr auto
_S_invoke_with_runtime_index(_Fp&& __f, size_t __index)
{
return [&__f, __index]<size_t _Idx>(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<typename _Fp>
constexpr auto
_M_invoke_with_runtime_index(_Fp&& __f)
{ return _S_invoke_with_runtime_index(std::forward<_Fp>(__f), _M_it.index()); }
template<typename... _Args>
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<!_Const> __it)
requires _Const && (convertible_to<iterator_t<_Vs>, iterator_t<const _Vs>> && ...)
: _M_parent(__it._M_parent)
{
_M_invoke_with_runtime_index([this, &__it]<size_t _Idx>() {
_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]<size_t _Idx>() {
++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]<size_t _Idx>() {
_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]<size_t _Idx>() {
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<iterator_t<__maybe_const_t<_Const, _Vs>>> && ...)
{
__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<iterator_t<__maybe_const_t<_Const, _Vs>>> && ...)
{ 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([&]<size_t _Ix>() -> difference_type {
return _S_invoke_with_runtime_index([&]<size_t _Iy>() -> 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;
[&]<size_t _Idx = _Iy + 1>(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([&]<size_t _Ix>() -> 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;
[&]<size_t _Idx = _Ix + 1>(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<iterator>, iter_reference_t<iterator>>
&& (... && indirectly_swappable<iterator_t<__maybe_const_t<_Const, _Vs>>>)
{
std::visit([&]<typename _Tp, typename _Up>(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<typename... _Ts>
concept __can_concat_view = requires { concat_view(std::declval<_Ts>()...); };
}
struct _Concat
{
template<typename... _Ts>
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

View file

@ -0,0 +1,74 @@
// { dg-do run { target c++26 } }
// { dg-add-options no_pch }
#include <ranges>
#if __cpp_lib_ranges_concat != 202403L
# error "Feature-test macro __cpp_lib_ranges_concat has wrong value in <ranges>"
#endif
#include <algorithm>
#include <vector>
#include <array>
#include <utility>
#include <testsuite_hooks.h>
#include <testsuite_iterators.h>
namespace ranges = std::ranges;
namespace views = std::views;
constexpr bool
test01()
{
std::vector<int> 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<int>);
static_assert(!ranges::forward_range<decltype(v)>);
VERIFY( ranges::equal(v | views::drop(1), x) );
}
int
main()
{
static_assert(test01());
test02();
}