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.

### 3502. `elements_view` should not be allowed to return dangling references

Section: 24.7.18.3 [range.elements.iterator] Status: WP Submitter: Tim Song Opened: 2020-11-18 Last modified: 2021-02-26

Priority: 2

View all other issues in [range.elements.iterator].

View all issues with WP status.

Discussion:

This compiles but the resulting view is full of dangling references:

>     std::vector<int> vec = {42};
>     auto r = vec | std::views::transform([](auto c) { return std::make_tuple(c, c); })
>                  | std::views::keys;

This is because `elements_view::iterator::operator*` is specified as

>     constexpr decltype(auto) operator*() const { return get<N>(*current_); }

Here `*current_` is a prvalue, and so the `get<N>` produces a reference into the materialized temporary that becomes dangling as soon as `operator*` returns.

We should either ban this case altogether, or make `operator*` (and `operator[]`) return by value when `*current_` is a prvalue and the corresponding tuple element is not a reference (since this `get` is `std::get`, we need not worry about weird user-defined overloads.)

[2020-11-29; Reflector prioritization]

Set priority to 2 during reflector discussions.

[2021-01-31 Tim adds PR]

[2021-02-08; Reflector poll]

Set status to Tentatively Ready after six votes in favour during reflector poll.

[2021-02-26 Approved at February 2021 virtual plenary. Status changed: Tentatively Ready → WP.]

Proposed resolution:

This wording is relative to N4878.

1. Modify 24.7.18.2 [range.elements.view], as indicated:

   >     namespace std::ranges {
   >       template<class T, size_t N>
   >         concept has-tuple-element =                   // exposition only
   >           requires(T t) {
   >             typename tuple_size<T>::type;
   >             requires N < tuple_size_v<T>;
   >             typename tuple_element_t<N, T>;
   >             { get<N>(t) } -> convertible_to<const tuple_element_t<N, T>&>;
   >           };

   >       template<class T, size_t N>
   >       concept returnable-element = is_reference_v<T> || move_constructible<tuple_element_t<N, T>>;

   >       template<input_range V, size_t N>
   >         requires view<V> && has-tuple-element<range_value_t<V>, N> &&
   >                  has-tuple-element<remove_reference_t<range_reference_t<V>>, N> &&
   >                  returnable-element<range_reference_t<V>, N>
   >       class elements_view : public view_interface<elements_view<V, N>> {
   >         […]
   >       };
   >     }

2. Modify 24.7.18.3 [range.elements.iterator] as indicated:

   > >     namespace std::ranges {
   > >       template<input_range V, size_t N>
   > >         requires view<V> && has-tuple-element<range_value_t<V>, N> &&
   > >                  has-tuple-element<remove_reference_t<range_reference_t<V>>, N> &&
   > >                  returnable-element<range_reference_t<V>, N>
   > >       template<bool Const>
   > >       class elements_view<V, N>::iterator {                 // exposition only
   > >         using Base = maybe-const<Const, V>;                 // exposition only

   > >         iterator_t<Base> current_ = iterator_t<Base>();     // exposition only

   > >         static constexpr decltype(auto) get-element(const iterator_t<Base>& i);    // exposition only
   > >       public:
   > >         […]
   > >         constexpr decltype(auto) operator*() const
   > >         { return get<N>get-element(*current_); }

   > >         […]
   > >         constexpr decltype(auto) operator[](difference_type n) const
   > >         requires random_access_range<Base>
   > >         { return get<N>get-element(*(current_ + n)); }
   > >       };
   > >     }

   >     static constexpr decltype(auto) get-element(const iterator_t<Base>& i);    // exposition only

   > > -?- Effects: Equivalent to:

   > > >     if constexpr (is_reference_v<range_reference_t<Base>>) {
   > > >       return get<N>(*i);
   > > >     }
   > > >     else {
   > > >       using E = remove_cv_t<tuple_element_t<N, range_reference_t<Base>>>;
   > > >       return static_cast<E>(get<N>(*i));
   > > >     }

3. Modify 24.7.18.4 [range.elements.sentinel] as indicated:

   > >     namespace std::ranges {
   > >       template<input_range V, size_t N>
   > >         requires view<V> && has-tuple-element<range_value_t<V>, N> &&
   > >                  has-tuple-element<remove_reference_t<range_reference_t<V>>, N> &&
   > >                  returnable-element<range_reference_t<V>, N>
   > >       template<bool Const>
   > >       class elements_view<V, N>::sentinel {                 // exposition only
   > >         […]
   > >       };
   > >     }