24 Ranges library [ranges]

24.7 Range adaptors [range.adaptors]

24.7.10 Join view [range.join]

24.7.10.1 Overview [range.join.overview]

join_­view flattens a view of ranges into a view.
Example
:
vector<string> ss{"hello", " ", "world", "!"};
join_view greeting{ss};
for (char ch : greeting)
  cout << ch;                                   // prints: hello world!
— end example
 ]

24.7.10.2 Class template join_­view [range.join.view]

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>>)
  class join_view : public view_interface<join_view<V>> {
  private:
    using InnerRng =                    // exposition only
      range_reference_t<V>;
    // [range.join.iterator], class template join_­view​::​iterator
    template<bool Const>
      struct iterator;                  // exposition only
    // [range.join.sentinel], class template join_­view​::​sentinel
    template<bool Const>
      struct sentinel;                  // exposition only

    V base_ = V();                      // exposition only
    all_view<InnerRng> inner_ =         // exposition only, present only when !is_­reference_­v<InnerRng>
      all_view<InnerRng>();
  public:
    join_view() = default;
    constexpr explicit join_view(V base);

    template<input_range R>
      requires viewable_range<R> && constructible_from<V, all_view<R>>
    constexpr explicit join_view(R&& r);

    constexpr auto begin() {
      return iterator<simple-view<V>>{*this, ranges::begin(base_)};
    }

    constexpr auto begin() const
    requires input_range<const V> &&
             is_reference_v<range_reference_t<const V>> {
      return iterator<true>{*this, ranges::begin(base_)};
    }

    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 input_range<const V> &&
             is_reference_v<range_reference_t<const V>> {
      if constexpr (forward_range<const V> &&
                    is_reference_v<range_reference_t<const V>> &&
                    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};
    }
  };

  template<class R>
    explicit join_view(R&&) -> join_view<all_view<R>>;
}
constexpr explicit join_view(V base);
Effects: Initializes base_­ with std​::​move(base).
template<input_range R> requires viewable_range<R> && constructible_from<V, all_view<R>> constexpr explicit join_view(R&& r);
Effects: Initializes base_­ with views​::​all(std​::​forward<R>(r)).

24.7.10.3 Class template join_­view​::​iterator [range.join.iterator]

namespace std::ranges {
template<class V>
  template<bool Const>
  struct join_view<V>::iterator {
  private:
    using Parent =                                              // exposition only
      conditional_t<Const, const join_view, join_view>;
    using Base   = conditional_t<Const, const V, V>;            // exposition only

    static constexpr bool ref_is_glvalue =                      // exposition only
      is_reference_v<range_reference_t<Base>>;

    iterator_t<Base> outer_ = iterator_t<Base>();               // exposition only
    iterator_t<range_reference_t<Base>> inner_ =                // exposition only
      iterator_t<range_reference_t<Base>>();
    Parent* parent_ = nullptr;                                  // exposition only

    constexpr void satisfy();                                   // exposition only
  public:
    using iterator_concept  = see below;
    using iterator_category = see below;
    using value_type        = range_value_t<range_reference_t<Base>>;
    using difference_type   = see below;

    iterator() = default;
    constexpr iterator(Parent& parent, iterator_t<V> outer);
    constexpr iterator(iterator<!Const> i)
      requires Const &&
               convertible_to<iterator_t<V>, iterator_t<Base>> &&
               convertible_to<iterator_t<InnerRng>,
                              iterator_t<range_reference_t<Base>>>;

    constexpr decltype(auto) operator*() const { return *inner_; }

    constexpr iterator_t<Base> operator->() const
      requires has-arrow<iterator_t<Base>>;

    constexpr iterator& operator++();
    constexpr void operator++(int);
    constexpr iterator operator++(int)
      requires ref_is_glvalue && forward_range<Base> &&
               forward_range<range_reference_t<Base>>;

    constexpr iterator& operator--()
      requires ref_is_glvalue && bidirectional_range<Base> &&
               bidirectional_range<range_reference_t<Base>>;

    constexpr iterator operator--(int)
      requires ref_is_glvalue && bidirectional_range<Base> &&
               bidirectional_range<range_reference_t<Base>>;

    friend constexpr bool operator==(const iterator& x, const iterator& y)
      requires ref_is_glvalue && equality_comparable<iterator_t<Base>> &&
               equality_comparable<iterator_t<range_reference_t<Base>>>;

    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_)));
  };
}
iterator​::​iterator_­concept is defined as follows:
  • If ref_­is_­glvalue is true,
    • If Base and range_­reference_­t<Base> each model bidirectional_­range, then iterator_­concept denotes bidirectional_­iterator_­tag.
    • Otherwise, if Base and range_­reference_­t<Base> each model forward_­range, then iterator_­concept denotes forward_­iterator_­tag.
  • Otherwise, iterator_­concept denotes input_­iterator_­tag.
