3761. cartesian_product_view::iterator::operator- should pass by reference

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

Priority: Not Prioritized

View all other issues in [range.cartesian.iterator].

View all issues with C++23 status.

Discussion:

There are two problems with cartesian_product_view::iterator::operator-.

First, distance-from is not const-qualified, which would cause the common version of operator- to produce a hard error inside the function body since it invokes distance-from on a const iterator object.

Second, the non-common version of operator- passes iterator by value, which unnecessarily prohibits subtracting default_sentinel from an lvalue iterator whose first underlying iterator is non-copyable. Even if we std::move it into operator-, the other overload will still be ill-formed since it returns -(i - s) which will calls i's deleted copy constructor.

The proposed resolution is to add const qualification to distance-from and make the non-common version of iterator::operator- pass by const reference. Although the Tuple parameter of distance-from is guaranteed to be copyable, I think it would be more appropriate to pass it by reference.

[2022-09-08]

As suggested by Tim Song, the originally submitted proposed wording has been refined to use const Tuple& instead of Tuple&.

[2022-09-23; Reflector poll]

Set status to Tentatively Ready after six 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 [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:
        […]
    
        friend constexpr difference_type operator-(const iterator& i, default_sentinel_t)
          requires cartesian-is-sized-sentinel<Const, sentinel_t, First, Vs...>;
        friend constexpr difference_type operator-(default_sentinel_t, const iterator& i)
          requires cartesian-is-sized-sentinel<Const, sentinel_t, First, Vs...>;
    
        […]
      private:
        […]
        
        template<class Tuple>
          constexpr difference_type distance-from(const Tuple& t) const;            // exposition only
          
        […]
      };
    }
    

    […]

    template<class Tuple>
      constexpr difference_type distance-from(const Tuple& t) const;
    

    -7- Let:

    1. (7.1) — scaled-size(N) be the product of static_cast<difference_type>(ranges::size(std::get<N>(parent_->bases_))) and scaled-size(N + 1) if N < sizeof...(Vs), otherwise static_cast<difference_type>(1);

    2. (7.2) — scaled-distance(N) be the product of static_cast<difference_type>(std::get<N>(current_) - std::get<N>(t)) and scaled-size(N + 1); and

    3. (7.3) — scaled-sum be the sum of scaled-distance(N) for every integer 0 ≤ N ≤ sizeof...(Vs).

    -8- Preconditions: scaled-sum can be represented by difference_type.

    -9- Returns: scaled-sum.

    […]

    friend constexpr difference_type operator-(const iterator& i, default_sentinel_t)
      requires cartesian-is-sized-sentinel<Const, sentinel_t, First, Vs...>;
    

    -32- Let end-tuple be an object of a type that is a specialization of tuple, such that:

    1. (32.1) — std::get<0>(end-tuple) has the same value as ranges::end(std::get<0>(i.parent_->bases_));

    2. (32.2) — std::get<N>(end-tuple) has the same value as ranges::begin(std::get<N>(i.parent_->bases_)) for every integer 1 ≤ N ≤ sizeof...(Vs).

    -33- Effects: Equivalent to: return i.distance-from(end-tuple);

    friend constexpr difference_type operator-(default_sentinel_t, const iterator& i)
      requires cartesian-is-sized-sentinel<Const, sentinel_t, First, Vs...>;
    

    -34- Effects: Equivalent to: return -(i - s);