Title
join_view fails to support ranges of ranges with non-default_initializable iterators
Status
c++23
Section
[range.join.iterator]
Submitter
Casey Carter

Created on 2021-06-16.00:00:00 last changed 12 months ago

Messages

Date: 2022-11-17.00:42:33

Proposed resolution:

This wording is relative to N4892.

  1. Modify [range.join.iterator] as indicated:

    namespace std::ranges {
      template<input_range V>
        requires view<V> && input_range<range_reference_t<V>> &&
                 (is_reference_v<range_reference_t<V>> ||
                  view<range_value_t<V>>)
      template<bool Const>
      struct join_view<V>::iterator {
        […]
        optional<InnerIter> inner_ = InnerIter();
        […]
        iterator() requires default_initializable<OuterIter> &&
                            default_initializable<InnerIter> = default;
        […]
        constexpr decltype(auto) operator*() const { return **inner_; }
        […]
        friend constexpr decltype(auto) iter_move(const iterator& i)
        noexcept(noexcept(ranges::iter_move(*i.inner_))) {
          return ranges::iter_move(*i.inner_);
        }
        
        friend constexpr void iter_swap(const iterator& x, const iterator& y)
          noexcept(noexcept(ranges::iter_swap(*x.inner_, *y.inner_)))
          requires indirectly_swappable<InnerIter>;
      };
    }
    
    […]
    constexpr void satisfy();       // exposition only
    

    -5- Effects: Equivalent to:

    auto update_inner = [this](const iterator_t<Base>& x) -> auto&& {
    […] 
    };
    for (; outer_ != ranges::end(parent_->base_); ++outer_) {
      auto&& inner = update_inner(*outer_);
      inner_ = ranges::begin(inner);
      if (*inner_ != ranges::end(inner))
        return;
    }
    if constexpr (ref-is-glvalue)
      inner_.reset() = InnerIter();
    
    […]
    constexpr InnerIter operator->() const
      requires has-arrow<InnerIter> && copyable<InnerIter>;
    

    -8- Effects: Equivalent to: return *inner_;

    constexpr iterator& operator++();
    

    -9- Let inner-range be:

    […]

    -10- Effects: Equivalent to:

    auto&& inner_rng = inner-range;
    if (++*inner_ == ranges::end(inner_rng)) {
      ++outer_;
      satisfy();
    }
    return *this;
    
    […]
    constexpr iterator& operator--()
      requires ref-is-glvalue && bidirectional_range<Base> &&
               bidirectional_range<range_reference_t<Base>> &&
               common_range<range_reference_t<Base>>;
    

    -13- Effects: Equivalent to:

    if (outer_ == ranges::end(parent_->base_))
      inner_ = ranges::end(*--outer_);
    while (*inner_ == ranges::begin(*outer_))
      *inner_ = ranges::end(*--outer_);
    --*inner_;
    return *this;
    
    […]
    friend constexpr void iter_swap(const iterator& x, const iterator& y)
      noexcept(noexcept(ranges::iter_swap(*x.inner_, *y.inner_)))
      requires indirectly_swappable<InnerIter>;
    

    -16- Effects: Equivalent to: return ranges::iter_swap(*x.inner_, *y.inner_);

Date: 2022-11-12.00:00:00

[ 2022-11-12 Approved at November 2022 meeting in Kona. Status changed: Immediate → WP. ]

Date: 2022-11-11.02:39:56

[ Kona 2022-11-08; Accepted at joint LWG/SG9 session. Move to Immediate ]

Date: 2021-08-15.00:00:00

[ 2021-08-23; Louis Dionne comments and provides improved wording ]

I believe the currently proposed resolution is missing the removal of the default_initializable<InnerIter> constraint on join_view::iterator's default constructor in [range.join.iterator]. Indeed, after the currently-proposed resolution, join_view::iterator reads like:

template<input_range V>
  requires […]
struct join_view<V>::iterator {
private:
  optional<InnerIter> inner_; // exposition only
  […]
public:
  iterator() requires default_initializable<OuterIter> &&
                      default_initializable<InnerIter> = default;
    […]
};

I believe we should drop the default_initializable<InnerIter> constraint from the default constructor (that seems like an oversight unless I missed something):

template<input_range V>
  requires […]
struct join_view<V>::iterator {
private:
  optional<InnerIter> inner_; // exposition only
  […]
public:
  iterator() requires default_initializable<OuterIter> = default;
  […]
};
Date: 2021-06-15.00:00:00

