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

### 3282. `subrange` converting constructor should disallow derived to base conversions

Section: 24.5.4 [range.subrange] Status: C++20 Submitter: Eric Niebler Opened: 2019-09-10 Last modified: 2021-02-25

Priority: 1

View all other issues in [range.subrange].

View all issues with C++20 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.4 [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.4.2 [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.4 [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.4.2 [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: […]

   > > […]