|
| 1 | +--- |
| 2 | +title: "`zip()` Should Be an Infinite Range" |
| 3 | +document: DXXXXR0 |
| 4 | +date: 2025-05-31 |
| 5 | +audience: SG9, LEWG |
| 6 | +author: |
| 7 | + - name: Hui Xie |
| 8 | + |
| 9 | + - name: Tristan Melen |
| 10 | + email: <> |
| 11 | +toc: true |
| 12 | +--- |
| 13 | + |
| 14 | +# Revision History |
| 15 | + |
| 16 | +## R0 |
| 17 | + |
| 18 | +- Initial revision. |
| 19 | + |
| 20 | +# Abstract |
| 21 | + |
| 22 | +This paper proposes a fix that makes the `views::zip()` an infinite range `views::repeat(tuple())` instead of the current `views::empty<tuple<>>` |
| 23 | + |
| 24 | + |
| 25 | +# Design Considerations |
| 26 | + |
| 27 | +## Rationale for the Current `zip()` Design |
| 28 | + |
| 29 | +### P2321R2 |
| 30 | + |
| 31 | +According to [@P2321R2], the reason for the current behaviour of `zip()` was: |
| 32 | + |
| 33 | +> As in range-v3, zipping nothing produces an `empty_view` of the appropriate type. |
| 34 | +
|
| 35 | +Searching through the historical commits in range-v3, the behaviour of `zip()` was added in this commit [@range-v3]. |
| 36 | + |
| 37 | +In this commit, both nullary `cartesian_product()` and `zip()` were added, both yield an empty range, and both are incorrect. And the rationale for this commit was |
| 38 | + |
| 39 | +> This allows us to remove the nasty MSVC special case for empty `cartesian_products`. |
| 40 | +
|
| 41 | +In conclusion, this behaviour did not seem to be a result of careful design, but just chosen to work around a "nasty MSVC special case". |
| 42 | + |
| 43 | +### P2540R1 |
| 44 | + |
| 45 | +The discussion on the [@reflector] indicated that `cartesian_product()` should be `single(tuple())` and `zip()` should be `repeat(tuple())`. |
| 46 | + |
| 47 | +Luckily, [@P2540R1] fixed `cartesian_product`. So it no longer follows the "range-v3" behaviour but the correct one `views::single(tuple())`. |
| 48 | + |
| 49 | +However, the paper intentionally left `zip`'s behaviour unchanged, after . |
| 50 | + |
| 51 | +The main reason behind this decision was |
| 52 | + |
| 53 | +> In particular, `zip` has the property that it is the inner join of the indexed sets, and is the main diagonal of the Cartesian product. However, the identity element for `zip` is `repeat(tuple<>)`, the infinite range of repeated empty tuples. |
| 54 | +> If we allowed zip of an empty range of ranges to be its identity element, we would be introducing an inconsistency into the system, where two different formulations of notionally the same thing produces different answers. |
| 55 | +
|
| 56 | +However, the authors believe that the above conclusion is flawed. |
| 57 | + |
| 58 | +TODO: explain the main diagonal and identity yield same results |
| 59 | + |
| 60 | +## TODO |
| 61 | +- explain any_of/all_of's identity |
| 62 | +- explain cartesian_product |
| 63 | +- explain the identify component of "minimum" operation should be infinity |
| 64 | +- we prefer the result to be infinite range. if this cannot be agreed on, at the minimum, zip() should be ill-formed |
| 65 | + |
| 66 | +## Other Languages |
| 67 | + |
| 68 | +### Julia |
| 69 | + |
| 70 | +Julia's `zip()` produces an infinite range of empty tuples, which is in line with the authors' view. |
| 71 | + |
| 72 | +### Rust |
| 73 | + |
| 74 | +Rust's compiler actively rejects `multizip` or `izip!` calls with no argument. This is much better than giving the wrong results. |
| 75 | + |
| 76 | +### Java, C#, Haskell |
| 77 | + |
| 78 | +These languages either only supports a zip with two ranges, or provide zip3, zip4, ... functions for more ranges, possibly due to |
| 79 | +lack of support for variadic arguments. However, they don't provide a zip0, for a good reason. |
| 80 | + |
| 81 | +### Python |
| 82 | + |
| 83 | +Python got it wrong too. `zip()` is an empty iterable. It is unclear what drives the design decision on this. The author has asked this question |
| 84 | +on StackOverflow's Python community long time ago [@stackoverflow], but there is no satisfying answer. |
| 85 | + |
| 86 | +## An Infinite Range is not a `ranges::range` |
| 87 | + |
| 88 | +Whether an infinite range is a `ranges::range` is out of the scope of this paper. After all, this paper only delegates this special case of `views::zip` to `views::repeat` without inventing anything new. Whatever fix that is needed to fix `ranges::range` concept to work nicely with infinite ranges will fix all the existing views, i.e. `views::iota(0)`, `views::repeat(x)` and so on in one go. |
| 89 | + |
| 90 | + |
| 91 | +# Implementation Experience |
| 92 | + |
| 93 | + |
| 94 | +# Wording |
| 95 | + |
| 96 | +Modify [range.zip.overview]{.sref} section (2.1) as |
| 97 | + |
| 98 | +- (2.1) [`auto(views::empty<tuple<>>)`]{.rm} [`views::repeat(tuple())`]{.add} if `Es` is an empty pack, |
| 99 | + |
| 100 | + |
| 101 | +## Feature Test Macro |
| 102 | + |
| 103 | +Bump the FTM `__cpp_lib_ranges_zip` |
| 104 | + |
| 105 | +--- |
| 106 | +references: |
| 107 | + - id: stackoverflow |
| 108 | + citation-label: stackoverflow |
| 109 | + title: "Why does Python `zip()` yield nothing when given no iterables?" |
| 110 | + URL: https://stackoverflow.com/questions/71561715/why-does-python-zip-yield-nothing-when-given-no-iterables |
| 111 | + |
| 112 | + - id: range-v3 |
| 113 | + citation-label: range-v3 |
| 114 | + title: "[zip] Correctly zip no ranges" |
| 115 | + URL: https://github.com/ericniebler/range-v3/commit/ef9a650d4da87f02c5c079055e09017825c92fb3 |
| 116 | + |
| 117 | + - id: reflector |
| 118 | + citation-label: reflector |
| 119 | + title: "[isocpp-lib-ext] zip and cartesian_product base case" |
| 120 | + URL: https://lists.isocpp.org/lib-ext/2022/01/22023.php |
| 121 | + |
| 122 | + - id: libcxx |
| 123 | + citation-label: libcxx |
| 124 | + title: "Implementation of zip() in libc++" |
| 125 | + author: |
| 126 | + - family: Xie |
| 127 | + given: Hui |
| 128 | + URL: todo |
| 129 | +--- |
| 130 | + |
| 131 | +<style> |
| 132 | +.bq{ |
| 133 | + display: block; |
| 134 | + margin-block-start: 1em; |
| 135 | + margin-block-end: 1em; |
| 136 | + margin-inline-start: 40px; |
| 137 | + margin-inline-end: 40px; |
| 138 | +} |
| 139 | +</style> |
0 commit comments