[ 2021-06-23; Reflector poll ]

Set priority to 3 after reflector poll.

Previous resolution [SUPERSEDED]:

Wording relative to the post 2021-06 virtual plenary working draft. This PR is currently being implemented in MSVC.

  1. Modify [range.join.iterator] as indicated:

    namespace std::ranges {
      template<input_range V>
        requires view<V> && input_range<range_reference_t<V>> &&
                 (is_reference_v<range_reference_t<V>> ||
                  view<range_value_t<V>>)
      template<bool Const>
      struct join_view<V>::iterator {
        […]
        optional<InnerIter> inner_ = InnerIter();
        […]
        constexpr decltype(auto) operator*() const { return **inner_; }
        […]
        friend constexpr decltype(auto) iter_move(const iterator& i)
        noexcept(noexcept(ranges::iter_move(*i.inner_))) {
          return ranges::iter_move(*i.inner_);
        }
        
        friend constexpr void iter_swap(const iterator& x, const iterator& y)
          noexcept(noexcept(ranges::iter_swap(*x.inner_, *y.inner_)))
          requires indirectly_swappable<InnerIter>;
      };
    }
    
    […]
    constexpr void satisfy();       // exposition only
    

    -5- Effects: Equivalent to:

    auto update_inner = [this](const iterator_t<Base>& x) -> auto&& {
    […] 
    };
    for (; outer_ != ranges::end(parent_->base_); ++outer_) {
      auto&& inner = update_inner(*outer_);
      inner_ = ranges::begin(inner);
      if (*inner_ != ranges::end(inner))
        return;
    }
    if constexpr (ref-is-glvalue)
      inner_.reset() = InnerIter();
    
    […]
    constexpr InnerIter operator->() const
      requires has-arrow<InnerIter> && copyable<InnerIter>;
    

    -8- Effects: Equivalent to: return *inner_;

    constexpr iterator& operator++();
    

    -9- Let inner-range be:

    […]

    -10- Effects: Equivalent to:

    auto&& inner_rng = inner-range;
    if (++*inner_ == ranges::end(inner_rng)) {
      ++outer_;
      satisfy();
    }
    return *this;
    
    […]
    constexpr iterator& operator--()
      requires ref-is-glvalue && bidirectional_range<Base> &&
               bidirectional_range<range_reference_t<Base>> &&
               common_range<range_reference_t<Base>>;
    

    -13- Effects: Equivalent to:

    if (outer_ == ranges::end(parent_->base_))
      inner_ = ranges::end(*--outer_);
    while (*inner_ == ranges::begin(*outer_))
      *inner_ = ranges::end(*--outer_);
    --*inner_;
    return *this;
    
    […]
    friend constexpr void iter_swap(const iterator& x, const iterator& y)
      noexcept(noexcept(ranges::iter_swap(*x.inner_, *y.inner_)))
      requires indirectly_swappable<InnerIter>;
    

    -16- Effects: Equivalent to: return ranges::iter_swap(*x.inner_, *y.inner_);

Date: 2021-06-16.00:00:00

join_view::iterator has exposition-only members outer_ — which holds an iterator into the adapted range — and inner_ — which holds an iterator into the range denoted by outer_. After application of P2325R3 "Views should not be required to be default constructible" to the working draft, single-pass iterators can be non-default_initializable. P2325R3 constrains join_view::iterator's default constructor to require that the types of both outer_ and inner_ are default_initializable, indicating an intent to support such iterator types. However, the effect of the non-default constructor specified in [range.join.iterator] paragraph 6 is to default-initialize inner_, which is ill-formed if its type is not default_initializable.

History
Date User Action Args
2023-11-22 15:47:43adminsetstatus: wp -> c++23
2022-11-17 00:42:33adminsetmessages: + msg13088
2022-11-17 00:42:33adminsetstatus: immediate -> wp
2022-11-11 02:39:56adminsetstatus: ready -> immediate
2022-11-10 23:33:20adminsetmessages: + msg13000
2022-11-10 23:33:20adminsetstatus: new -> ready
2021-08-27 18:24:12adminsetmessages: + msg12019
2021-06-23 14:18:24adminsetmessages: + msg11968
2021-06-19 14:33:00adminsetmessages: + msg11938
2021-06-16 00:00:00admincreate