Title
transform_view's sentinel<false> not comparable with iterator<true>
Status
new
Section
[range.transform.sentinel][range.join.sentinel]
Submitter
Jonathan Wakely

Created on 2020-05-26.00:00:00 last changed 1 month ago

Messages

Date: 2020-05-28.18:45:19

Proposed resolution:

This wording is relative to N4861.

  1. Modify [ranges.syn], header <ranges> synopsis, as indicated:

    [Drafting note: The project editor is kindly asked to consider replacing editorially all of the

    "using Base = conditional_t<Const, const V, V>;"

    occurrences by

    "using Base = maybe-const<Const, V>;"

    ]

    […]
    namespace std::ranges {
    […]
      namespace views { inline constexpr unspecified filter = unspecified; }
      
      template<bool Const, class T>
        using maybe-const = conditional_t<Const, const T, T>; // exposition-only
      
      // [range.transform], transform view
      template<input_range V, copy_constructible F>
        requires view<V> && is_object_v<F> &&
                 regular_invocable<F&, range_reference_t<V>>
      class transform_view;
    […]
    }
    
  2. Modify [range.transform.sentinel], class template transform_view::sentinel synopsis, as indicated:

    namespace std::ranges {
      template<input_range V, copy_constructible F>
        requires view<V> && is_object_v<F> &&
                 regular_invocable<F&, range_reference_t<V>> &&
                 can-reference<invoke_result_t<F&, range_reference_t<V>>>
      template<bool Const>
      class transform_view<V, F>::sentinel {
        […]
        constexpr sentinel_t<Base> base() const;
        
        template<bool OtherConst>
          requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> 
        friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
        
        template<bool OtherConst>
          requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> 
        friend constexpr range_difference_t<Base>
          operator-(const iterator<OtherConst>& x, const sentinel& y)
            requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
    
        template<bool OtherConst>
          requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> 
        friend constexpr range_difference_t<Base>
          operator-(const sentinel& y, const iterator<OtherConst>& x)
            requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
      };
    }
    
    […]
    template<bool OtherConst>
      requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> 
    friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
    

    -4- Effects: Equivalent to: return x.current_ == y.end_;

    template<bool OtherConst>
      requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> 
    friend constexpr range_difference_t<Base>
      operator-(const iterator<OtherConst>& x, const sentinel& y)
        requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
    

    -5- Effects: Equivalent to: return x.current_ - y.end_;

    template<bool OtherConst>
      requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> 
    friend constexpr range_difference_t<Base>
      operator-(const sentinel& y, const iterator<OtherConst>& x)
        requires sized_sentinel_for<sentinel_t<Base>, iterator_t<Base>>;
    

    -6- Effects: Equivalent to: return y.end_ - x.current_;

  3. Modify [range.join.sentinel], class template join_view::sentinel synopsis, as indicated:

    namespace std::ranges {
      template<input_range V>
        requires view<V> && input_range<range_reference_t<V>> &&
                 (is_reference_v<range_reference_t<V>> ||
                 view<range_value_t<V>>)
      template<bool Const>
      class join_view<V>::sentinel {
        […]
    
        template<bool OtherConst>
          requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
        friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);  
    };
    }
    
    […]
    template<bool OtherConst>
      requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
    friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);  
    

    -3- Effects: Equivalent to: return x.outer_ == y.end_;

Date: 2020-05-26.00:00:00

A user reported that this doesn't compile:

#include <list>
#include <ranges>

std::list v{1, 2}; // works if std::vector
auto view1 = v | std::views::take(2);
auto view2 = view1 | std::views::transform([] (int i) { return i; });
bool b = std::ranges::cbegin(view2) == std::ranges::end(view2);

The comparison is supposed to use operator==(iterator<Const>, sentinel<Const>) after converting sentinel<false> to sentinel<true>. However, the operator== is a hidden friend so is not a candidate when comparing iterator<true> with sentinel<false>. The required conversion would only happen if we'd found the operator, but we can't find the operator until after the conversion happens.

As Patrick noted, the join_view sentinel has a similar problem.

The proposed wording shown below has been suggested by Casey and has been implemented and tested in GCC's libstdc++.

History
Date User Action Args
2020-05-28 18:45:19adminsetmessages: + msg11309
2020-05-26 00:00:00admincreate