Title
elements_view should not be allowed to return dangling references
Status
c++23
Section
[range.elements.iterator]
Submitter
Tim Song

Created on 2020-11-18.00:00:00 last changed 5 months ago

Messages

Date: 2021-02-26.17:31:29

Proposed resolution:

This wording is relative to N4878.

  1. Modify [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 [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 [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
        […]
      };
    }
    
Date: 2021-02-26.00:00:00

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

Date: 2021-02-15.00:00:00

[ 2021-02-08; Reflector poll ]

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

Date: 2021-01-31.00:00:00

[ 2021-01-31 Tim adds PR ]

Date: 2020-11-15.00:00:00

[ 2020-11-29; Reflector prioritization ]

Set priority to 2 during reflector discussions.

Date: 2020-11-21.16:11:44

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.)

History
Date User Action Args
2023-11-22 15:47:43adminsetstatus: wp -> c++23
2021-02-26 17:31:29adminsetmessages: + msg11706
2021-02-26 17:31:29adminsetstatus: ready -> wp
2021-02-08 13:01:11adminsetmessages: + msg11683
2021-02-08 13:01:11adminsetstatus: new -> ready
2021-01-31 16:45:21adminsetmessages: + msg11674
2021-01-31 16:45:21adminsetmessages: + msg11673
2020-11-29 14:06:26adminsetmessages: + msg11637
2020-11-18 00:00:00admincreate