Title
subrange converting constructor should disallow derived to base conversions
Status
c++20
Section
[range.subrange]
Submitter
Eric Niebler

Created on 2019-09-10.00:00:00 last changed 45 months ago

Messages

Date: 2020-02-10.19:48:51

Proposed resolution:

This wording is relative to N4830.

  1. Modify [range.subrange] as indicated:

    namespace std::ranges {
      template<class From, class To>
        concept convertible-to-non-slicing = // exposition only
          convertible_to<From, To> &&
          !(is_pointer_v<decay_t<From>> &&
          is_pointer_v<decay_t<To>> &&
          not-same-as<remove_pointer_t<decay_t<From>>, remove_pointer_t<decay_t<To>>>);
          
      template<class T>
        concept pair-like = // exposition only
          […]
          
      template<class T, class U, class V>
        concept pair-like-convertible-to = // exposition only
          !range<T> && pair-like<remove_reference_t<T>> &&
          requires(T&& t) {
            { get<0>(std::forward<T>(t)) } -> convertible_to<U>;
            { get<1>(std::forward<T>(t)) } -> convertible_to<V>;
          };
          
       template<class T, class U, class V>
         concept pair-like-convertible-from = // exposition only
           !range<T> && pair-like<T> && 
           constructible_from<T, U, V> &&
           convertible-to-non-slicing<U, tuple_element_t<0, T>> &&
           convertible_to<V, tuple_element_t<1, T>>;
    
    […]
    […]
      template<input_or_output_iterator I, sentinel_for<I> S = I, subrange_kind K =
               sized_sentinel_for<S, I> ? subrange_kind::sized : subrange_kind::unsized>
        requires (K == subrange_kind::sized || !sized_sentinel_for<S, I>)
      class subrange : public view_interface<subrange<I, S, K>> {
      private:
        […]
      public:
        subrange() = default;
        
        constexpr subrange(convertible-to-non-slicing<I> auto i, S s) requires (!StoreSize);
        
        constexpr subrange(convertible-to-non-slicing<I> auto i, S s, 
                           make-unsigned-like-t(iter_difference_t<I>) n) requires (K == subrange_kind::sized);
        
        template<not-same-as<subrange> R>
          requires forwarding-range<R> &&
            convertible_toconvertible-to-non-slicing<iterator_t<R>, I> && 
            convertible_to<sentinel_t<R>, S>
        constexpr subrange(R&& r) requires (!StoreSize || sized_range<R>);
        
        template<forwarding-range R>
          requires convertible_toconvertible-to-non-slicing<iterator_t<R>, I> && 
    	    convertible_to<sentinel_t<R>, S>
        constexpr subrange(R&& r, make-unsigned-like-t(iter_difference_t<I>) n)
          requires (K == subrange_kind::sized)
            : subrange{ranges::begin(r), ranges::end(r), n}
        {}
        
        template<not-same-as<subrange> PairLike>
          requires pair-like-convertible-to<PairLike, I, S>
        constexpr subrange(PairLike&& r) requires (!StoreSize)
          : subrange{std::get<0>(std::forward<PairLike>(r)),
                     std::get<1>(std::forward<PairLike>(r))}
        {}
    
        template<pair-like-convertible-to<I, S> PairLike>
        constexpr subrange(PairLike&& r, make-unsigned-like-t(iter_difference_t<I>) n)
          requires (K == subrange_kind::sized)
          : subrange{std::get<0>(std::forward<PairLike>(r)),
                     std::get<1>(std::forward<PairLike>(r)), n}
        {}
      […]
      };
      
      template<input_or_output_iterator I, sentinel_for<I> S>
      subrange(I, S) -> subrange<I, S>;  
      
      […]
    }
    
  2. Modify [range.subrange.ctor] as indicated:

    constexpr subrange(convertible-to-non-slicing<I> auto i, S s) requires (!StoreSize);
    

    -1- Expects: […]

    […]

    constexpr subrange(convertible-to-non-slicing<I> auto i, S s, 
                       make-unsigned-like-t(iter_difference_t<I>) n) requires (K == subrange_kind::sized);
    

    -2- Expects: […]

    […]

    template<not-same-as<subrange> R>
      requires forwarding-range<R> &&
        convertible_toconvertible-to-non-slicing<iterator_t<R>, I> && 
        convertible_to<sentinel_t<R>, S>
    constexpr subrange(R&& r) requires (!StoreSize || sized_range<R>);
    

    -6- Effects: […]

    […]

Date: 2020-02-10.00:00:00

[ 2020-02-10 Move to Immediate Monday afternoon in Prague ]

Date: 2020-02-15.00:00:00

[ 2020-02-10; Prague ]

The group identified minor problems that have been fixed in the revised wording.

Date: 2020-02-10.15:42:40

[ 2019-11 LEWG says OK; Status to Open. Friday PM discussion in Belfast. Casey to investigate and report back. ]

Previous resolution [SUPERSEDED]:

