On Tue, Jul 15, 2025 at 11:56 AM Jonathan Wakely <jwak...@redhat.com> wrote:

> On Tue, 15 Jul 2025 at 09:25, Tomasz Kaminski <tkami...@redhat.com> wrote:
> >
> >
> >
> > On Mon, Jul 14, 2025 at 10:43 PM Jonathan Wakely <jwak...@redhat.com>
> wrote:
> >>
> >> The standard specifies some of the effects of ranges::advance in terms
> >> of "Equivalent to:" and it's observable that our current implementation
> >> deviates from the precise specification in the standard.  This was
> >> causing some failures in the libc++ testsuite.
> >>
> >> For the sized_sentinel_for<I, S> case I optimized our implementation to
> >> avoid redundant calls when we have already checked that there's nothing
> >> to do.  We were eliding `advance(i, bound)` when the iterator already
> >> equals the sentinel, and eliding `advance(i, n)` when `n` is zero. In
> >> both cases, removing the seemingly redundant calls is not equivalent to
> >> the spec because `i = std::move(bound)` or `i += 0` operations can be
> >> observed by program-defined iterators. This patch inlines the observable
> >> side effects of advance(i, bound) or advance(i, 0) without actually
> >> calling those functions.
> >>
> >> For the non-sized sentinel case, `if (i == bound || n == 0)` is
> >> different from `if (n == 0 || i == bound)` for the case where n is zero
> >> and a program-defined iterator observes the number of comparisons.
> >> This patch changes it to do `n == 0` first. I don't think this is
> >> required by the standard, as this condition is not "Equivalent to:" any
> >> observable sequence of operations, but testing `n == 0` first is
> >> probably cheaper anyway.
> >>
> >> libstdc++-v3/ChangeLog:
> >>
> >>         * include/bits/ranges_base.h (ranges::advance(i, n, bound)):
> >>         Ensure that observable side effects on iterators match what is
> >>         specified in the standard.
> >> ---
> >>
> >> For most iterator types, I assume the compiler will inline these extra
> >> redundant operations, so they'll only make a difference for iterators
> >> that do actually observe the number of operations.
> >>
> >> Tested powerpc64le-linux.
> >
> > LGTM
> >>
> >>
> >>  libstdc++-v3/include/bits/ranges_base.h | 18 +++++++++++++++---
> >>  1 file changed, 15 insertions(+), 3 deletions(-)
> >>
> >> diff --git a/libstdc++-v3/include/bits/ranges_base.h
> b/libstdc++-v3/include/bits/ranges_base.h
> >> index 0251e5d0928a..4a0b9e2f5ec3 100644
> >> --- a/libstdc++-v3/include/bits/ranges_base.h
> >> +++ b/libstdc++-v3/include/bits/ranges_base.h
> >> @@ -882,7 +882,14 @@ namespace ranges
> >>             const auto __diff = __bound - __it;
> >
> > Would you mind changing this iter_difference_t<_It>, this is required to
> be exactly that type,
>
> Yes, if that helps with reading the code, let's be explicit about the
> type. I've made that change.
>
> > but I think it would help me with understanding (see comment below)
> >>
> >>
> >>             if (__diff == 0)
> >> -             return __n;
> >> +             {
> >> +               // inline any possible side effects of advance(it,
> bound)
> >> +               if constexpr (assignable_from<_It&, _Sent>)
> >> +                 __it = std::move(__bound);
> >> +               else if constexpr (random_access_iterator<_It>)
> >> +                 __it += 0;
> >> +               return __n;
> >> +             }
> >>             else if (__diff > 0 ? __n >= __diff : __n <= __diff)
> >
> > I was wondering if we should check precondition __glibcxx_assert((__n <
> 0) == (__diff < 0)),
> > but then I realized that we never enter this branch, if __n and __diff
> disagree on direction.
>
> Right. The standard says to check |n| >= |diff| and if we did that,
> then we would need to check that the directions agree. But that would
> mean abs(n) >= abs(diff) which would be three comparisons and we'd
> also need to check the directions agree. The way I implemented it only
> does two, and implicitly checks the directions agree. If they don't
> agree then we take the next branch and fail the assertion there.
>
> It's not necessarily better code the way I did the comparisons, at
> least not with GCC:
> https://godbolt.org/z/P6cc7s3WP
> But it avoids needing two assertions, so that should be less code for
> the entire function.
>
> If assertions are disabled then we take the "wrong" branch and do
> advance(i, n) instead of advance(i, bound), but that case is UB anyway
> so there is no "wrong" outcome. However, now that I think about it,
> maybe advance(i, bound) would be safer for the UB case, if we assume
> that the sentinel is the correct bound. Advancing by an incorrect
> value of n (in the wrong direction) might take us out of bounds,
> whereas advance(i, bound) will not, assuming a correct bound.
>
I am not sure, if one direction disagrees, then I am not sure if betting on
sentinel
being correct is any more likely than n being correct. So I am fine with
leaving this as is.

>
> > And both __n and __diff are or iter_different_t, so there is no chance
> of signed->unsigned promotion.
>
> Yeah.
>
>

Reply via email to