3685. In lazy_split_view, CTAD doesn't work when given an input_range input and a tiny-range pattern

Section: 25.7.16.2 [range.lazy.split.view] Status: New Submitter: Konstantin Varlamov Opened: 2022-03-23 Last modified: 2022-05-17

Priority: 3

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:

In lazy_split_view, the deduction guide that accepts two arbitrary types wraps the arguments in views::all_t (25.7.16.2 [range.lazy.split.view]):

template<class R, class P>
  lazy_split_view(R&&, P&&) -> lazy_split_view<views::all_t<R>, views::all_t<P>>;

When trying to use an input_range as the input, lazy_split_view requires the pattern type to satisfy the exposition-only concept tiny-range. Trying to use CTAD with an input_range and a tiny-range as arguments results in a compiler error, as demonstrated in the demo link:

// Assuming InputRange and TinyRange are valid types satisfying the
// corresponding concepts.
std::ranges::lazy_split_view view{InputRange(), TinyRange()}; // Compiler error

The underlying reason is that tiny-range requires the given type to contain a static member function size() that returns a number <=1 (25.7.16.2 [range.lazy.split.view]):

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, when given a range, views::all_t wraps the type in a ranges::owning_view. owning_view doesn't satisfy tiny-range for any template parameter because it never contains the static size() function required by the concept.

A general resolution might be modifying owning_view so that it satisfies tiny-range when the given type is a tiny-range (that would require moving the tiny-range concept from 25.7.16.2 [range.lazy.split.view] to 25.5.2 [range.utility.helpers]). A more localized solution can be to change the deduction guide in lazy_split_view to avoid wrapping a type satisfying tiny-range in views::all_t.

[2022-05-17; Reflector poll]

Set priority to 3 after reflector poll. One vote for NAD.

Proposed resolution:

This wording is relative to N4910.

  1. Modify 25.5.2 [range.utility.helpers] as indicated:

    [Drafting note: This change effectively just moves the definitions of require-constant and tiny-range from 25.7.16.2 [range.lazy.split.view] to 25.5.2 [range.utility.helpers].]

    […]
    template<class T, class U>
      concept different-from = // exposition only
        !same_as<remove_cvref_t<T>, remove_cvref_t<U>>;
    
    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);
    
  2. Modify 25.7.6.3 [range.owning.view], class template owning_view synopsis, as indicated:

    […]
    constexpr static auto size() requires tiny-range<R>
    { return R::size(); }
    constexpr auto size() requires sized_range<R>
    { return ranges::size(r_); }
    constexpr auto size() const requires sized_range<const R>
    { return ranges::size(r_); }
    […]
    
  3. Modify 25.7.16.2 [range.lazy.split.view], class template lazy_split_view synopsis, as indicated:

    [Drafting note: This change effectively just moves the definitions of require-constant and tiny-range from 25.7.16.2 [range.lazy.split.view] to 25.5.2 [range.utility.helpers].]

    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>> {
        […]
      };
      […]
    }