*This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Open status.*

`cartesian_product_view`

produces an invalid range if the first range is input and one of the ranges is empty**Section:** 26.7.33.2 [range.cartesian.view] **Status:** Open
**Submitter:** Tomasz Kamiński **Opened:** 2022-09-12 **Last modified:** 2023-02-07

**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.33.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.33.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`

.