---
title: "views::slice"
document: P3216R3
date: 2026-06-16
audience: LEWG, SG9 (Ranges)
reply-to:
  - "Hewill Kang <hewillk@gmail.com>"
---

- Abstract
  - Revision history
  - Discussion
  - Design
  - Implementation experience
  - Proposed change
  - References

## Abstract

This paper proposes the Tier 1 adaptor `views::slice` (as described in [P2760](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2760r1.html)) to enhance the C++29 ranges library. Notably, this is the first standard range adaptor that accepts two arguments — `start` and `end` — to specify the interval [`start`, `end`) for slicing a range.

## Revision history

### R0

Initial revision.

### R1

Introduce new `slice_view` class based on feedback from SG9 St. Louis.

### R2

Apply specialization for `optional` based on feedback from SG9 Kona.

### R3

Provide `const begin()` member when the underlying range is not a `forward_range`.

## Discussion

Slicing — a means of extracting a contiguous subrange from a sequence by specifying a start and end index — is a fundamental operation in modern programming. Many mainstream languages, such as Python and Rust, offer built-in slice syntax, making it a familiar and expected feature for developers. In the C++ ecosystem, while the Ranges library has greatly enhanced composability and expressiveness, it currently lacks a direct, ergonomic, and standard way to perform slicing by index.

### Why views::slice is needed

- Familiarity
- Readability
- Generic and Robust Semantics

Given the above, the introduction of `views::slice` fills a clear gap in the C++ Ranges library, providing a direct, expressive, and interoperable way to extract subranges by index. It aligns C++ with industry standards, improves code readability, and empowers developers to write more concise and correct range-based code. For these reasons, `views::slice` is a valuable and timely addition to C++29:

```
  string_view text = "Hello, world!";
  auto sub1 = text | views::slice(7, 12);  // "world"

  vector v = {1, 2, 3, 4, 5};
  auto sub3 = v | views::slice(1, 10);     // [2, 3, 4, 5]
  auto sub2 = v | views::slice(2, 2);      // empty range
  auto sub4 = v | views::slice(5, 10);     // empty range
```

## Design

### The second argument should be end instead of size

An alternative design for `slice` is to accept a starting index and a *size*. However, the author prefers to use the end index as the second parameter as this is intuitive and consistent with other language syntaxes:

| Language | Syntax | Stride Support | Negative Indices | Out-of-Bounds Behavior | Notes |
| --- | --- | --- | --- | --- | --- |
| Python | `a[start:end:step]` | ✅ Yes | ✅ Yes | ✅ Truncated |  |
| Rust | `&a[start..end]` | ❌ No | ❌ No | ❌ Panics | Need to use `.iter().step_by(n)` to stride |
| Go | `a[start:end]` | ❌ No | ❌ No | ❌ Panics | `a[start:end:max]` controls capacity, not stride |
| JavaScript | `a.slice(start, end)` | ❌ No | ✅ Yes | ✅ Truncated |  |
| Ruby | `a[start, length]` or `a[start..end]` | ❌ No | ✅ Yes | ✅ Truncated |  |

This is the de facto standard for slicing.

### Motivation for a dedicated slice_view class

