3326. enable_view has false positives

Section: 25.4.4 [range.view] Status: C++20 Submitter: Germany Opened: 2019-11-06 Last modified: 2021-02-25

Priority: 0

View all other issues in [range.view].

View all issues with C++20 status.

Discussion:

Addresses DE 282

"Since the difference between range and view is largely semantic, the two are differentiated with the help of enable_view." (§3)

enable_view is designed as on opt-in trait to specify that a type is a view. It defaults to true for types derived from view_base (§4.2) which is clearly a form of opt-in. But it also employs a heuristic assuming that anything with iterator == const_iterator is also view (§4.3).

This is a very poor heuristic, the same paragraph already needs to define six exceptions from this rule for standard library types (§4.2).

Experience in working with range-v3 has revealed multiple of our own library types as being affected from needing to opt-out from the "auto-opt-in", as well. This is counter-intuitive: something that was never designed to be a view shouldn't go through hoops so that it isn't treated as a view.

Proposed change:

Make enable_view truly be opt-in by relying only on explicit specialisation or inheritance from view_base. This means removing 24.4.4 §4.2 - §4.4 and introducing new §4.2 "Otherwise, false".

Double-check if existing standard library types like basic_string_view and span need to opt-in to being a view now.

Casey Carter:

enable_view (25.4.4 [range.view]) is designed as on opt-in trait to specify that a type is a view. It defaults to true for types derived from view_base — which is a form of opt-in — and it also employs a heuristic. Unfortunately, the heuristic has false positives. The working draft itself includes six exceptions to the heuristic for standard library types. Since false positives are much more problematic for users than false negatives, we should eliminate the heuristic.

[2019-11 Status to Ready during Wednesday night issue processing in Belfast.]

Proposed resolution:

This wording is relative to N4835.

  1. Modify 25.4.4 [range.view] as indicated:

    template<class T>
      inline constexpr bool enable_view = see belowderived_from<T, view_base>;
    

    -4- Remarks: For a type T, the default value of enable_view<T> is:

    1. (4.1) — If derived_from<T, view_base> is true, true.

    2. (4.2) — Otherwise, if T is a specialization of class template initializer_list (17.10 [support.initlist]), set (23.4.6 [set]), multiset (23.4.7 [multiset]), unordered_set (23.5.6 [unord.set]), unordered_multiset (23.5.7 [unord.multiset]), or match_results (28.6.9 [re.results]), false.

    3. (4.3) — Otherwise, if both T and const T model range and range_reference_t<T> is not the same type as range_reference_t<const T>, false. [Note: Deep const-ness implies element ownership, whereas shallow const-ness implies reference semantics. — end note]

    4. (4.4) — Otherwise, true.

  2. Modify 27.3.2 [string.view.synop], header <string_view> synopsis, as indicated:

    namespace std {
      // 27.3.3 [string.view.template], class template basic_string_view
      template<class charT, class traits = char_traits<charT>>
      class basic_string_view;
      
      template<class charT, class traits>
        inline constexpr bool ranges::enable_view<basic_string_view<charT, traits>> = true;
        
      […]
    }
    
  3. Modify 23.7.2.1 [span.syn], header <span> synopsis, as indicated:

    namespace std {
      // constants
      inline constexpr size_t dynamic_extent = numeric_limits<size_t>::max();
      
      // 23.7.2.2 [views.span], class template span
      template<class ElementType, size_t Extent = dynamic_extent>
      class span;
        
      template<class ElementType, size_t Extent>
        inline constexpr bool ranges::enable_view<span<ElementType, Extent>> = Extent == 0 || 
          Extent == dynamic_extent;
        
      […]
    }
    
  4. Modify [range.split.outer.value], class split_view::outer_iterator::value_type synopsis, as indicated:

    [Drafting note: The following applies the proposed wording for LWG 3276]

    namespace std::ranges {
      template<class V, class Pattern>
      template<bool Const>
      struct split_view<V, Pattern>::outer_iterator<Const>::value_type 
        : view_interface<value_type> {
      private:
        outer_iterator i_ = outer_iterator(); // exposition only
      public:
        value_type() = default;
        constexpr explicit value_type(outer_iterator i);
    
        constexpr inner_iterator<Const> begin() const;
        constexpr default_sentinel_t end() const;
      };
    }