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

Created on 2020-05-26.00:00:00 last changed 12 months ago

Messages

Date: 2020-11-09.21:40:50

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<Basemaybe-const<OtherConst, V>>
          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<Basemaybe-const<OtherConst, V>>
          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<Basemaybe-const<OtherConst, V>>
      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<Basemaybe-const<OtherConst, V>>
      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-11-09.00:00:00

[ 2020-11-09 Approved In November virtual meeting. Status changed: Tentatively Ready → WP. ]

Date: 2020-10-15.00:00:00

[ 2020-10-02; Status to Tentatively Ready after five positive votes on the reflector ]

Date: 2020-08-21.00:00:00

[ 2020-08-21 Tim updates PR per telecon discussion ]

As noted in the PR of LWG 3406, the return type of operator- should be based on the constness of the iterator rather than that of the sentinel, as sized_sentinel_for<S, I> ([iterator.concept.sizedsentinel]) requires decltype(i - s) to be iter_difference_t<I>.

Date: 2020-07-15.00:00:00

[ 2020-07-17; Priority set to 1 in telecon ]

Should be considered together with 3406 and 3449.

Previous resolution [SUPERSEDED]:

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
2023-11-22 15:47:43adminsetstatus: wp -> c++23
2020-11-09 21:40:50adminsetmessages: + msg11577
2020-11-09 21:40:50adminsetstatus: ready -> wp
2020-10-02 17:02:44adminsetmessages: + msg11496
2020-10-02 17:02:44adminsetstatus: new -> ready
2020-08-22 01:49:00adminsetmessages: + msg11456
2020-07-17 22:37:26adminsetmessages: + msg11399
2020-05-28 18:45:19adminsetmessages: + msg11309
2020-05-26 00:00:00admincreate