---
title: "static_sized_range"
document: P3928R1
date: 2026-06-15
audience: SG9, LEWG
reply-to:
  - "Hewill Kang <hewillk@gmail.com>"
---

- Abstract
  - Revision history
  - Discussion
  - Design
  - Proposed change

## Abstract

This paper introduces `static_sized_range`, a refinement of `sized_range` for ranges whose sizes are known at compile time.

It allows detecting and retrieving a range's size as a constant expression through a new `consteval` function template `static_size_of`.

This enables compile-time reasoning about range sizes and improves `constexpr` support and optimization opportunities in range adaptors and algorithms.

## Revision history

### R1

Change variable template `range_static_size_v` to function template based on feetback on SG9 in Brno.

Add discussion on breaking change, especially CTAD for `span`.

### R0

Initial revision.

## Discussion

The Ranges library initially attempted to identify ranges with compile-time known sizes using an exposition-only concept, `*tiny-range*`, which comes from [[range.lazy.split.view]](https://eel.is/c++draft/range.lazy.split.view):

```
  template<auto> struct require-constant;                       // exposition only

  template<class R>
  concept tiny-range =                                          // exposition only
    sized_range<R> &&
    requires { typename require-constant<remove_reference_t<R>::size()>; } &&
    (remove_reference_t<R>::size() <= 1);
    
```

However, this approach relied on the presence of a static member `size()` and excluded many types, such as `span<int, 1>`. There was no general mechanism in the language to allow generic code to verify whether `ranges::size(r)` could be evaluated as a constant expression.

The acceptance of [P2280](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2280r4.html) provided a new opportunity: by modifying the rules of constant evaluation, it allows expressions involving references to unknown objects to be considered valid constant expressions when the result does not depend on the object's identity.

This makes `ranges::size(r)` more generally usable at compile time and allows defining a concept like `static_sized_range` in a simpler and more intuitive way. Additionally, the C++26 `simd` specification heavily relies on this language enhancement: its wording repeatedly checks that `ranges::size(r)` is a constant expression to ensure, at compile time, the correctness of vector sizes.

However, these compile-time checks and optimizations should not be limited to `simd`. Introducing the `static_sized_range` concept is therefore worthwhile, as it enables compile-time reasoning about range sizes and broader use in generic programming.

## Design

### New concept and function template

This proposal introduces two related entities: the `static_sized_range` concept and the `static_size_of` function template. The `static_sized_range` concept refines `sized_range` by further requiring that `ranges::size(r)` be evaluable as a constant expression. It now can defined as follows:

```
  template<class T>
    concept static_sized_range =
      sized_range<T> && requires(T& t) { cw<ranges::size(t)>; };
```

Here, `cw` enables the formation of a `constant_wrapper` introduced in [P2781](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2781r9.html) from an expression known to be a constant expression, allowing the concept to directly check whether `ranges::size(r)` can be evaluated at compile time, as enabled by [P2280](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2280r4.html).

Once such a range is identified, its compile-time size can be retrieved through a function template:

```
  template<static_sized_range T>
    consteval auto static_size_of() {
      return decltype([](T& t) { return cw<ranges::size(t)>; }(declval<T&>()))::value;
    }
```

The definition relies on the same mechanism: it promotes the result of `ranges::size(r)` to a type via `constant_wrapper`, and then extracts the value from that type. This effectively turns a compile-time constant into a type-level entity, allowing the function template to retrieve the constant directly through ordinary deduction.

Together, `static_sized_range` and `static_size_of` provide a minimal yet general mechanism for compile-time reasoning about range extents:

```
  static_assert(ranges::static_sized_range<array<int, 3>>);
  static_assert(ranges::static_size_of<array<int, 3>>() == 3);

  static_assert(ranges::static_sized_range<span<int, 5>>);
  static_assert(ranges::static_size_of<span<int, 5>>() == 5);

  static_assert(ranges::static_sized_range<ranges::single_view<int>>);
  static_assert(ranges::static_size_of<ranges::single_view<int>>() == 1);

  static_assert(ranges::static_sized_range<ranges::empty_view<int>>);
  static_assert(ranges::static_size_of<ranges::empty_view<int>>() == 0);

  static_assert(!ranges::static_sized_range<span<int>>);
  static_assert(!ranges::static_sized_range<vector<int>>);
  static_assert(!ranges::static_sized_range<optional<int>>);
  static_assert(ranges::static_size_of<string>() == 42); // ill-formed: constraints not satisfied
```

### Enhancements to integer-class type (LWG 4409)

Although `static_size_of<R>()` exposes the size of a `static_sized_range` at compile time, expressions such as `(static_size_of<R>() >= 1)` may not be constant expressions, since `ranges::size(r)` can return an integral-class type lacking `constexpr` comparison or arithmetic operators.

LWG [4409](https://cplusplus.github.io/LWG/issue4409) highlights this limitation, noting that such types may behave like integers at runtime but are not necessarily usable in constant expressions.

However, it is natural to expect that integer-class types should support compile-time operations like built-in integers. To align with this expectation, it is desirable to clarify the wording for any integer-class type defined in [[iterator.concept.winc]](https://wg21.link/iterator.concept.winc), ensuring well-defined `constexpr` behaviors.

In addition, LWG [4546](https://cplusplus.github.io/LWG/issue4546) indicates that the current standard does not explicitly specify that integer-class types are structural types so that they can be used as template parameters, so this part should also be clarified to ensure the validity of using `cw<ranges::size(r)>` for constant expression determination.

### Enhancements to ref_view

Currently, `ref_view<R>::size()` simply forwards to the underlying range via `**r_*`:

```
  constexpr auto size() const requires sized_range<R>
  { return ranges::size(*r_); }
```

Even if `R` satisfies `static_sized_range`, this expression is still not a constant expression. As a result, for example, `ref_view<array<int, 42>>` does not model `static_sized_range`.

This can be fixed by having `size()` return `static_size_of<R>()` when `R` models `static_sized_range`, while keeping the original behavior for other ranges:

```
  constexpr auto size() const requires sized_range<R> { 
    if constexpr (static_sized_range<R>)
      return static_size_of<R>();
    else
      return ranges::size(*r_); 
  }
```

Similarly, `empty()` should receive similar treatment so that `ranges::empty` on `ref_view` can also be a constant expression. The effect of this change can be seen in the following example:

```
  array a{1, 2, 3, 4, 5};
  auto r = a | views::transform([](int i) { return i * i; })
             | views::reverse;

  static_assert(ranges::size(r) == 5);                     // ok
  static_assert(!ranges::empty(r));                        // ok
  static_assert(ranges::static_sized_range<decltype(r)>);  // ok
  static_assert(ranges::static_size_of<decltype(r)>() == 5); // ok
```

### Enhancements to front/back across array/span/view_interface

For `view_interface`, the `front()` and `back()` members can benefit from `static_sized_range`. If a derived view models `static_sized_range`, calls on an empty range can be rejected at compile time.

Similarly, `array<T, N>` and `span<T, N>` with a fixed extent can take advantage of this: when `N == 0`, invoking `front()` or `back()` can be diagnosed at compile time, providing safety without relying on runtime *Hardened preconditions* added in [P3471](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3471r4.html).

### Enhancements to join_view (LWG 4401)

LWG [4401](https://cplusplus.github.io/LWG/issue4401) observes that `join_view` is not considered a sized_range, even though in certain cases — such as when the outer range is sized and the inner range has a fixed size — its total size can be determined.

With the introduction of `static_sized_range` and `static_size_of`, this limitation can be addressed more generally: when the inner range models `static_sized_range`, the size of a `join_view` can be computed as `ranges::size(*base_*) * static_size_of<*InnerRng>*()`.

This effectively makes `ranges::join_view<span<array<int, 3>, 4>>>` a `static_sized_range` with a static size of `12`.

It is worth noting that this gives `join_view` the potential to support random-access, which is a very exciting enhancement, but this requires additional paper for design.

### Enhancements to lazy_split_view (LWG 3855, 4108)

LWG [3855](https://cplusplus.github.io/LWG/issue3855) notes that `*tiny-range*` is limited and not fully general. Using `static_sized_range` and `static_size_of`, we can rewrite `*tiny-range*` with a concept that correctly captures ranges whose size is known at compile time, and a range with `static_size_of<R>() <= 1` naturally satisfies the intended semantics of `*tiny-range*`

This means that `lazy_split_view` can now take an `array` or `span` of size 1 or 0 as the patterns, which is a nice enhancement:

```
  array a{42};
  auto r1 = views::istream<int>(in1)  | views::lazy_split(a);         // ok
  auto r2 = views::istream<int>(in2)  | views::lazy_split(array{42}); // ok
  auto r3 = views::istream<int>(in3)  | views::lazy_split(span{a});   // ok
```

LWG [4108](https://cplusplus.github.io/LWG/issue4108) observes that `lazy_split_view` cannot provide `size()` for certain valid cases. With `static_sized_range`, this can be improved: when the underlying range is sized and the pattern is statically empty, `lazy_split_view` can conditionally provide `size()` computed as `ranges::size(*base_*)`.

### Enhancement to span (LWG 4397, 4404)

LWG [4397](https://cplusplus.github.io/LWG/issue4397) concerns constructing a `span` from a statically sized range. The standard currently enforces this with *Hardened Preconditions* at runtime to reject ranges whose size does not match the fixed extent. Applying `static_sized_range` allows this requirement to be expressed at compile time, which is a useful enhancement.

LWG [4404](https://cplusplus.github.io/LWG/issue4404) deals with class template argument deduction (CTAD) for `span(R&&)`. When the underlying range has a statically known size, the current standard does not uniformly propagate this information through CTAD due to language rule before [P2280](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2280r4.html). With the newly `static_sized_range`, the compiler can conditionally treat such `span` constructions as statically sized, enabling more precise compile-time checks and better integration with generic code.

### Enhancement to simd/inplace_vector/define_static_array wording (LWG 4396, LWG 4537)

For `simd`, vector sizes must be known at compile time so that the compiler can generate correct SIMD instructions. The standard currently expresses this requirement as "If `ranges::size(r)` is a constant expression," which is not easily reusable in generic code.

With the introduction of `static_sized_range`, this check can be simplified to "If `R` models `static_sized_range`". This formulation is concise, clearly expresses the intent, and can be applied consistently across the standard library. Combined with `static_size_of`, it provides a uniform mechanism for compile-time size reasoning.

Note that the similar wording in LWG [4396](https://cplusplus.github.io/LWG/issue4396) can also be simplified with `static_sized_range`, making the intent explicit and consistent with `simd`.

The same wording improvements can also be applied to LWG [4537](https://cplusplus.github.io/LWG/issue4537) for `define_static_array`.

### Enhancement to ranges::min/max/minmax

Those algorithms specify as a *Preconditions* that the input range must not be empty.

With the introduction of `static_sized_range`, these preconditions can be verified at compile time. For example, invoking `ranges::min` on `array<int, 0>` could be statically rejected, improving diagnostic clarity and program safety by turning a runtime undefined behavior into a compile-time error.

Additionally, `static_sized_range` provides opportunities for compile-time optimizations, with many more potential enhancements throughout the library. For example, `ranges::equal` between two ranges of different static sizes can be evaluated to false at compile time, reducing unnecessary instantiations and runtime checks.

### Compile-time size considerations for optional/inplace_vector

After [P3168](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3168r2.html), `optional` could in principle be treated as a `*tiny-range*`, since its maximum size is one. If it were considered `*tiny-range*`, `lazy_split_view` could use an `optional` as the pattern for input ranges:

```
  auto r = views::istream<int>(in)  | views::lazy_split(optional{42});
```

However, `*tiny-range*` is intended to have a fully known, precise size at compile time to support efficient code generation, which `optional` does not provide. For this reason, the author does not adopt such a treatment.

Additionally, similar to `optional`, `inplace_vector` also has a statically known maximum size. In theory, this could be used to enhance range adaptors such as `reserve_hint` member introduced in [P2846](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2846r6.pdf): for example, a `join_view` of a range of `optional` could have `reserve_hint` returning `ranges::size(*base_*) * 1`, and `ranges::size(*base_*) * N` for range of `inplace_vector<T, N>`. However, this is a very specialized optimization and is not pursued here.

In theory, calls to `front`, `back`, or `pop_back` on an `inplace_vector` with zero capacity could be rejected at compile time. However, this is an extremely limited case and does not justify adding such checks.

We can also do similar checks for `append_range`/`insert_range` for `inplace_vector`, that is, when the append range is a static-sized, the size must not be greater than the capacity of `inplace_vector`. However, this still does not cover the situation when the append range is not greater than the capacity but overflows because the current size of `inplace_vector` is not a constant expression. However, if LEWG still feels it is worthwhile, the authors are happy to add wording to it.

### ❗ Breakage ❗

One of the improvements proposed in this paper is for the `span`'s CTAD, that is, when a `span` takes a `static_sized_range`, it will be deduced as a static `span`, for example:

```
  auto s = views::single(42);
  auto sp1 = span(s);      // span<int> before this proposal, span<int, 1> after this proposal
  auto e = views::empty<int>;
  auto sp2 = span(e);      // span<int> before this proposal, span<int, 0> after this proposal
```

However, the author believes this is a worthwhile enhancement because it deduces a more efficient type. In the standard, the only types that satisfy both `static_sized_range` and `contiguous_range` are raw fixed-size array, `array`, static `span`, and, as mentioned above, `empty_view` and `single_view`. It's worth noting that in the first three cases, CTAD is already deduced into a static `span`, leading the authors to believe that the actual breakage is relatively small.

As a consequence, the enhancements to `ref_view::size` in this paper will lead to `define_static_array` returning different types, or make `simd::vec` accept more range types, because it makes spellings like `a | views::transform(...)` possibly `static_sized_range`, although the author believes this is the right direction and is willing to add an appendix if necessary.

## Proposed change

This wording is relative to [Lastest Working Draft](https://eel.is/c++draft).
