Issue 3502: elements_view should not be allowed to return dangling references (original) (raw)


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

3502. elements_view should not be allowed to return dangling references

Section: 25.7.23.3 [range.elements.iterator] Status: C++23 Submitter: Tim Song Opened: 2020-11-18 Last modified: 2023-11-22

Priority: 2

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

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

View all issues with C++23 status.

Discussion:

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

std::vector 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(*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 25.7.23.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 || move_­constructible<tuple_element_t<N, T>>;

template<input_range V, size_t N>
requires view && has-tuple-element<range_value_t, N> &&
has-tuple-element<remove_reference_t<range_reference_t>, N> &&
returnable-element<range_reference_t, N>
class elements_view : public view_interface<elements_view<V, N>> {
[…]
};
} 2. Modify 25.7.23.3 [range.elements.iterator] as indicated:

namespace std::ranges {
template<input_range V, size_t N>
requires view && has-tuple-element<range_value_t, N> &&
has-tuple-element<remove_reference_t<range_reference_t>, N> &&
returnable-element<range_reference_t, N>
template
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 getget-element(*current); }

[…]
constexpr decltype(auto) operator[](difference_type n) const
requires random_­access_­range<_Base_>
{ return getget-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(*i);
}
else {
using E = remove_cv_t<tuple_element_t<N, range_reference_t<_Base_>>>;
return static_cast(get(*i));
} 3. Modify 25.7.23.4 [range.elements.sentinel] as indicated:
namespace std::ranges {
template<input_range V, size_t N>
requires view && has-tuple-element<range_value_t, N> &&
has-tuple-element<remove_reference_t<range_reference_t>, N> &&
returnable-element<range_reference_t, N>
template
class elements_view<V, N>::sentinel { // exposition only
[…]
};
}