3760. cartesian_product_view::iterator's parent_ is never valid

Section: 25.7.33 [range.cartesian] Status: C++23 Submitter: Hewill Kang Opened: 2022-08-27 Last modified: 2023-11-22

Priority: Not Prioritized

View all issues with C++23 status.

Discussion:

cartesian_product_view::iterator has a pointer member parent_ that points to cartesian_product_view, but its constructor only accepts a tuple of iterators, which makes parent_ always default-initialized to nullptr.

The proposed resolution is to add an aliased Parent parameter to the constructor and initialize parent_ with addressof, as we usually do.

[2022-09-23; Reflector poll]

Set status to Tentatively Ready after eight votes in favour during reflector poll.

[2022-11-12 Approved at November 2022 meeting in Kona. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4917.

  1. Modify 25.7.33.2 [range.cartesian.view] as indicated:

    constexpr iterator<false> begin()
      requires (!simple-view<First> || ... || !simple-view<Vs>);
    

    -2- Effects: Equivalent to: return iterator<false>(*this, tuple-transform(ranges::begin, bases_));

    constexpr iterator<true> begin() const
      requires (range<const First> && ... && range<const Vs>);
    

    -3- Effects: Equivalent to: return iterator<true>(*this, tuple-transform(ranges::begin, bases_));

    constexpr iterator<false> end()
      requires ((!simple-view<First> || ... || !simple-view<Vs>)
        && cartesian-product-is-common<First, Vs...>);
    constexpr iterator<true> end() const
      requires cartesian-product-is-common<const First, const Vs...>;
    

    -4- Let:

    1. (4.1) — is-const be true for the const-qualified overload, and false otherwise;

    2. (4.2) — is-empty be true if the expression ranges::empty(rng) is true for any rng among the underlying ranges except the first one and false otherwise; and

    3. (4.3) — begin-or-first-end(rng) be expression-equivalent to is-empty ? ranges::begin(rng) : cartesian-common-arg-end(rng) if rng is the first underlying range and ranges::begin(rng) otherwise.

    -5- Effects: Equivalent to:

    iterator<is-const> it(*this, tuple-transform(
      [](auto& rng){ return begin-or-first-end(rng); }, bases_));
    return it;
    
  2. Modify [ranges.cartesian.iterator] as indicated:

    namespace std::ranges {
      template<input_range First, forward_range... Vs>
        requires (view<First> && ... && view<Vs>)
      template<bool Const>
      class cartesian_product_view<First, Vs...>::iterator {
      public:
        […]
    
      private:
        using Parent = maybe-const<Const, cartesian_product_view>;           // exposition only
        Parentmaybe-const<Const, cartesian_product_view>* parent_ = nullptr; // exposition only
        tuple<iterator_t<maybe-const<Const, First>>,
          iterator_t<maybe-const<Const, Vs>>...> current_;                   // exposition only
        
        template<size_t N = sizeof...(Vs)>
          constexpr void next();                                             // exposition only
        
        template<size_t N = sizeof...(Vs)>
          constexpr void prev();                                             // exposition only
        
        template<class Tuple>
          constexpr difference_type distance-from(Tuple t);                  // exposition only
        
        constexpr explicit iterator(Parent& parent, tuple<iterator_t<maybe-const<Const, First>>,
          iterator_t<maybe-const<Const, Vs>>...> current);                   // exposition only
      };
    }
    

    […]

    constexpr explicit iterator(Parent& parent, tuple<iterator_t<maybe-const<Const, First>>,
      iterator_t<maybe-const<Const, Vs>>...> current);
    

    -10- Effects: Initializes parent_ with addressof(parent) and current_ with std::move(current).

    constexpr iterator(iterator<!Const> i) requires Const &&
      (convertible_to<iterator_t<First>, iterator_t<const First>> &&
        ... && convertible_to<iterator_t<Vs>, iterator_t<const Vs>>);
    

    -11- Effects: Initializes parent_ with i.parent_ and current_ with std::move(i.current_).