This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.

3282. subrange converting constructor should disallow derived to base conversions

Section: 24.5.3 [range.subrange] Status: WP Submitter: Eric Niebler Opened: 2019-09-10 Last modified: 2020-02-24

Priority: 1

View all other issues in [range.subrange].

View all issues with WP status.

Discussion:

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.

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

[2019-10; Marshall comments]

This issue would resolve US-285.

[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 24.5.3 [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 24.5.3.1 [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: […]

    […]

[2020-02-10; Prague]

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

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

Proposed resolution:

This wording is relative to N4830.

  1. Modify 24.5.3 [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 24.5.3.1 [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: […]

    […]