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

3855. tiny-range is not quite right

Section: 25.7.16.2 [range.lazy.split.view] Status: New Submitter: Hewill Kang Opened: 2023-01-07 Last modified: 2025-04-28

Priority: 4

View other active issues in [range.lazy.split.view].

View all other issues in [range.lazy.split.view].

View all issues with New status.

Discussion:

Currently, lazy_split_view supports input range when the element of the pattern is less than or equal to 1. In order to ensure this condition at compile time, tiny-range constrains the type R to model sized_range and requires (remove_reference_t<R>::size() <= 1) to be a constant expression.

However, modeling a sized_range does not guarantee that ranges::size will be evaluated by R::size(). For example, when disable_sized_range<R> is specialized to true or R::size() returns a non-integer-like type, ranges::size can still compute the size by subtracting the iterator-sentinel pair when both satisfy sized_sentinel_for.

Since the lazy_split_view's iterator uses R::size() to get the constant value of the pattern, we must ensure that this is indeed how ranges::size is calculated. Also, I think we can simplify tiny-range with bool_constant in a way similar to LWG 3150(i), which removes the introduction of require-constant.

[2023-02-01; Reflector poll]

Set priority to 4 after reflector poll. Only matters for pathological types. Maybe use requires bool_constant<ranges::size(r) <= 1>.

Previous resolution [SUPERSEDED]:

This wording is relative to N4917.

  1. 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
         sized_range<R> &&
         requires { typename require-constant<remove_reference_t<R>::size()>; } &&
         (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 : public view_interface<lazy_split_view<V, Pattern>> {
        […]
      };
      […]
    }
    
    
    template<class R>
    concept tiny-range = // exposition only
       sized_range<R> &&
       requires { requires bool_constant<(remove_reference_t<R>::size() <= 1)>::value; };
    

    -?- Given an lvalue r of type remove_reference_t<R>, R models tiny-range only if ranges::size(r) is evaluated by remove_reference_t<R>::size().

    constexpr lazy_split_view(V base, Pattern pattern);
    

    -1- Effects: : Initializes base_ with std::move(base), and pattern_ with std::move(pattern).

[2025-04-27, Hewill provides alternative wording]

Proposed resolution:

This wording is relative to N5008.

  1. Modify 25.7.16.2 [range.lazy.split.view] as indicated:

    [Drafting note: This benefits from P2280 that a call to a member function of a non-constexpr object can be a constant expression if it does not actually access the member.

    This would make views::lazy_split(r, span<int, 0>{}) well-formed, which can be seen as an enhancement.]

    namespace std::ranges {
      template<auto> struct require-constant;                       // exposition only
    
      template<class R>
      concept tiny-range =                                          // exposition only
        sized_range<R> &&
        requires (R& r) { requires bool_constant<ranges::size(r) <= 1>::value; }
        requires { typename require-constant<remove_reference_t<R>::size()>; } &&
        (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>> {
        […]
      };
      […]
    }
    
  2. Modify 25.7.16.5 [range.lazy.split.inner] as indicated:

    [Drafting note: We can't use if constexpr (ranges::size(i_.parent_->pattern_) == 0) here because it is not a constant expression, and it seems more intuitive to just use ranges::empty combined with runtime if which is always well-formed. Note that the PR does not seek the aggressive optimization that minimizes the instantiation as this is not the intent of the current design (for example, outer-iterator& operator++() can be specialized for the case where Pattern::size() == 0 to save some O(1) comparisons), library implementations are free to optimize as it pleases.]

    constexpr inner-iterator& operator++();
    

    -5- Effects: Equivalent to:

    incremented_ = true;
    if constexpr (!forward_range<Base>) {
      if (ranges::empty(i_.parent_->pattern_))
      if constexpr (Pattern::size() == 0) {
        return *this;
      }
    }
    ++i_.current;
    return *this;