**Section:** 26.7.32.2 [range.cartesian.view] **Status:** Open
**Submitter:** Tomasz Kamiński **Opened:** 2022-09-12 **Last modified:** 2023-02-07 02:58:54 UTC

**Priority: **2

**View all issues with** Open status.

**Discussion:**

In case when `cartesian_product_view` is common and one of the inner ranges is empty,
it needs to produce equal iterators from `begin`/`end`. We currently create a
sequence of `begin` iterators as both `begin` and `end` iterators. This
assumes that `begin` iterator is copyable, which may not be the case with the input range,
even in the case if that range is common — in such case, we require that only sentinel
is semantically copy-constructible, not begin even if they are the same type.

To illustrate, C++98 input iterators (like `directory_iterator`) are syntactically
copy-constructible, but only default constructed object, that corresponds to sentinels are
semantically copyable — the copy produces an equivalent result. As a consequence for
`directory_iterator d`, and empty `std::string_view sv`, the
`view::cartesian_product(d, sv)` produces an invalid range.

To fix the problem, we need to move the logic of adjusting the first range iterator to return
`[end, begin, ..., begin]` for `begin`. This is safe, as we require the end
to be always semantically copy-constructible. This again can be done only if computing the end
can be done in `𝒪(1)` i.e. the first range is common.

*[2022-09-28; Reflector poll]*

Set priority to 2 after reflector poll.

*[2022-09-28; LWG telecon]*

Discussed issue. Tim suggested to add a new semantic requirement to
`sentinel_for` that when `S` and `I` are the same type
then `i == i` is true for any non-singular `i` of type `I`.

**Proposed resolution:**

This wording is relative to N4910.

Modify 26.7.32.2 [range.cartesian.view] as indicated:

[

*Drafting note*: We can optimize the comparison with`default_sentinel_t`to compare only the iterator to the first range if the range is common. This is observable, as we call comparison of user-provided iterators.]constexpr

*iterator*<false> begin() requires (!*simple-view*<First> || ... || !*simple-view*<Vs>);~~-2-~~*Effects*: Equivalent to:`return`*iterator*<false>(*tuple-transform*(ranges::begin,*bases_*));constexpr

*iterator*<true> begin() const requires (range<const First> && ... && range<const Vs>);~~-3-~~*Effects*: Equivalent to:`return`*iterator*<true>(*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:

(4.1) —

be*is-const*`true`for the const-qualified overloads, and`false`otherwise;(4.?) —

be*is-end*`true`for the`end`overloads, and`false`otherwise;(4.2) —

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

be expression-equivalent to*begin-or-first-end*(rng)if*is-end*||*is-empty*?*cartesian-common-arg-end*(rng) : ranges::begin(rng)*is-empty*? ranges::begin(rng) :*cartesian-common-arg-end*(rng)is*cartesian-product-common-arg*<*maybe-const*<*is-const*, First>>`true`and`rng`is the first underlying range, and`ranges::begin(rng)`otherwise.

-5-

*Effects*: Equivalent to:iterator<

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

friend constexpr bool operator==(const

*iterator*& x, default_sentinel_t);-26-

*Returns*:(?.1) — If

is*cartesian-product-common-arg*<*maybe-const*<Const, First>>`true`, returns`std::get<0>(x.`.*current_*) == ranges::end(std::get<0>(x.*parent_*->*bases_*))(?.2) — Otherwise,

if`true``std::get<`is*i*>(x.*current_*) == ranges::end(std::get<*i*>(x.*parent_*->*bases_*))`true`for any integer`0 ≤`,*i*≤ sizeof...(Vs)~~; otherwise,~~returns`false``true`.(?.3) — Otherwise, returns

`false`.