Document number P3928R1
Date 2026-06-15
Audience SG9, LEWG
Reply-to Hewill Kang <hewillk@gmail.com>

static_sized_range

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]:

  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 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 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.

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 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], ensuring well-defined constexpr behaviors.

In addition, LWG 4546 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.

Enhancements to join_view (LWG 4401)

LWG 4401 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 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 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 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 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. 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 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 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, 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: 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.

    1. Add a new feature-test macro to 17.3.2 [version.syn]:

      #define __cpp_lib_ranges_static_sized_range 2026XXL // freestanding, also in <ranges>
    2. Modify 21.4.3 [meta.define.static] as indicated:

      template<ranges::input_range R>
        consteval span<const ranges::range_value_t<R>, see below> define_static_array(R&& r);
      

      -16- Effects: Equivalent to:

      using T = ranges::range_value_t<R>;
      meta::info array = meta::reflect_constant_array(r);
      if (meta::is_array_type(meta::type_of(array))) {
        return span<const T, see below>(meta::extract<const T*>(array), meta::extent(meta::type_of(array)));
      } else {
        return span<const T, see below>(static_cast<const T*>(nullptr), 0);
      }
      

      -?- Remarks: The second template argument of the returned span type is static_cast<size_t>(ranges::static_size_of<R>()ranges::size(r)) if R models ranges::static_sized_rangeranges::size(r) is a constant expression, and dynamic_extent otherwise.

    3. Modify 23.2.4 [sequence.reqmts] as indicated:

      a.assign_range(rg)
      

      -60- Result: void

      -61- Mandates: assignable_from<T&, ranges::range_reference_t<R>> is modeled. For inplace_vector, if R models ranges::static_sized_rangeranges::size(rg) is a constant expression then ranges::static_size_of<R>()ranges::size(rg)a.max_size().

      […]
      a.front();
      

      -71- Result: reference; const_reference for constant a.

      -?- Mandates: For array, a.empty() is false.

      -72- Hardened preconditions: a.empty() is false.

      […]

      a.back();

      -75- Result: reference; const_reference for constant a.

      -?- Mandates: For array, a.empty() is false.

      -76- Hardened preconditions: a.empty() is false.

    4. Modify 23.3.16.2 [inplace.vector.cons] as indicated:

      template<container-compatible-range<T> R>
        constexpr inplace_vector(from_range_t, R&& rg);
      

      -?- Mandates: If R models ranges::static_sized_rangeranges::size(rg) is a constant expression then ranges::static_size_of<R>()ranges::size(rg)N.

      -9- Effects: Constructs an inplace_vector with the elements of the range rg.

      -10- Complexity: Linear in ranges::distance(rg).

    5. Modify 23.7.2.2.2 [span.cons] as indicated:

      template<class R> constexpr explicit(extent != dynamic_extent) span(R&& r);
      

      -16- Mandates: If extent is not equal to dynamic_extent and R models ranges::static_sized_rangeranges::size(r) is a constant expression, then ranges::static_size_of<R>()ranges::size(r) == extent is true.

      -17- Constraints: Let U be remove_reference_t<ranges::range_reference_t<R>>. […]

    6. Modify 23.7.2.2.3 [span.deduct] as indicated:

      template<class R>
        span(R&&) -> see belowspan<remove_reference_t<ranges::range_reference_t<R>>>;
      

      -2- Constraints: R satisfies ranges::contiguous_range.

      -?- Remarks: Let T denote the type remove_reference_t<ranges::range_reference_t<R>>. The deduced type is span<T, static_cast<size_t>(ranges::static_size_of<R>())> if R models ranges::static_sized_range, otherwise span<T>.

    7. Modify 23.7.2.2.6 [span.elem] as indicated:

      constexpr reference front() const;
      

      -?- Mandates: If extent is not equal to dynamic_extent, then empty() is false.

      -6- Hardened preconditions: empty() is false.

      […]

      constexpr reference back() const;
      

      -?- Mandates: If extent is not equal to dynamic_extent, then empty() is false.

      -9- Hardened preconditions: empty() is false.

    8. Modify 24.3.4.4 [iterator.concept.winc] as indicated:

      -9- All integer-class types are structural types ([temp.param]) that model regular ([concepts.object]) and three_way_comparable<strong_ordering> ([cmp.concept]).

      -?- Operations on integer-class type shall be usable in constant expressions.

    9. Modify 25.2 [ranges.syn] as indicated:

      // mostly freestanding
      #include <compare>              // see [compare.syn]
      #include <initializer_list>     // see [initializer.list.syn]
      #include <iterator>             // see [iterator.synopsis]
      
      namespace std::ranges {
        […]
        // [range.sized], sized ranges
        template<class>
          constexpr bool disable_sized_range = false;
      
        template<class T>
          concept approximately_sized_range = see below;
      
        template<class T>
          concept sized_range = see below;
      
        //  [range.static.sized], static sized ranges
        template<class T>
          concept static_sized_range = see below;
      
        template<static_sized_range T>
          consteval auto static_size_of();
        […]
      }
      
    10. Add 25.4.? Static sized ranges [range.static.sized] after 25.4.4 [range.sized] as indicated:

      -1- The static_sized_range concept refines sized_range with the requirement that the number of elements in the range can be determined in compile-time using ranges::static_size_of.

      template<class T>
        constexpr bool is-static-sized-range = 
          requires(T& t) { cw<ranges::size(t)>; };      // exposition only
      
      template<class T>
        concept static_sized_range =
          sized_range<T> && is-static-sized-range<T>;
      
      auto cw-size-of(auto& t)
        -> decltype(cw<ranges::size(t)>);               // exposition only
      
      template<static_sized_range T>
        consteval auto static_size_of() {
          return decltype(cw-size-of(declval<T&>()))::value;
        }
    11. Modify 25.5.3.2 [view.interface.members] as indicated:

      constexpr decltype(auto) front() requires forward_range<D>;
      constexpr decltype(auto) front() const requires forward_range<const D>;
      

      -?- Mandates: Let R be decltype(derived()). If R models ranges::static_sized_range, then ranges::static_size_of<R>() > 0.

      -1- Hardened preconditions: !empty() is true.

      -2- Effects: Equivalent to: return *ranges::begin(derived());

      constexpr decltype(auto) back() requires bidirectional_range<D> && common_range<D>;
      constexpr decltype(auto) back() const
        requires bidirectional_range<const D> && common_range<const D>;
      

      -?- Mandates: Let R be decltype(derived()). If R models ranges::static_sized_range, then ranges::static_size_of<R>() > 0.

      -3- Hardened preconditions: !empty() is true.

    12. Edit 25.7.6.2 [range.ref.view] as indicated:

      namespace std::ranges {
        template<range R>
          requires is_object_v<R>
        class ref_view : public view_interface<ref_view<R>> {
        […]
        public:
          […]
          constexpr bool empty() const
            requires requires { ranges::empty(*r_); } { 
            if constexpr (static_sized_range<R>)
              return static_size_of<R>() == 0;
            else
              return ranges::empty(*r_); 
          }
      
          constexpr auto size() const requires sized_range<R> { 
            if constexpr (static_sized_range<R>)
              return static_size_of<R>();
            else
              return ranges::size(*r_); 
          }
          […]
        };
        […]
      }
    13. Modify 25.7.14.2 [range.join.view] as indicated:

      namespace std::ranges {
        template<input_range V>
          requires view<V> && input_range<range_reference_t<V>>>
        class join_view : public view_interface<join_view<V>> {
        […]
        public:
          […]
          constexpr auto size()
            requires sized_range<V> &&
                     static_sized_range<InnerRng> {
            using CT = common_type_t<range_size_t<V>, range_size_t<InnerRng>>;
            return CT(ranges::size(base_)) * CT(static_size_of<InnerRng>());
          }
      
          constexpr auto size() const
            requires sized_range<const V> &&
                     static_sized_range<range_reference_t<const V>> {
            using InnerConstRng = range_reference_t<const V>;
            using CT = common_type_t<range_size_t<const V>, range_size_t<InnerConstRng>>;
            return CT(ranges::size(base_)) * CT(static_size_of<InnerConstRng>());
          }
        };
        […]
      }
      
    14. Modify 25.7.15.2 [range.join.with.view] as indicated:

      namespace std::ranges {
        […]
        template<input_range V, forward_range Pattern>
          requires view<V> && input_range<range_reference_t<V>>
                && view<Pattern>
                && concatable<range_reference_t<V>, Pattern>
        class join_with_view : public view_interface<join_with_view<V, Pattern>> {
        […]
        public:
          […]
          constexpr auto size()
            requires sized_range<V> &&
                     sized_range<Pattern> &&
                     static_sized_range<InnerRng> {
            using CT = common_type_t<
              range_size_t<V>, range_size_t<InnerRng>, range_size_t<Pattern>>;
            const auto base_size = ranges::size(base_);
            if (base_size == 0)
              return CT(0);
            return CT(base_size) * CT(static_size_of<InnerRng>()) +
                   CT(base_size - 1) * CT(ranges::size(pattern_));
          }
      
          constexpr auto size() const
            requires sized_range<const V> &&
                     sized_range<const Pattern> &&
                     static_sized_range<range_reference_t<const V>> {
            using InnerConstRng = range_reference_t<const V>;
            using CT = common_type_t<
              range_size_t<const V>, range_size_t<InnerConstRng>, range_size_t<const Pattern>>;
            const auto base_size = ranges::size(base_);
            if (base_size == 0)
              return CT(0);
            return CT(base_size) * CT(static_size_of<InnerConstRng>()) +
                   CT(base_size - 1) * CT(ranges::size(pattern_));
          }
        };
        […]
      }
      
    15. Modify 25.7.16.2 [range.lazy.split.view] as indicated:

      namespace std::ranges {
        template<auto> struct require-constant;                       // exposition only
      
        template<class R>
        concept tiny-range =                                          // exposition only
          static_sized_range<R> &&
          requires { typename require-constant<remove_reference_t<R>::size()>; } &&
          (static_size_of<R>()remove_reference_t<R>::size() <= 1);
      
        template<input_range V, forward_range Pattern>
          requires view<V> && view<Pattern> &&
                   indirectly_comparable<iterator_t<V>, iterator_t<Pattern>, ranges::equal_to> &&
                   (forward_range<V> || tiny-range<Pattern>)
        class lazy_split_view::view_interface<lazy_split_view<V, Pattern>> {
          […]
          constexpr auto size()
            requires sized_range<V> &&
                     static_sized_range<Pattern> &&
                     (static_size_of<Pattern>() == 0)
          { return ranges::size(base_); }
      
          constexpr auto size() const
            requires sized_range<const V> &&
                     static_sized_range<Pattern> &&
                     (static_size_of<Pattern>() == 0)
          { return ranges::size(base_); }
        };
        […]
      }
      
    16. Modify 25.7.16.5 [range.lazy.split.inner] as indicated:

      constexpr inner-iterator& operator++();
      

      -5- Effects: Equivalent to:

      incremented_ = true;
      if constexpr (!forward_range<Base>) {
        if constexpr (static_size_of<Pattern>Pattern::size() == 0) {
          return *this;
        }
      }
      ++i_.current;
      return *this;
      
    17. Modify 25.7.17.2 [range.split.view] as indicated:

      namespace std::ranges {
        template<forward_range V, forward_range Pattern>
          requires view<V> && view<Pattern> &&
                   indirectly_comparable<iterator_t<V>, iterator_t<Pattern>, ranges::equal_to>
        class split_view : public view_interface<split_view<V, Pattern>> {
          […]
          constexpr auto size()
            requires sized_range<V> &&
                     static_sized_range<Pattern> &&
                     (static_size_of<Pattern>() == 0) {
            return ranges::size(base_);
          }
      
          constexpr auto size() const
            requires sized_range<const V> &&
                     static_sized_range<Pattern> &&
                     (static_size_of<Pattern>() == 0) {
            return ranges::size(base_);
          }
        };
        […]
      }
      
    18. Modify 26.8.9 [alg.min.max] as indicated:

      template<class T>
        constexpr T min(initializer_list<T> r);
      […]
      template<execution-policy Ep, sized-random-access-range R, class Proj = identity,
               indirect_strict_weak_order<projected<iterator_t<R>, Proj>> Comp = ranges::less>
        requires indirectly_copyable_storable<iterator_t<R>, range_value_t<R>*>
        range_value_t<R>
          ranges::min(Ep&& exec, R&& r, Comp comp = {}, Proj proj = {});
      

      -?- Mandates: If R models ranges::static_sized_range, then ranges::static_size_of<R>() > 0.

      -5- Preconditions: ranges::distance(r) > 0. […]

      […]
      template<class T>
        constexpr T max(initializer_list<T> r);
      […]
      template<execution-policy Ep, sized-random-access-range R, class Proj = identity,
               indirect_strict_weak_order<projected<iterator_t<R>, Proj>> Comp = ranges::less>
        requires indirectly_copyable_storable<iterator_t<R>, range_value_t<R>*>
        range_value_t<R>
          ranges::max(Ep&& exec, R&& r, Comp comp = {}, Proj proj = {});
      

      -?- Mandates: If R models ranges::static_sized_range, then ranges::static_size_of<R>() > 0.

      -13- Preconditions: ranges::distance(r) > 0. […]

      […]
      template<class T>
        constexpr pair<T, T> minmax(initializer_list<T> t);
      […]
      template<execution-policy Ep, sized-random-access-range R, class Proj = identity,
               indirect_strict_weak_order<projected<iterator_t<R>, Proj>> Comp = ranges::less>
        requires indirectly_copyable_storable<iterator_t<R>, range_value_t<R>*>
        ranges::minmax_result<range_value_t<R>>
          ranges::minmax(Ep&& exec, R&& r, Comp comp = {}, Proj proj = {});
      

      -?- Mandates: If R models ranges::static_sized_range, then ranges::static_size_of<R>() > 0.

      -21- Preconditions: ranges::distance(r) > 0. […]

    19. Modify 29.10.7.2 [simd.ctor] as indicated:

      template<class R, class... Flags>
        constexpr basic_vec(R&& r, flags<Flags...> = {});
      template<class R, class... Flags>
        constexpr basic_vec(R&& r, const mask_type& mask, flags<Flags...> = {});
      

      -12- Let mask be mask_type(true) for the overload with no mask parameter.

      -13- Constraints:

      1. (13.1) — R models ranges::contiguous_range and ranges::static_sized_range,

      2. (13.2) — ranges::size(r) is a constant expression,

      3. (13.3) — ranges::static_size_of<R>()ranges::size(r) is equal to size(), and

      4. (13.4) — ranges::range_value_t<R> is a vectorizable type and satisfies explicitly-convertible-to<T>.

      […]
      template<class R, class... Ts>
        basic_vec(R&& r, Ts...) -> see below;
      

      -17- Constraints:

      1. (17.1) — R models ranges::contiguous_range and ranges::static_sized_range., and

      2. (17.2) — ranges::size(r) is a constant expression.

      -18- Remarks: The deduced type is equivalent to vec<ranges::range_value_t<R>, static_cast<simd-size-type>(ranges::static_size_of<R>()ranges::size(r))>.

    20. Modify 29.10.8.6 [simd.loadstore] as indicated:

      template<class V = see below, ranges::contiguous_range R, class... Flags>
        requires ranges::sized_range<R>
        constexpr V unchecked_load(R&& r, flags<Flags...> f = {});
      […]
      template<class V = see below, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>
        constexpr V unchecked_load(I first, S last, const typename V::mask_type& mask,
                                   flags<Flags...> f = {});
      

      -1- Let […]

      -2- Mandates: If R models ranges::static_sized_rangeranges::size(r) is a constant expression then ranges::static_size_of<R>()ranges::size(r)V::size().

      […]
      template<class T, class Abi, ranges::contiguous_range R, class... Flags>
        requires ranges::sized_range<R>
        constexpr void unchecked_store(const basic_vec<T, Abi>& v, R&& r, flags<Flags...> f = {});
      […]
      template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>
        constexpr void unchecked_store(const basic_vec<T, Abi>& v, I first, S last,
          const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {});
      

      -11- Let […]

      -22- Mandates: If R models ranges::static_sized_rangeranges::size(r) is a constant expression then ranges::static_size_of<R>()ranges::size(r)simd-size-v<T, Abi>.