]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Make ranges::advance(it, n, bound) follow standard more strictly
authorJonathan Wakely <jwakely@redhat.com>
Mon, 14 Jul 2025 10:21:24 +0000 (11:21 +0100)
committerJonathan Wakely <redi@gcc.gnu.org>
Tue, 15 Jul 2025 19:31:47 +0000 (20:31 +0100)
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.

Reviewed-by: Tomasz KamiƄski <tkaminsk@redhat.com>
libstdc++-v3/include/bits/ranges_base.h

index 0251e5d0928a99de86a9c2654bb8bfdfe5a1a757..7df638db2d6fdca214cb9383291f2e53a3c7cc9b 100644 (file)
@@ -879,10 +879,17 @@ namespace ranges
       {
        if constexpr (sized_sentinel_for<_Sent, _It>)
          {
-           const auto __diff = __bound - __it;
+           const iter_difference_t<_It> __diff = __bound - __it;
 
            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)
              {
                (*this)(__it, __bound);
@@ -897,9 +904,14 @@ namespace ranges
                return 0;
              }
            else
-             return 0;
+             {
+               // inline any possible side effects of advance(it, n)
+               if constexpr (random_access_iterator<_It>)
+                 __it += 0;
+               return 0;
+             }
          }
-       else if (__it == __bound || __n == 0)
+       else if (__n == 0 || __it == __bound)
          return __n;
        else if (__n > 0)
          {