[PATCH] libstdc++: Implement C++23 P1659R3 starts_with and ends_with
Tomasz Kaminski
tkaminsk@redhat.com
Mon May 19 14:11:42 GMT 2025
On Mon, May 19, 2025 at 4:09 PM Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
> On Mon, May 19, 2025 at 4:02 PM Patrick Palka <ppalka@redhat.com> wrote:
>
>> On Mon, 19 May 2025, Tomasz Kaminski wrote:
>>
>> >
>> >
>> > On Mon, May 19, 2025 at 6:47 AM Patrick Palka <ppalka@redhat.com>
>> wrote:
>> > I would appreciate a short explanation on the approach being put here,
>> > in the message. Like passing -1 as means of saying, size not know.
>> >
>> > Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
>> >
>> > From the non-stylistic changes, I have noticed that we need some
>> explicit conversion between different types.
>> > I think we should add test that would check with size being
>> __max_diff_t, maybe we should add such a range to
>> > testutils_iterators.
>> > Rest of the of comments are mostly stylistic.
>> >
>> >
>> > -- >8 --
>> >
>> > libstdc++-v3/ChangeLog:
>> >
>> > * include/bits/ranges_algo.h (__starts_with_fn,
>> starts_with):
>> > Define.
>> > (__ends_with_fn, ends_with): Define.
>> > * include/bits/version.def (ranges_starts_ends_with):
>> Define.
>> > * include/bits/version.h: Regenerate.
>> > * include/std/algorithm: Provide
>> __cpp_lib_ranges_starts_ends_with.
>> > * src/c++23/std.cc.in (ranges::starts_with): Export.
>> > (ranges::ends_with): Export.
>> > * testsuite/25_algorithms/ends_with/1.cc: New test.
>> > * testsuite/25_algorithms/starts_with/1.cc: New test.
>> > ---
>> > libstdc++-v3/include/bits/ranges_algo.h | 232
>> ++++++++++++++++++
>> > libstdc++-v3/include/bits/version.def | 8 +
>> > libstdc++-v3/include/bits/version.h | 10 +
>> > libstdc++-v3/include/std/algorithm | 1 +
>> > libstdc++-v3/src/c++23/std.cc.in | 4 +
>> > .../testsuite/25_algorithms/ends_with/1.cc | 129 ++++++++++
>> > .../testsuite/25_algorithms/starts_with/1.cc | 128 ++++++++++
>> > 7 files changed, 512 insertions(+)
>> > create mode 100644
>> libstdc++-v3/testsuite/25_algorithms/ends_with/1.cc
>> > create mode 100644
>> libstdc++-v3/testsuite/25_algorithms/starts_with/1.cc
>> >
>> > diff --git a/libstdc++-v3/include/bits/ranges_algo.h
>> b/libstdc++-v3/include/bits/ranges_algo.h
>> > index f36e7dd59911..c59a555f528a 100644
>> > --- a/libstdc++-v3/include/bits/ranges_algo.h
>> > +++ b/libstdc++-v3/include/bits/ranges_algo.h
>> > @@ -438,6 +438,238 @@ namespace ranges
>> >
>> > inline constexpr __search_n_fn search_n{};
>> >
>> > +#if __glibcxx_ranges_starts_ends_with // C++ >= 23
>> > + struct __starts_with_fn
>> > + {
>> > + template<input_iterator _Iter1, sentinel_for<_Iter1> _Sent1,
>> > + input_iterator _Iter2, sentinel_for<_Iter2> _Sent2,
>> > + typename _Pred = ranges::equal_to,
>> > + typename _Proj1 = identity, typename _Proj2 =
>> identity>
>> > + requires indirectly_comparable<_Iter1, _Iter2, _Pred,
>> _Proj1, _Proj2>
>> > + constexpr bool
>> > + operator()(_Iter1 __first1, _Sent1 __last1,
>> > + _Iter2 __first2, _Sent2 __last2, _Pred __pred =
>> {},
>> > + _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const
>> > + {
>> > + iter_difference_t<_Iter1> __n1 = -1;
>> > + iter_difference_t<_Iter2> __n2 = -1;
>> > + if constexpr (sized_sentinel_for<_Sent1, _Iter1>)
>> > + __n1 = __last1 - __first1;
>> > + if constexpr (sized_sentinel_for<_Sent2, _Iter2>)
>> > + __n2 = __last2 - __first2;
>> > + return _S_impl(std::move(__first1), __last1,
>> > + std::move(__first2), __last2,
>> > + std::move(__pred),
>> > + std::move(__proj1), std::move(__proj2),
>> > + __n1, __n2);
>> > + }
>> > +
>> > + template<input_range _Range1, input_range _Range2,
>> > + typename _Pred = ranges::equal_to,
>> > + typename _Proj1 = identity, typename _Proj2 =
>> identity>
>> > + requires indirectly_comparable<iterator_t<_Range1>,
>> iterator_t<_Range2>,
>> > + _Pred, _Proj1, _Proj2>
>> > + constexpr bool
>> > + operator()(_Range1&& __r1, _Range2&& __r2, _Pred __pred =
>> {},
>> > + _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const
>> > + {
>> > + range_difference_t<_Range1> __n1 = -1;
>> > + range_difference_t<_Range1> __n2 = -1;
>> > + if constexpr (sized_range<_Range1>)
>> > + __n1 = ranges::size(__r1);
>> > + if constexpr (sized_range<_Range2>)
>> > + __n2 = ranges::size(__r2);
>> > + return _S_impl(ranges::begin(__r1), ranges::end(__r1),
>> > + ranges::begin(__r2), ranges::end(__r2),
>> > + std::move(__pred),
>> > + std::move(__proj1), std::move(__proj2),
>> > + __n1, __n2);
>> > + }
>> > +
>> > + template<typename _Iter1, typename _Sent1, typename _Iter2,
>> typename _Sent2,
>> > + typename _Pred,
>> > + typename _Proj1, typename _Proj2>
>> > + static constexpr bool
>> > + _S_impl(_Iter1 __first1, _Sent1 __last1,
>> >
>> > I think I would make this function private. The user should not look
>> into wrappers,
>> > but it does need to be public.
>> > I have slight preference for ordering arguments in following manner:
>> > first1, last1, n1,
>> > first2, last2, n2,
>>
>> Done.
>>
>> > + _Iter2 __first2, _Sent2 __last2,
>> > + _Pred __pred,
>> > + _Proj1 __proj1, _Proj2 __proj2,
>> > + iter_difference_t<_Iter1> __n1,
>> > + iter_difference_t<_Iter2> __n2)
>> > + {
>> >
>> > + if (__n1 != -1 && __n2 != -1)
>> >
>> > Very subjective, but I would other ifs, to not nest:
>> > if (__first2 == __last)
>> > return true;
>> > else if (n1 == -1 || n2 == -2)
>> > mismatch;
>> > else if (n1 < n2)
>> > return false;
>> > else if constexpr (random_access_iterator<_Iter1>)
>> > equal(first, first + n2, ....)
>> > else
>> > equal with counted.
>>
>> Nice, I like that too.
>>
>> >
>> > + {
>> > + if (__n1 < __n2)
>> > + return false;
>> > + if constexpr (random_access_iterator<_Iter1>)
>> > + return ranges::equal(__first1, __first1 + __n2,
>> >
>> > I think you need to cast __n2 to iter_difference_t<Iter1> here
>> explicitly,
>> > it must fit because it is smaller or equal to n1, but still there may
>> not be a candidate.
>>
>> Done, thanks for auditing this integer-class type stuff.
>>
>> > + std::move(__first2), __last2,
>> > + std::move(__pred),
>> > + std::move(__proj1),
>> std::move(__proj2));
>> > + else
>> > + return
>> ranges::equal(counted_iterator(std::move(__first1), __n2),
>> >
>> > Similar here, cast to iter_difference_t<Iter1>.
>>
>> Done.
>>
>>
>> > + default_sentinel,
>> > + std::move(__first2), __last2,
>> > + std::move(__pred),
>> > + std::move(__proj1),
>> std::move(__proj2));
>> > + }
>> > + else
>> > + return ranges::mismatch(std::move(__first1), __last1,
>> > + std::move(__first2), __last2,
>> > + std::move(__pred),
>> > + std::move(__proj1),
>> std::move(__proj2)).in2 == __last2;
>> > + }
>> > + };
>> > +
>> > + inline constexpr __starts_with_fn starts_with{};
>> > +
>> > + struct __ends_with_fn
>> > + {
>> > + template<input_iterator _Iter1, sentinel_for<_Iter1> _Sent1,
>> > + input_iterator _Iter2, sentinel_for<_Iter2> _Sent2,
>> > + typename _Pred = ranges::equal_to,
>> > + typename _Proj1 = identity, typename _Proj2 =
>> identity>
>> > + requires (forward_iterator<_Iter1> ||
>> sized_sentinel_for<_Sent1, _Iter1>)
>> > + && (forward_iterator<_Iter2> ||
>> sized_sentinel_for<_Sent2, _Iter2>)
>> > + && indirectly_comparable<_Iter1, _Iter2, _Pred, _Proj1,
>> _Proj2>
>> > + constexpr bool
>> > + operator()(_Iter1 __first1, _Sent1 __last1,
>> > + _Iter2 __first2, _Sent2 __last2, _Pred __pred =
>> {},
>> > + _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const
>> > + {
>> > + iter_difference_t<_Iter1> __n1 = -1;
>> > + iter_difference_t<_Iter2> __n2 = -1;
>> > + if constexpr (sized_sentinel_for<_Sent1, _Iter1>)
>> > + __n1 = __last1 - __first1;
>> > + if constexpr (sized_sentinel_for<_Sent2, _Iter2>)
>> > + __n2 = __last2 - __first2;
>> > + return _S_impl(std::move(__first1), __last1,
>> > + std::move(__first2), __last2,
>> > + std::move(__pred),
>> > + std::move(__proj1), std::move(__proj2),
>> > + __n1, __n2);
>> > + }
>> > +
>> > + template<input_range _Range1, input_range _Range2,
>> > + typename _Pred = ranges::equal_to,
>> > + typename _Proj1 = identity, typename _Proj2 =
>> identity>
>> > + requires (forward_range<_Range1> || sized_range<_Range1>)
>> > + && (forward_range<_Range2> || sized_range<_Range2>)
>> > + && indirectly_comparable<iterator_t<_Range1>,
>> iterator_t<_Range2>,
>> > + _Pred, _Proj1, _Proj2>
>> > + constexpr bool
>> > + operator()(_Range1&& __r1, _Range2&& __r2, _Pred __pred =
>> {},
>> > + _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const
>> > + {
>> > + range_difference_t<_Range1> __n1 = -1;
>> > + range_difference_t<_Range1> __n2 = -1;
>> > + if constexpr (sized_range<_Range1>)
>> > + __n1 = ranges::size(__r1);
>> > + if constexpr (sized_range<_Range2>)
>> > + __n2 = ranges::size(__r2);
>> > + return _S_impl(ranges::begin(__r1), ranges::end(__r1),
>> > + ranges::begin(__r2), ranges::end(__r2),
>> > + std::move(__pred),
>> > + std::move(__proj1), std::move(__proj2),
>> > + __n1, __n2);
>> > + }
>> > +
>> > + template<typename _Iter1, typename _Sent1,
>> > + typename _Iter2, typename _Sent2,
>> > + typename _Pred,
>> > + typename _Proj1, typename _Proj2>
>> > + static constexpr bool
>> > + _S_impl(_Iter1 __first1, _Sent1 __last1,
>> >
>> > Similar comments about the private, and other of arguments as for
>> above.,
>>
>> Done.
>>
>> > + _Iter2 __first2, _Sent2 __last2,
>> > + _Pred __pred,
>> > + _Proj1 __proj1, _Proj2 __proj2,
>> > + iter_difference_t<_Iter1> __n1,
>> > + iter_difference_t<_Iter2> __n2)
>> > + {
>> > + if (__n2 == -1)
>> > + {
>> > + if constexpr (forward_iterator<_Iter2>)
>> > + __n2 = ranges::distance(__first2, __last2);
>> > + else
>> > + // Size is known and passed by the caller.
>> > + __builtin_unreachable();
>> > + }
>> > +
>> > + if (__n1 != -1)
>> > + {
>> > + if (__n1 < __n2)
>> > + return false;
>> > + if constexpr (random_access_iterator<_Iter1>
>> > + || !bidirectional_iterator<_Iter1>)
>> > + {
>> > + ranges::advance(__first1, __n1 - __n2);
>> >
>> > I think you need to cast n2 to iter_difference_t<_Iter1> here
>> explicitly.
>>
>> Done.
>>
>> > + return ranges::equal(std::move(__first1), __last1,
>> > + std::move(__first2), __last2,
>> > + std::move(__pred),
>> > + std::move(__proj1),
>> std::move(__proj2));
>> > + }
>> > + }
>> > +
>> > + if constexpr (bidirectional_iterator<_Iter1>)
>> >
>> > + {
>> > + iter_difference_t<_Iter2> __m = __n2;
>> > + _Iter1 __it1 = ranges::next(__first1, __last1);
>> >
>> > Could you add a comment, that ranges::advance(__it, -__n2, __first)
>> cannot be used here,
>> > because __n2 may not fit into iter_difference<Iter1>?
>>
>> Good point, I think that's true in general, but if the size of the
>> haystack is known (i.e. __n1 != -1) then we already checked that
>> __n1 < __n2 earlier, so I think we can use ranges::advance (with
>> a cast) in that case.
>>
>> If __n1 == -1 then we can't use ranges::advance also because we need
>> to detect and return false if the first (haystack) range is smaller
>> than the second (needle) range, which IIUC we can't do with advance.
>>
> There is ranges::advance with sentinel (f, n, l) that I would use here.
>
But if we do not know if size of n1, then we cannot cast __n2 to
iter_difference_t<Iter1>,
because we do not know if it will fit. first large may use char as distance
type, and other long.
>
>> This made me realize that for a sized non-common bidirectional haystack,
>> we should instead use the sized haystack code path, since it's fewer
>> operations overall due to ranges::next(__first, __last1) in the
>> bidirectional code path having to traverse the entire range. So I
>> changed
>>
>> if constexpr (random_access_iterator<_Iter1>
>> || !bidirectional_iterator<_Iter1>)
>>
>> to
>>
>> if constexpr (random_access_iterator<_Iter1>
>> || !(bidirectional_iterator<_Iter1>
>> && same_as<_Iter1, _Sent1>))
>>
> Indeed, I was thinking about non-common bidirectional ranges, and if they
> should
> use forward_range code path, but for sure here it makes sense.
>
>
>>
>> > + while (__m != 0 && __it1 != __first1)
>> > + {
>> > + --__m;
>> > + --__it1;
>> > + }
>> > + if (__m != 0)
>> > + return false;
>> > + return ranges::equal(__it1, __last1,
>> > + std::move(__first2), __last2,
>> > + std::move(__pred),
>> > + std::move(__proj1),
>> std::move(__proj2));
>> > + }
>> > + else if constexpr (forward_iterator<_Iter1>)
>> > + {
>> > + if (__first2 == __last2)
>> > + return true;
>> >
>> > Could you make this if first in the _S_impl, and similarly for
>> starts_with,
>> > I think it will make it a lot easier to reason about it.
>>
>> Done.
>>
>> > +
>> > + _Iter1 __prev_first1;
>> > + __n1 = 0;
>> > + while (true)
>> > + {
>> > + iter_difference_t<_Iter2> __m = __n2;
>> > + _Iter1 __it1 = __first1;
>> > + while (__m != 0 && __it1 != __last1)
>> > + {
>> > + ++__n1;
>> > + --__m;
>> > + ++__it1;
>> > + }
>> >
>> >
>> > + if (__m != 0)
>> > + {
>> > + // __glibcxx_assert(__it1 == __last1);
>> > + if (__n1 < __n2)
>> > + return false;
>> > + ranges::advance(__prev_first1, __n2 - __m);
>> >
>> > Again we need to cast __n2 - __m to iter_difference_t<Iter1>.
>>
>> Done. I also combined this line and the next into one using
>> ranges::next:
>>
>> __first1 = ranges::next(__prev_first1,
>> iter_difference_t<_Iter1>(__n2 - __m));
>>
>> > + __first1 = __prev_first1;
>> > + break;
>> > + }
>> > + __prev_first1 = __first1;
>> > + __first1 = __it1;
>> > + }
>> > + return ranges::equal(__first1, __last1,
>> > + std::move(__first2), __last2,
>> > + std::move(__pred),
>> > + std::move(__proj1),
>> std::move(__proj2));
>> > + }
>> > + else
>> > + // Should have been handled by the __n1 != -1 case.
>> >
>> > Could you expand a comment here, adding something like:
>> > If _first_1 is input_iterot, it must be sized, so this was handled by
>> _n != 1 case
>>
>> Done.
>>
>> > + __builtin_unreachable();
>> > + }
>> > +
>> > + };
>> > +
>> > + inline constexpr __ends_with_fn ends_with{};
>> > +#endif // __glibcxx_ranges_starts_ends_with
>> > +
>> > struct __find_end_fn
>> > {
>> > template<forward_iterator _Iter1, sentinel_for<_Iter1>
>> _Sent1,
>> > diff --git a/libstdc++-v3/include/bits/version.def
>> b/libstdc++-v3/include/bits/version.def
>> > index 6ca148f0488f..8db1967cc184 100644
>> > --- a/libstdc++-v3/include/bits/version.def
>> > +++ b/libstdc++-v3/include/bits/version.def
>> > @@ -1660,6 +1660,14 @@ ftms = {
>> > };
>> > };
>> >
>> > +ftms = {
>> > + name = ranges_starts_ends_with;
>> > + values = {
>> > + v = 202106;
>> > + cxxmin = 23;
>> > + };
>> > +};
>> > +
>> > ftms = {
>> > name = constexpr_bitset;
>> > values = {
>> > diff --git a/libstdc++-v3/include/bits/version.h
>> b/libstdc++-v3/include/bits/version.h
>> > index 48a090c14a3d..3749528633f2 100644
>> > --- a/libstdc++-v3/include/bits/version.h
>> > +++ b/libstdc++-v3/include/bits/version.h
>> > @@ -1848,6 +1848,16 @@
>> > #endif /* !defined(__cpp_lib_ranges_find_last) &&
>> defined(__glibcxx_want_ranges_find_last) */
>> > #undef __glibcxx_want_ranges_find_last
>> >
>> > +#if !defined(__cpp_lib_ranges_starts_ends_with)
>> > +# if (__cplusplus >= 202100L)
>> > +# define __glibcxx_ranges_starts_ends_with 202106L
>> > +# if defined(__glibcxx_want_all) ||
>> defined(__glibcxx_want_ranges_starts_ends_with)
>> > +# define __cpp_lib_ranges_starts_ends_with 202106L
>> > +# endif
>> > +# endif
>> > +#endif /* !defined(__cpp_lib_ranges_starts_ends_with) &&
>> defined(__glibcxx_want_ranges_starts_ends_with) */
>> > +#undef __glibcxx_want_ranges_starts_ends_with
>> > +
>> > #if !defined(__cpp_lib_constexpr_bitset)
>> > # if (__cplusplus >= 202100L) && _GLIBCXX_HOSTED &&
>> (__cpp_constexpr_dynamic_alloc)
>> > # define __glibcxx_constexpr_bitset 202202L
>> > diff --git a/libstdc++-v3/include/std/algorithm
>> b/libstdc++-v3/include/std/algorithm
>> > index 321a5e22d86b..1563cdf2b17c 100644
>> > --- a/libstdc++-v3/include/std/algorithm
>> > +++ b/libstdc++-v3/include/std/algorithm
>> > @@ -74,6 +74,7 @@
>> > #define __glibcxx_want_ranges_contains
>> > #define __glibcxx_want_ranges_find_last
>> > #define __glibcxx_want_ranges_fold
>> > +#define __glibcxx_want_ranges_starts_ends_with
>> > #define __glibcxx_want_robust_nonmodifying_seq_ops
>> > #define __glibcxx_want_sample
>> > #define __glibcxx_want_shift
>> > diff --git a/libstdc++-v3/src/c++23/std.cc.in
>> b/libstdc++-v3/src/c++23/std.cc.in
>> > index 417c8a1a5626..2e4b3f75234f 100644
>> > --- a/libstdc++-v3/src/c++23/std.cc.in
>> > +++ b/libstdc++-v3/src/c++23/std.cc.in
>> > @@ -506,6 +506,10 @@ export namespace std
>> > using ranges::find_last;
>> > using ranges::find_last_if;
>> > using ranges::find_last_if_not;
>> > +#endif
>> > +#if __cpp_lib_ranges_starts_ends_with
>> > + using ranges::starts_with;
>> > + using ranges::ends_with;
>> > #endif
>> > }
>> > }
>> > diff --git a/libstdc++-v3/testsuite/25_algorithms/ends_with/1.cc
>> b/libstdc++-v3/testsuite/25_algorithms/ends_with/1.cc
>> > new file mode 100644
>> > index 000000000000..e5714101aecc
>> > --- /dev/null
>> > +++ b/libstdc++-v3/testsuite/25_algorithms/ends_with/1.cc
>> > @@ -0,0 +1,129 @@
>> > +// { dg-do run { target c++23 } }
>> > +
>> > +#include <algorithm>
>> > +
>> > +#include <testsuite_hooks.h>
>> > +#include <testsuite_iterators.h>
>> > +
>> > +namespace ranges = std::ranges;
>> > +
>> > +template<typename Range1, typename Range2>
>> > +void
>> > +test01()
>> > +{
>> > + int n[] = {1,2,3,4,5,6,7,8,9,10};
>> > +
>> > + Range1 haystack(n, n+10);
>> > + Range2 needle(n+7, n+10);
>> > + VERIFY( ranges::ends_with(haystack, needle) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n, n+10);
>> > + VERIFY( ranges::ends_with(haystack, needle) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+6, n+9);
>> > + VERIFY( !ranges::ends_with(haystack, needle) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+6, n+9);
>> > + VERIFY( ranges::ends_with(haystack, needle,
>> > + [](int n, int m) { return std::abs(n
>> - m) <= 1; }) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+6, n+9);
>> > + VERIFY( ranges::ends_with(haystack, needle,
>> > + ranges::equal_to{},
>> > + [](int n) { return n - 1; }) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+6, n+9);
>> > + VERIFY( ranges::ends_with(haystack, needle,
>> > + ranges::equal_to{},
>> > + std::identity{},
>> > + [](int n) { return n + 1; }) );
>> > +
>> > + haystack = Range1(n, n+5);
>> > + needle = Range2(n, n+10);
>> > + VERIFY( !ranges::ends_with(haystack, needle) );
>> > +}
>> > +
>> > +template<typename Range1, typename Range2>
>> > +void
>> > +test02()
>> > +{
>> > + int n[] = {1,2,3,4,5,6,7,8,9,10};
>> > +
>> > + Range1 haystack(n, n+10);
>> > + Range2 needle(n+7, n+10);
>> > + VERIFY( ranges::ends_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end()) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n, n+10);
>> > + VERIFY( ranges::ends_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end()) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+6, n+9);
>> > + VERIFY( !ranges::ends_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end()) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+6, n+9);
>> > + VERIFY( ranges::ends_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end(),
>> > + [](int n, int m) { return std::abs(n
>> - m) <= 1; }) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+6, n+9);
>> > + VERIFY( ranges::ends_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end(),
>> > + ranges::equal_to{},
>> > + [](int n) { return n - 1; }) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+6, n+9);
>> > + VERIFY( ranges::ends_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end(),
>> > + ranges::equal_to{},
>> > + std::identity{},
>> > + [](int n) { return n + 1; }) );
>> > +
>> > + haystack = Range1(n, n+5);
>> > + needle = Range2(n, n+10);
>> > + VERIFY( !ranges::ends_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end()) );
>> > +
>> > + haystack = Range1(n, n+5);
>> > + needle = Range2(n+10, n+10);
>> > + VERIFY( ranges::ends_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end()) );
>> > +}
>> >
>> > Could you add the usual test with a second range using __max_diff_t as
>> difference type.
>>
>> What would be the best way to test the integer-class type situation for
>> all the various code paths (non-forward, non-bidirectional,
>> non-random-access etc)?
>>
>> > +
>> > +int
>> > +main()
>> > +{
>> > + using namespace __gnu_test;
>> > + using forward = test_forward_range<int>;
>> > + using input_sized = test_input_sized_range<int>;
>> > + using input_sized_sent = test_sized_range_sized_sent<int,
>> input_iterator_wrapper>;
>> > + using random_access = test_random_access_range<int>;
>> > + using random_access_sized =
>> test_random_access_sized_range<int>;
>> > + using random_access_sized_sent =
>> test_sized_range_sized_sent<int, random_access_iterator_wrapper>;
>> > +
>> > + test01<forward, forward>();
>> > + test01<random_access, random_access>();
>> > + test02<forward, forward>();
>> > + test02<random_access, random_access>();
>> > +
>> > + test01<input_sized, input_sized>();
>> > + test01<random_access_sized, random_access_sized>();
>> > + // test02<input_sized, input_sized>(); constraint violation
>> > + test02<random_access_sized, random_access_sized>();
>> > +
>> > + test01<input_sized_sent, input_sized_sent>();
>> > + test01<random_access_sized_sent, random_access_sized_sent>();
>> > + test02<input_sized_sent, input_sized_sent>();
>> > + test02<random_access_sized_sent, random_access_sized_sent>();
>> > +}
>> > diff --git
>> a/libstdc++-v3/testsuite/25_algorithms/starts_with/1.cc
>> b/libstdc++-v3/testsuite/25_algorithms/starts_with/1.cc
>> > new file mode 100644
>> > index 000000000000..805f31ea2b03
>> > --- /dev/null
>> > +++ b/libstdc++-v3/testsuite/25_algorithms/starts_with/1.cc
>> > @@ -0,0 +1,128 @@
>> > +// { dg-do run { target c++23 } }
>> > +
>> > +#include <algorithm>
>> > +
>> > +#include <testsuite_hooks.h>
>> > +#include <testsuite_iterators.h>
>> > +
>> > +namespace ranges = std::ranges;
>> > +
>> > +template<typename Range1, typename Range2>
>> > +void
>> > +test01()
>> > +{
>> > + int n[] = {1,2,3,4,5,6,7,8,9,10};
>> > +
>> > + Range1 haystack(n, n+10);
>> > + Range2 needle(n, n+3);
>> > + VERIFY( ranges::starts_with(haystack, needle) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n, n+10);
>> > + VERIFY( ranges::starts_with(haystack, needle) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+1, n+4);
>> > + VERIFY( !ranges::starts_with(haystack, needle) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+1, n+4);
>> > + VERIFY( ranges::starts_with(haystack, needle,
>> > + [](int n, int m) { return
>> std::abs(n - m) <= 1; }) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+1, n+4);
>> > + VERIFY( ranges::starts_with(haystack, needle,
>> > + ranges::equal_to{},
>> > + [](int n) { return n + 1; }) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+1, n+4);
>> > + VERIFY( ranges::starts_with(haystack, needle,
>> > + ranges::equal_to{},
>> > + std::identity{},
>> > + [](int n) { return n - 1; }) );
>> > +
>> > + haystack = Range1(n, n+5);
>> > + needle = Range2(n, n+10);
>> > + VERIFY( !ranges::starts_with(haystack, needle) );
>> > +
>> > + haystack = Range1(n, n+5);
>> > + needle = Range2(n+10, n+10);
>> > + VERIFY( ranges::starts_with(haystack, needle) );
>> > +}
>> > +
>> > +template<typename Range1, typename Range2>
>> > +void
>> > +test02()
>> > +{
>> > + int n[] = {1,2,3,4,5,6,7,8,9,10};
>> > +
>> > + Range1 haystack(n, n+10);
>> > + Range2 needle(n, n+3);
>> > + VERIFY( ranges::starts_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end()) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n, n+10);
>> > + VERIFY( ranges::starts_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end()) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+1, n+4);
>> > + VERIFY( !ranges::starts_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end()) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+1, n+4);
>> > + VERIFY( ranges::starts_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end(),
>> > + [](int n, int m) { return
>> std::abs(n - m) <= 1; }) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+1, n+4);
>> > + VERIFY( ranges::starts_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end(),
>> > + ranges::equal_to{},
>> > + [](int n) { return n + 1; }) );
>> > +
>> > + haystack = Range1(n);
>> > + needle = Range2(n+1, n+4);
>> > + VERIFY( ranges::starts_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end(),
>> > + ranges::equal_to{},
>> > + std::identity{},
>> > + [](int n) { return n - 1; }) );
>> > +
>> > + haystack = Range1(n, n+5);
>> > + needle = Range2(n, n+10);
>> > + VERIFY( !ranges::starts_with(haystack.begin(), haystack.end(),
>> > + needle.begin(), needle.end()) );
>> > +}
>> > +
>> > +int
>> > +main()
>> > +{
>> > + using namespace __gnu_test;
>> > + using input = test_input_range<int>;
>> > + using input_sized = test_input_sized_range<int>;
>> > + using input_sized_sent = test_sized_range_sized_sent<int,
>> input_iterator_wrapper>;
>> > + using random_access = test_random_access_range<int>;
>> > + using random_access_sized =
>> test_random_access_sized_range<int>;
>> > + using random_access_sized_sent =
>> test_sized_range_sized_sent<int, random_access_iterator_wrapper>;
>> > +
>> > + test01<input, input>();
>> > + test01<random_access, random_access>();
>> > + test02<input, input>();
>> > + test02<random_access, random_access>();
>> > +
>> > + test01<input_sized, input_sized>();
>> > + test01<random_access_sized, random_access_sized>();
>> > + test02<input_sized, input_sized>();
>> > + test02<random_access_sized, random_access_sized>();
>> > +
>> > + test01<input_sized_sent, input_sized_sent>();
>> > + test01<random_access_sized_sent, random_access_sized_sent>();
>> > + test02<input_sized_sent, input_sized_sent>();
>> > + test02<random_access_sized_sent, random_access_sized_sent>();
>> > +}
>> > --
>> > 2.49.0.608.gcb96e1697a
>> >
>> >
>> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20250519/1d2fb232/attachment-0001.htm>
More information about the Libstdc++
mailing list