On Thu, 22 May 2025 at 16:48, Jonathan Wakely <jwak...@redhat.com> wrote: > > On Thu, 22 May 2025 at 16:44, Jonathan Wakely <jwak...@redhat.com> wrote: > > > > On Thu, 22 May 2025 at 16:25, Tomasz Kaminski <tkami...@redhat.com> wrote: > > > > > > > > > > > > On Thu, May 22, 2025 at 5:15 PM Tomasz Kaminski <tkami...@redhat.com> > > > wrote: > > >> > > >> > > >> > > >> On Thu, May 22, 2025 at 5:04 PM Jonathan Wakely <jwak...@redhat.com> > > >> wrote: > > >>> > > >>> On Thu, 22 May 2025 at 15:50, Tomasz Kaminski <tkami...@redhat.com> > > >>> wrote: > > >>> > > > >>> > > > >>> > > > >>> > On Thu, May 22, 2025 at 1:42 PM Jonathan Wakely <jwak...@redhat.com> > > >>> > wrote: > > >>> >> > > >>> >> The current overload set for __unique_copy handles three cases: > > >>> >> > > >>> >> - The input range uses forward iterators, the output range does not. > > >>> >> This is the simplest case, and can just compare adjacent elements > > >>> >> of > > >>> >> the input range. > > >>> >> > > >>> >> - Neither the input range nor output range use forward iterators. > > >>> >> This requires a local variable copied from the input range and > > >>> >> updated > > >>> >> by assigning each element to the local variable. > > >>> >> > > >>> >> - The output range uses forward iterators. > > >>> >> For this case we compare the current element from the input range > > >>> >> with > > >>> >> the element just written to the output range. > > >>> >> > > >>> >> There are two problems with this implementation. Firstly, the third > > >>> >> case > > >>> >> assumes that the value type of the output range can be compared to > > >>> >> the > > >>> >> value type of the input range, which might not be possible at all, or > > >>> >> might be possible but give different results to comparing elements of > > >>> >> the input range. This is the problem identified in LWG 2439. > > >>> >> > > >>> >> Secondly, the third case is used when both ranges use forward > > >>> >> iterators, > > >>> >> even though the first case could (and should) be used. This means > > >>> >> that > > >>> >> we compare elements from the output range instead of the input range, > > >>> >> with the problems described above (either not well-formed, or might > > >>> >> give > > >>> >> the wrong results). > > >>> >> > > >>> >> The cause of the second problem is that the overload for the first > > >>> >> case > > >>> >> looks like: > > >>> >> > > >>> >> OutputIterator > > >>> >> __unique_copy(ForwardIter, ForwardIter, OutputIterator, BinaryPred, > > >>> >> forward_iterator_tag, output_iterator_tag); > > >>> >> > > >>> >> When the output range uses forward iterators this overload cannot be > > >>> >> used, because forward_iterator_tag does not inherit from > > >>> >> output_iterator_tag, so is not convertible to it. > > >>> >> > > >>> >> To fix these problems we need to implement the resolution of LWG > > >>> >> 2439 so > > >>> >> that the third case is only used when the value types of the two > > >>> >> ranges > > >>> >> are the same. This ensures that the comparisons are well behaved. We > > >>> >> also need to ensure that the first case is used when both ranges use > > >>> >> forward iterators. > > >>> >> > > >>> >> This change replaces a single step of tag dispatching to choose > > >>> >> between > > >>> >> three overloads with two step of tag dispatching, choosing between > > >>> >> two > > >>> >> overloads at each step. The first step dispatches based on the > > >>> >> iterator > > >>> >> category of the input range, ignoring the category of the output > > >>> >> range. > > >>> >> The second step only happens when the input range uses non-forward > > >>> >> iterators, and dispatches based on the category of the output range > > >>> >> and > > >>> >> whether the value type of the two ranges is the same. So now the > > >>> >> cases > > >>> >> that are handled are: > > >>> >> > > >>> >> - The input range uses forward iterators. > > >>> >> - The output range uses non-forward iterators or a different value > > >>> >> type. > > >>> >> - The output range uses forward iterators and has the same value > > >>> >> type. > > >>> >> > > >>> >> For the second case, the old code used > > >>> >> __gnu_cxx::__ops::__iter_comp_val > > >>> >> to wrap the predicate in another level of indirection. That seems > > >>> >> unnecessary, as we can just use a pointer to the local variable > > >>> >> instead > > >>> >> of an iterator referring to it. > > >>> >> > > >>> >> libstdc++-v3/ChangeLog: > > >>> >> > > >>> >> PR libstdc++/120386 > > >>> >> * include/bits/stl_algo.h (__unique_copy_1): New overloads > > >>> >> for > > >>> >> the case where the input range uses non-forward iterators. > > >>> >> (__unique_copy): Replace three overloads with two, depending > > >>> >> only on the iterator category of the input range. Dispatch to > > >>> >> __unique_copy_1 for the non-forward case. > > >>> >> (unique_copy): Only pass the input range category to > > >>> >> __unique_copy. > > >>> >> --- > > >>> >> > > >>> >> Tested x86_64-linux. > > >>> > > > >>> > LGTM. Only small suggestion, regarding the change of order of > > >>> > arguments. > > >>> > > >>> I forgot to say that I need to add tests for each of the cases, > > >>> especially the case that fails with the existing code! > > >>> > > >>> >> > > >>> >> > > >>> >> libstdc++-v3/include/bits/stl_algo.h | 80 > > >>> >> +++++++++++++++------------- > > >>> >> 1 file changed, 44 insertions(+), 36 deletions(-) > > >>> >> > > >>> >> diff --git a/libstdc++-v3/include/bits/stl_algo.h > > >>> >> b/libstdc++-v3/include/bits/stl_algo.h > > >>> >> index f5361aeab7e2..c0bb17f9c8b2 100644 > > >>> >> --- a/libstdc++-v3/include/bits/stl_algo.h > > >>> >> +++ b/libstdc++-v3/include/bits/stl_algo.h > > >>> >> @@ -918,24 +918,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > >>> >> > > >>> >> __gnu_cxx::__ops::__iter_comp_iter(__binary_pred)); > > >>> >> } > > >>> >> > > >>> >> - /** > > >>> >> - * This is an uglified > > >>> >> - * unique_copy(_InputIterator, _InputIterator, _OutputIterator, > > >>> >> - * _BinaryPredicate) > > >>> >> - * overloaded for forward iterators and output iterator as > > >>> >> result. > > >>> >> - */ > > >>> >> + // Implementation of std::unique_copy for forward iterators. > > >>> >> + // This case is easy, just compare *i with *(i-1). > > >>> >> template<typename _ForwardIterator, typename _OutputIterator, > > >>> >> typename _BinaryPredicate> > > >>> >> _GLIBCXX20_CONSTEXPR > > >>> >> _OutputIterator > > >>> >> __unique_copy(_ForwardIterator __first, _ForwardIterator __last, > > >>> >> _OutputIterator __result, _BinaryPredicate > > >>> >> __binary_pred, > > >>> >> - forward_iterator_tag, output_iterator_tag) > > >>> >> + forward_iterator_tag) > > >>> >> { > > >>> >> _ForwardIterator __next = __first; > > >>> >> *__result = *__first; > > >>> >> while (++__next != __last) > > >>> >> - if (!__binary_pred(__first, __next)) > > >>> >> + if (!__binary_pred(__next, __first)) > > >>> > > > >>> > I would prefer if you will not do this change, and pass iterators > > >>> > that were already seen as the first argument. > > >>> > > >>> The standard seems clear that it should be bool(pred(*i, *(i - 1))) > > >>> In theory a predicate could depend on that. > > >> > > >> Oh, indeed. > > >>> > > >>> > > >>> > Note that the forward-output overload, preserves this order: > > >>> > *__result = *__first; > > >>> > while (++__first != __last) > > >>> > if (!__binary_pred(__result, __first)) > > >>> > > >>> Ah yes, well I should have changed that too ;-) > > >>> > > >>> What's your reason for preferring the current order? > > >> > > >> That what I would intuitively expect, that left argument is an element > > >> that is left to right argument. > > >> And if you sorted range with predicate lt, then passing not_fn(lt) is > > >> equivalent to checking equality. > > > > > > It seems that I have built my expectation based on unique, that check > > > i-1, i. > > > https://eel.is/c++draft/alg.unique#1 > > > > The standard seems clear, however, all three of libstdc++, libc++ and > > MSVC use the "wrong" order in unique_copy. > > > > So I think the standard should be changed. > > The docs for the SGI STL matched the standard: > https://web.archive.org/web/20161012091520/http://www.sgi.com/tech/stl/unique_copy.html > > But the actual SGI STL implementation (which libstdc++ is based on) > does not match its own docs: > https://web.archive.org/web/20170429054511/http://www.sgi.com/tech/stl/stl_algo.h > > My copy of "The C++ Standard Template Library" by Plauger, Stepanov, > Lee and Musser is in a box somewhere in storage, but what it describes > probably matches the MSVC STL.
Our ranges::unique_copy is inconsistent, but I think we should do this to make it consistently "wrong": --- a/libstdc++-v3/include/bits/ranges_algo.h +++ b/libstdc++-v3/include/bits/ranges_algo.h @@ -1250,8 +1250,8 @@ namespace ranges while (++__first != __last) { if (!(bool)std::__invoke(__comp, - std::__invoke(__proj, *__first), - std::__invoke(__proj, __value))) + std::__invoke(__proj, __value)), + std::__invoke(__proj, *__first)) { __value = *__first; *++__result = __value; MSVC ranges::unique_copy is "wrong", and libc++ ranges::unique_copy uses the same impl as their std::unique_copy so is "wrong". range-v3 is "wrong": https://github.com/ericniebler/range-v3/blob/ca1388fb9da8e69314dda222dc7b139ca84e092f/include/range/v3/algorithm/unique_copy.hpp#L46 But cmcstl2 is "right", i.e. matches the standard https://github.com/CaseyCarter/cmcstl2/blob/a7a714a9159b08adeb00a193e77b782846b3b20e/include/stl2/detail/algorithm/unique_copy.hpp#L30