This wording is relative to N4830.

  1. Modify [range.subrange] as indicated:

    namespace std::ranges {
      template<class From, class To>
        concept convertible-to-non-slicing = // exposition only
          convertible_to<From, To> &&
          !(is_pointer_v<decay_t<From>> &&
          is_pointer_v<decay_t<To>> &&
          not-same-as<remove_pointer_t<decay_t<From>>, remove_pointer_t<decay_t<To>>>);
          
      template<class T>
        concept pair-like = // exposition only
          […]
          
      template<class T, class U, class V>
        concept pair-like-convertible-to = // exposition only
          !range<T> && pair-like<remove_reference_t<T>> &&
          requires(T&& t) {
            { get<0>(std::forward<T>(t)) } -> convertible_to<U>;
            { get<1>(std::forward<T>(t)) } -> convertible_to<V>;
          };
          
       template<class T, class U, class V>
         concept pair-like-convertible-from = // exposition only
           !range<T> && pair-like<T> && 
           constructible_from<T, U, V> &&
           convertible-to-non-slicing<U, tuple_element_t<0, T>> &&
           convertible_to<V, tuple_element_t<1, T>>;
    
    […]
    […]
      template<input_or_output_iterator I, sentinel_for<I> S = I, subrange_kind K =
               sized_sentinel_for<S, I> ? subrange_kind::sized : subrange_kind::unsized>
        requires (K == subrange_kind::sized || !sized_sentinel_for<S, I>)
      class subrange : public view_interface<subrange<I, S, K>> {
      private:
        […]
      public:
        subrange() = default;
        
        constexpr subrange(convertible-to-non-slicing<I> auto i, S s) requires (!StoreSize);
        
        constexpr subrange(convertible-to-non-slicing<I> auto i, S s, 
                           make-unsigned-like-t(iter_difference_t<I>) n) requires (K == subrange_kind::sized);
        
        template<not-same-as<subrange> R>
          requires forwarding-range<R> &&
            convertible_toconvertible-to-non-slicing<iterator_t<R>, I> && 
            convertible_to<sentinel_t<R>, S>
        constexpr subrange(R&& r) requires (!StoreSize || sized_range<R>);
        
        template<forwarding-range R>
          requires convertible_to<iterator_t<R>, I> && convertible_to<sentinel_t<R>, S>
        constexpr subrange(R&& r, make-unsigned-like-t(iter_difference_t<I>) n)
          requires (K == subrange_kind::sized)
            : subrange{ranges::begin(r), ranges::end(r), n}
        {}
        
        template<not-same-as<subrange> PairLike>
          requires pair-like-convertible-to<PairLike, I, S>
        constexpr subrange(PairLike&& r) requires (!StoreSize)
          : subrange{std::get<0>(std::forward<PairLike>(r)),
                     std::get<1>(std::forward<PairLike>(r))}
        {}
    
        template<pair-like-convertible-to<I, S> PairLike>
        constexpr subrange(PairLike&& r, make-unsigned-like-t(iter_difference_t<I>) n)
          requires (K == subrange_kind::sized)
          : subrange{std::get<0>(std::forward<PairLike>(r)),
                     std::get<1>(std::forward<PairLike>(r)), n}
        {}
      […]
      };
      
      template<input_or_output_iterator I, sentinel_for<I> S>
      subrange(I, S) -> subrange<I, S>;  
      
      […]
    }
    
  2. Modify [range.subrange.ctor] as indicated:

    constexpr subrange(convertible-to-non-slicing<I> auto i, S s) requires (!StoreSize);
    

    -1- Expects: […]

    […]

    constexpr subrange(convertible-to-non-slicing<I> auto i, S s, 
                       make-unsigned-like-t(iter_difference_t<I>) n) requires (K == subrange_kind::sized);
    

    -2- Expects: […]

    […]

    template<not-same-as<subrange> R>
      requires forwarding-range<R> &&
        convertible_toconvertible-to-non-slicing<iterator_t<R>, I> && 
        convertible_to<sentinel_t<R>, S>
    constexpr subrange(R&& r) requires (!StoreSize || sized_range<R>);
    

    -6- Effects: […]

    […]

Date: 2020-02-10.15:15:47

[ 2019-10; Marshall comments ]

This issue would resolve US-285.

Date: 2019-10-07.02:21:30

[ 2019-10 Priority set to 1 and status to LEWG after reflector discussion ]

Date: 2019-09-10.00:00:00

The following code leads to slicing and general badness:

struct Base {};
struct Derived : Base {};
subrange<Derived*> sd;
subrange<Base*> sb = sd;

Traversal operations on iterators that are pointers do pointer arithmetic. If a Base* is actually pointing to a Derived*, then pointer arithmetic is invalid. subrange's constructors can easily flag this invalid code, and probably should.

The following PR incorporates the suggested fix to issue LWG 3281 I previously reported.

Suggested priority: P1, since it will be hard to fix this after C++20 ships.

History
Date User Action Args
2021-02-25 10:48:01adminsetstatus: wp -> c++20
2020-02-24 16:02:59adminsetstatus: immediate -> wp
2020-02-10 19:48:51adminsetmessages: + msg11032
2020-02-10 19:48:51adminsetstatus: open -> immediate
2020-02-10 15:42:40adminsetmessages: + msg11013
2020-02-10 15:15:47adminsetmessages: + msg11011
2019-11-08 15:56:47adminsetmessages: + msg10808
2019-11-08 15:56:47adminsetstatus: lewg -> open
2019-10-07 02:21:30adminsetmessages: + msg10685
2019-10-07 02:21:30adminsetstatus: new -> lewg
2019-09-16 17:18:04adminsetmessages: + msg10635
2019-09-10 00:00:00admincreate