iterator​::​iterator_­category is defined as follows:
  • Let OUTERC denote iterator_­traits<iterator_­t<Base>>​::​iterator_­category, and let INNERC denote iterator_­traits<iterator_­t<range_­reference_­t<Base>>>​::​iterator_­category.
  • If ref_­is_­glvalue is true,
    • If OUTERC and INNERC each model derived_­from<bidirectional_­iterator_­tag>, iterator_­category denotes bidirectional_­iterator_­tag.
    • Otherwise, if OUTERC and INNERC each model derived_­from<forward_­iterator_­tag>, iterator_­category denotes forward_­iterator_­tag.
  • Otherwise, iterator_­category denotes input_­iterator_­tag.
iterator​::​difference_­type denotes the type:
common_type_t<
  range_difference_t<Base>,
  range_difference_t<range_reference_t<Base>>>
join_­view iterators use the satisfy function to skip over empty inner ranges.
constexpr void satisfy(); // exposition only
Effects: Equivalent to:
auto update_inner = [this](range_reference_t<Base> x) -> decltype(auto) {
  if constexpr (ref_is_glvalue) // x is a reference
    return (x);                 // (x) is an lvalue
  else
    return (parent_->inner_ = views::all(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_ = iterator_t<range_reference_t<Base>>();
constexpr iterator(Parent& parent, iterator_t<V> outer)
Effects: Initializes outer_­ with outer and parent_­ with addressof(parent); then calls satisfy().
constexpr iterator(iterator<!Const> i) requires Const && convertible_to<iterator_t<V>, iterator_t<Base>> && convertible_to<iterator_t<InnerRng>, iterator_t<range_reference_t<Base>>>;
Effects: Initializes outer_­ with std​::​move(i.outer_­), inner_­ with std​::​move(i.inner_­), and parent_­ with i.parent_­.
constexpr iterator_t<Base> operator->() const requires has-arrow<iterator_t<Base>>;
Effects: Equivalent to return inner_­;
constexpr iterator& operator++();
Let inner-range be:
  • If ref_­is_­glvalue is true, *outer_­.
  • Otherwise, parent_­->inner_­.
Effects: Equivalent to:
auto&& inner_rng = inner-range;
if (++inner_ == ranges::end(inner_rng)) {
  ++outer_;
  satisfy();
}
return *this;
constexpr void operator++(int);
Effects: Equivalent to: ++*this.
constexpr iterator operator++(int) requires ref_is_glvalue && forward_range<Base> && forward_range<range_reference_t<Base>>;
Effects: Equivalent to:
auto tmp = *this;
++*this;
return tmp;
constexpr iterator& operator--() requires ref_is_glvalue && bidirectional_range<Base> && bidirectional_range<range_reference_t<Base>>;
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;
constexpr iterator operator--(int) requires ref_is_glvalue && bidirectional_range<Base> && bidirectional_range<range_reference_t<Base>>;
Effects: Equivalent to:
auto tmp = *this;
--*this;
return tmp;
friend constexpr bool operator==(const iterator& x, const iterator& y) requires ref_is_glvalue && equality_comparable<iterator_t<Base>> && equality_comparable<iterator_t<range_reference_t<Base>>>;
Effects: Equivalent to: return x.outer_­ == y.outer_­ && x.inner_­ == y.inner_­;
friend constexpr void iter_swap(const iterator& x, const iterator& y) noexcept(noexcept(ranges::iter_swap(x.inner_, y.inner_)));
Effects: Equivalent to: return ranges​::​iter_­swap(x.inner_­, y.inner_­);

24.7.10.4 Class template join_­view​::​sentinel [range.join.sentinel]

namespace std::ranges {
  template<class V>
  template<bool Const>
  struct join_view<V>::sentinel {
  private:
    using Parent =                                      // exposition only
      conditional_t<Const, const join_view, join_view>;
    using Base   = conditional_t<Const, const V, V>;    // exposition only
    sentinel_t<Base> end_ = sentinel_t<Base>();         // exposition only
  public:
    sentinel() = default;

    constexpr explicit sentinel(Parent& parent);
    constexpr sentinel(sentinel<!Const> s)
      requires Const && convertible_to<sentinel_t<V>, sentinel_t<Base>>;

    friend constexpr bool operator==(const iterator<Const>& x, const sentinel& y);
  };
}
constexpr explicit sentinel(Parent& parent);
Effects: Initializes end_­ with ranges​::​end(parent.base_­).
constexpr sentinel(sentinel<!Const> s) requires Const && convertible_to<sentinel_t<V>, sentinel_t<Base>>;
Effects: Initializes end_­ with std​::​move(s.end_­).
friend constexpr bool operator==(const iterator<Const>& x, const sentinel& y);
Effects: Equivalent to: return x.outer_­ == y.end_­;

24.7.10.5 views​::​join [range.join.adaptor]

The name views​::​join denotes a range adaptor object ([range.adaptor.object]).
For some subexpression E, the expression views​::​join(E) is expression-equivalent to join_­view{E}.