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

3791. join_view::iterator::operator-- may be ill-formed

Section: 25.7.14.3 [range.join.iterator] Status: Resolved Submitter: Hewill Kang Opened: 2022-09-30 Last modified: 2023-03-23

Priority: 3

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

View all issues with Resolved status.

Discussion:

Currently, join_view::iterator::operator-- has the following Effects:

if (outer_ == ranges::end(parent_->base_))
  inner_ = ranges::end(*--outer_);
while (inner_ == ranges::begin(*outer_))
  inner_ = ranges::end(*--outer_);
--inner_;
return *this;

which uses ranges::end(*--outer_) to get the sentinel of the inner range. However, *--outer_ may return an rvalue reference to a non-borrowed range, in which case calling ranges::end will be ill-formed, for example:

#include <ranges>
#include <vector>

int main() {
  std::vector<std::vector<int>> v;
  auto r = v | std::views::transform([](auto& x) -> auto&& { return std::move(x); })
             | std::views::join;
  auto e = --r.end(); // hard error
}

The proposed resolution uses a temporary reference to bind *--outer_, so that ranges::end is always invoked on an lvalue range, which is consistent with the behavior of join_with_view::iterator::operator--.

[2022-10-12; Reflector poll]

Set priority to 3 after reflector poll.

"Could introduce an as_lvalue lambda (like auto as_lvalue = []<class T>(T&& x) -> T& { return (T&)x; };) and use it throughout."

Previous resolution [SUPERSEDED]:

This wording is relative to N4917.

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

    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_)) {
      auto&& inner = *--outer_;
      inner_ = ranges::end(inner*--outer_);
    }
    while (trueinner_ == ranges::begin(*outer_)) {
      if (auto&& tmp = *outer_; inner_ == ranges::begin(tmp)) {
        auto&& inner = *--outer_;
        inner_ = ranges::end(inner*--outer_);
      } else {
        break;
      }
    }
    --inner_;
    return *this;
    

[2023-03-22 Resolved by the adoption of P2770R0 in Issaquah. Status changed: New → Resolved.]

Proposed resolution: