Title
cartesian_product_view::iterator::prev is not quite right
Status
c++23
Section
[range.cartesian.iterator]
Submitter
Hewill Kang

Created on 2022-11-08.00:00:00 last changed 5 months ago

Messages

Date: 2023-02-13.10:17:57

Proposed resolution:

This wording is relative to N4917.

  1. Modify [ranges.cartesian.iterator] as indicated:

    template<size_t N = sizeof...(Vs)>
      constexpr void prev();
    

    -6- Effects: Equivalent to:

    auto& it = std::get<N>(current_);
    if constexpr (N > 0) {
      if (it == ranges::begin(std::get<N>(parent_->bases_))) {
        it = cartesian-common-arg-end(std::get<N>(parent_->bases_));
        if constexpr (N > 0) {
          prev<N - 1>();
        }
      }
    }
    --it;
    
Date: 2023-02-13.00:00:00

[ 2023-02-13 Approved at February 2023 meeting in Issaquah. Status changed: Voting → WP. ]

Date: 2022-11-10.23:33:23

[ Kona 2022-11-08; Move to Ready ]

Date: 2022-11-08.00:00:00

Currently, cartesian_product_view::iterator::prev has the following Effects:

auto& it = std::get<N>(current_);
if (it == ranges::begin(std::get<N>(parent_->bases_))) {
  it = cartesian-common-arg-end(std::get<N>(parent_->bases_));
  if constexpr (N > 0) {
    prev<N - 1>();
  }
}
--it;

which decrements the underlying iterator one by one using recursion. However, when N == 0, it still detects if the first iterator has reached the beginning and assigns it to the end, which is not only unnecessary, but also causes cartesian-common-arg-end to be applied to the first range, making it ill-formed in some cases, for example:

#include <ranges>

int main() {
  auto r = std::views::cartesian_product(std::views::iota(0));
  r.begin() += 3; // hard error
}

This is because, for the first range, cartesian_product_view::iterator::operator+= only requires it to model random_access_range. However, when x is negative, this function will call prev and indirectly calls cartesian-common-arg-end, since the latter constrains its argument to satisfy cartesian-product-common-arg, that is, common_range<R> || (sized_range<R> && random_access_range<R>), which is not the case for the unbounded iota_view, resulting in a hard error in prev's function body.

The proposed resolution changes the position of the if constexpr so that we just decrement the first iterator and nothing else.

History
Date User Action Args
2023-11-22 15:47:43adminsetstatus: wp -> c++23
2023-02-13 10:17:57adminsetmessages: + msg13361
2023-02-13 10:17:57adminsetstatus: voting -> wp
2023-02-06 15:33:48adminsetstatus: ready -> voting
2022-11-10 23:33:23adminsetmessages: + msg13013
2022-11-10 23:33:23adminsetstatus: new -> ready
2022-11-08 19:04:02adminsetmessages: + msg12963
2022-11-08 00:00:00admincreate