Title
Unnecessary constraint for join_view/join_with_view to be common_range
Status
new
Section
[range.join.view] [range.join.with.view]
Submitter
Hewill Kang

Created on 2026-06-16.00:00:00 last changed 1 week ago

Messages

Date: 2026-06-16.14:46:51

Proposed resolution:

This wording is relative to N5046.

  1. Modify [range.join.view], class template `join_view` synopsis, as indicated:

    namespace std::ranges {
      template<input_range V>
        requires view<V> && input_range<range_reference_t<V>>
      class join_view : public view_interface<join_view<V>> {
        […]
        constexpr auto end() {
          if constexpr (forward_range<V> &&
                        is_reference_v<InnerRng> && forward_range<InnerRng> &&
                        common_range<V> && common_range<InnerRng>)
            return iterator<simple-view<V>>{*this, ranges::end(base_)};
          else
            return sentinel<simple-view<V>>{*this};
        }
    
        constexpr auto end() const
          requires forward_range<const V> &&
                   is_reference_v<range_reference_t<const V>> &&
                   input_range<range_reference_t<const V>> {
          if constexpr (forward_range<range_reference_t<const V>> &&
                        common_range<const V> &&
                        common_range<range_reference_t<const V>>)
            return iterator<true>{*this, ranges::end(base_)};
          else
            return sentinel<true>{*this};
        }
      };
      […]
    }
    
  2. Modify [range.join.with.view] as indicated:

    namespace std::ranges {
      […]
      template<input_range V, forward_range Pattern>
        requires view<V> && input_range<range_reference_t<V>>
              && view<Pattern>
              && concatable<range_reference_t<V>, Pattern>
      class join_with_view : public view_interface<join_with_view<V, Pattern>> {
        […]
        constexpr auto end() {
          if constexpr (forward_range<V> &&
                        is_reference_v<InnerRng> && forward_range<InnerRng> &&
                        common_range<V> && common_range<InnerRng>)
            return iterator<simple-view<V> && simple-view<Pattern>>{*this, ranges::end(base_)};
          else
            return sentinel<simple-view<V> && simple-view<Pattern>>{*this};
        }
        constexpr auto end() const
          requires forward_range<const V> && forward_range<const Pattern> &&
                   is_reference_v<range_reference_t<const V>> &&
                   input_range<range_reference_t<const V>> &&
                   concatable<range_reference_t<const V>, const Pattern> {
          using InnerConstRng = range_reference_t<const V>;
          if constexpr (forward_range<InnerConstRng> &&
                        common_range<const V> && common_range<InnerConstRng>)
            return iterator<true>{*this, ranges::end(base_)};
          else
            return sentinel<true>{*this};
        }
      };
      […]
    }
    
Date: 2026-06-16.00:00:00

Currently, `join_view` will only be `common_range` if the following five constraints are met:

constexpr auto end() {
  if constexpr (forward_range<V> &&
                is_reference_v<InnerRng> && forward_range<InnerRng> &&
                common_range<V> && common_range<InnerRng>)
    return iterator<simple-view<V>>{*this, ranges::end(base_)};
  else
    return sentinel<simple-view<V>>{*this};
 }

The first three are reasonable, since those are necessary conditions for join_view::iterator being a `forward_iterator`: the iterator of both base and inner range should be `forward_iterator`, and the inner range should be a reference type to guarantees the multi-pass during the iteration, so that join_view::iterator can synthesize the following `operator==`:

   friend constexpr bool operator==(const iterator& x, const iterator& y)
     requires ref-is-glvalue && forward_range<Base> &&
              equality_comparable<iterator_t<range_reference_t<Base>>> {
     return x.outer_ == y.outer_ && x.inner_ == y.inner_;
   }

Where `outer_` is the iterator of base, and `inner_` is the iterator of inner range wrapped in `optional`. The constraints in the function signature match the corresponding first three constraints.

The fourth, requiring the base range to be `common_range`, is also reasonable because we construct join_view::iterator via ranges::end(base_), which is stored in its `outer_` member, which need to be the same type as ranges::begin(base_).

However, the last one, requiring the inner range to be a `common range`, is not very meaningful. Because we don't store the end iterator of the inner range in join_view::iterator, the inner_ member is just an `optional` with no value. When we call satisfy() in the constructor (to skip over empty inner ranges), such a constraint serves no purpose if we examine satisfy() closely:

constexpr void satisfy() {
  auto update_inner = [this](const iterator_t<Base>& x) -> auto&& {
  if constexpr (ref-is-glvalue)     // *x is a reference
    return *x;
  else
    return parent_->inner_.emplace-deref(x);
  };

  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();
  }

The call of `ranges::end(inner)` is only used to compare whether `inner_` has reached the end of the inner range, which has nothing to do with whether the inner range is a `common_range`. The only place where this condition is need is in the function body of `operator--`, which already imposes a constraint of common_range<range_reference_t<Base>>.

As a result, this unnecessary constraint prevents some reasonable cases where `join_view` can be a `common_range`, for example:

   vector<simd::vec<int>> v;
   auto j = v | views::join;

As above, `simd::vec` is `random_access_range`, but since it is not `common_range`, its role as the inner range makes the `join_view` no longer `common_range`. This means we cannot directly pass `j.begin()` and `j.end()` into the legacy algorithm which is unsatisfactory.

Note that `join_with_view` has the similar issue for being a `common_range`.

History
Date User Action Args
2026-06-16 14:46:51adminsetmessages: + msg16481
2026-06-16 00:00:00admincreate