As stated in [P2214](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2214r2.html#the-takedrop-family): "`slice(M, N)` is equivalent to `views::drop(M) | views::take(N - M)`, and you couldn't do much better as a first class view. range-v3 also supports a flavor that works as `views::slice(M, end - N)` for a special variable `end`, which likewise be equivalent to `r | views::drop(M) | views::drop_last(N)`."

While `views::drop` and `views::take` provide compositional power, a dedicated `slice_view` offers better API consistency, performance, and expressiveness. It enables more robust and efficient handling of subranges for the following reasons:

- `base()` member Consistency
- Global Range Awareness and `reserve_hint()`
- Performance Overhead
- Unified Boundary Handling
- Better Debug-ability
- Better Future Extensibility

### Handling of out-of-bounds

It should be noted that in range/v3, `views::slice` is also implemented with a dedicated view class, but it does **not** perform any boundary checking. It always assumes that the provided `start` and `end` indices are within valid bounds of the underlying range:

```
  auto ints  = {1, 2, 3, 4, 5};
  auto slice = ints | ranges::v3::views::slice(3, 9);
  std::println("{}", slice); // prints [4, 5, 0, 0, 0, 2147483647]
```

This design makes its `views::slice` effectively an unchecked version of slicing. If the indices are out of range, the behavior is undefined, potentially leading to runtime errors or undefined behavior. From a naming perspective, the `range/v3` version would be more accurately described as `views::unchecked_slice` or `views::slice_exactly`, reflecting its lack of safety checks.

In contrast, the proposed `views::slice` includes comprehensive boundary checking just like `views::take` and `views::drop`; it will safely adjust or clamp the specified indices as appropriate, ensuring well-defined and predictable behavior.

### Special variable *end* is not support

In range-v3, the special variable `*end*` is supported in `views::slice`, allowing users to write expressions like `views::slice(M, *end* - N)` to indicate slicing from index `M` up to `N` elements before the end of the range.

While this can be expressive in certain scenarios, the author believes it is unnecessary and potentially problematic for several reasons.

First, introducing a special variable such as

*end*

can make the syntax less clear and more
  confusing, especially for users who expect a straightforward two-index slicing interface similar to what is found in
  other mainstream languages.
  This added complexity may hinder readability and increase the learning curve for new users.

Second, and more importantly, range-v3's implementation does not perform boundary checking for the end. It assumes that the indices provided are always valid, which is fundamentally different from our proposed design. Supporting end-based expressions in a boundary-checked implementation introduces challenges, particularly for input ranges, since evaluating something like `*end* - N` would require traversing the entire range, which is infeasible for single-pass input ranges.

In summary, while the `*end*` variable enables some expressive patterns, it complicates the interface and is incompatible with a robust, boundary-checked design. For these reasons, the author does not adopting this feature in the proposal.

### Stride overload is not provided

As described in the table above, Python also allows an optional stride (step) parameter, its slice syntax `[start:end:step]` enables users to select every nth element or even reverse the sequence by specifying a negative stride.

However, JavaScript, Go, and many other languages with slicing capabilities (such as Ruby, Swift, or Kotlin) do not include stride as part of their native slice syntax; instead, users must use separate functions or methods to achieve similar effects. Rust's standard slice syntax does not support stride directly; users must use iterators like `.iter().step_by(n)` to achieve striding.

This supports the case against overloading `views::slice` with a stride parameter in C++, which, already provides a clear and composable way to achieve striding via `views::stride`. Chaining adaptors like `views::slice(M, N) | views::stride(P)` makes the intent and order of operations clear, whereas adding a stride overload to `views::slice` could blur the distinction between slicing and stepping, making the API less intuitive.

For these reasons, the author does not support stride.

### Specialization for return types

For certain well-known range types such as `iota_view`, `subrange`, or `span`, and so on, `views::slice(start, end)` can be optimized to return a view of the same type with adjusted bounds, rather than wrapping it in a generic `slice_view`. This follows the precedent set by `views::take` and `views::drop`, and can lead to fewer template instantiations, improved compile times, and better performance.

### Handling of non-sized range

For ranges that model `sized_range`, the subrange's start and end positions can be computed precisely. In such cases, the implementation can advance the iterator by the specified offset and construct a `counted_iterator` with the exact number of elements in the slice.

For ranges that do not model `sized_range`, the precise end position *cannot* be determined in advance. The implementation must instead use the bound-preserving overload of `ranges::next` to advance the iterator to the starting position. The resulting iterator is then wrapped in a `counted_iterator` with the *desired* count. Since the base range may not contain enough elements, a custom sentinel is used to detect the actual end of the range. This sentinel terminates iteration either when the count is exhausted or when the underlying iterator reaches the end of the base range, similar to the behavior of `take_view::*sentinel*`.

### Conditionally const-iterable

To satisfy the requirement that `begin()` of a view operates in amortized constant time, the implementation must cache the computed starting iterator when it cannot be obtained in constant time. In particular, when the base range does not model `random_access_range` or `sized_range`, advancing the `begin()` iterator to the desired offset may require O(n) time. In such cases, the result of this computation must be stored internally to avoid repeated traversal on subsequent calls to `begin()`, which means that `slice_view` may not be `const`-iterable in all cases.

However, in cases of pure input range, it is not actually necessary to cache the begin iterator because the `begin()` member can only be called once. This means that providing a `const begin()` is feasible, which is consistent with the design of [P3725](https://wg21.link/P3725R3) and LWG [4558](https://cplusplus.github.io/LWG/issue4558) (if LEWG encourage such direction).

## Implementation experience

The author implemented `views::slice` based on libstdc++, see [here](https://godbolt.org/z/o3aYd7jzT).

## Proposed change

This wording is relative to [N5014](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/n5014.pdf).

## References

[P2760R1]

Barry Revzin. A Plan for C++26 Ranges. URL:

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2760r1.html
