23 Ranges library [ranges]

23.8 Range adaptors [range.adaptors]

23.8.7 Join view [range.join]

23.8.7.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
]

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

namespace std::ranges {
  template<InputRange V>
    requires View<V> && InputRange<iter_reference_t<iterator_t<V>>> &&
             (is_reference_v<iter_reference_t<iterator_t<V>>> ||
              View<iter_value_t<iterator_t<V>>>)
  class join_view : public view_interface<join_view<V>> {
  private:
    using InnerRng =                    // exposition only
      iter_reference_t<iterator_t<V>>;
    template<bool Const>
      struct iterator;                  // exposition only
    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<InputRange R>
      requires ViewableRange<R> && Constructible<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 InputRange<const V> &&
             is_reference_v<iter_reference_t<iterator_t<const V>>> {
      return iterator<true>{*this, ranges::begin(base_)};
    }

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

    constexpr auto end() const
    requires InputRange<const V> &&
             is_reference_v<iter_reference_t<iterator_t<const V>>> {
      if constexpr (ForwardRange<const V> &&
                    is_reference_v<iter_reference_t<iterator_t<const V>>> &&
                    ForwardRange<iter_reference_t<iterator_t<const V>>> &&
                    CommonRange<const V> &&
                    CommonRange<iter_reference_t<iterator_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<InputRange R> requires ViewableRange<R> && Constructible<V, all_view<R>> constexpr explicit join_view(R&& r);
Effects: Initializes base_­ with view::all(std::forward<R>(r)).

23.8.7.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<iter_reference_t<iterator_t<Base>>>;

    iterator_t<Base> outer_ = iterator_t<Base>();               // exposition only
    iterator_t<iter_reference_t<iterator_t<Base>>> inner_ =     // exposition only
      iterator_t<iter_reference_t<iterator_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        =
      iter_value_t<iterator_t<iter_reference_t<iterator_t<Base>>>>;
    using difference_type   = see below;

    iterator() = default;
    constexpr iterator(Parent& parent, iterator_t<V> outer);
    constexpr iterator(iterator<!Const> i)
      requires Const &&
               ConvertibleTo<iterator_t<V>, iterator_t<Base>> &&
               ConvertibleTo<iterator_t<InnerRng>,
                             iterator_t<iter_reference_t<iterator_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 && ForwardRange<Base> &&
               ForwardRange<iter_reference_t<iterator_t<Base>>>;

    constexpr iterator& operator--()
      requires ref_is_glvalue && BidirectionalRange<Base> &&
               BidirectionalRange<iter_reference_t<iterator_t<Base>>>;

    constexpr iterator operator--(int)
      requires ref_is_glvalue && BidirectionalRange<Base> &&
               BidirectionalRange<iter_reference_t<iterator_t<Base>>>;

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

    friend constexpr bool operator!=(const iterator& x, const iterator& y)
      requires ref_is_glvalue && EqualityComparable<iterator_t<Base>> &&
               EqualityComparable<iterator_t<iter_reference_t<iterator_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 iter_­reference_­t<iterator_­t<Base>> each model BidirectionalRange, then iterator_­concept denotes bidirectional_­iterator_­tag.
    • Otherwise, if Base and iter_­reference_­t<iterator_­t<Base>> each model ForwardRange, 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<iter_­reference_­t<iterator_­t<Base>>>>::iterator_­category.
  • If ref_­is_­glvalue is true,
    • If OUTERC and INNERC each model DerivedFrom<bidirectional_­iterator_­tag>, iterator_­category denotes bidirectional_­iterator_­tag.
    • Otherwise, if OUTERC and INNERC each model DerivedFrom<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<
  iter_difference_t<iterator_t<Base>>,
  iter_difference_t<iterator_t<iter_reference_t<iterator_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](iter_reference_t<iterator_t<Base>> x) -> decltype(auto) {
  if constexpr (ref_is_glvalue) // x is a reference
    return (x);                 // (x) is an lvalue
  else
    return (parent_->inner_ = view::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<iter_reference_t<iterator_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 && ConvertibleTo<iterator_t<V>, iterator_t<Base>> && ConvertibleTo<iterator_t<InnerRng>, iterator_t<iter_reference_t<iterator_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 && ForwardRange<Base> && ForwardRange<iter_reference_t<iterator_t<Base>>>;
Effects: Equivalent to:
auto tmp = *this;
++*this;
return tmp;
constexpr iterator& operator--() requires ref_is_glvalue && BidirectionalRange<Base> && BidirectionalRange<iter_reference_t<iterator_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 && BidirectionalRange<Base> && BidirectionalRange<iter_reference_t<iterator_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 && EqualityComparable<iterator_t<Base>> && EqualityComparable<iterator_t<iter_reference_t<iterator_t<Base>>>>;
Effects: Equivalent to: return x.outer_­ == y.outer_­ && x.inner_­ == y.inner_­;
friend constexpr bool operator!=(const iterator& x, const iterator& y) requires ref_is_glvalue && EqualityComparable<iterator_t<Base>> && EqualityComparable<iterator_t<iter_reference_t<iterator_t<Base>>>>;
Effects: Equivalent to: return !(x == y);
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_­);

23.8.7.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 && ConvertibleTo<sentinel_t<V>, sentinel_t<Base>>;

    friend constexpr bool operator==(const iterator<Const>& x, const sentinel& y);
    friend constexpr bool operator==(const sentinel& x, const iterator<Const>& y);
    friend constexpr bool operator!=(const iterator<Const>& x, const sentinel& y);
    friend constexpr bool operator!=(const sentinel& x, const iterator<Const>& y);
  };
}
constexpr explicit sentinel(Parent& parent);
Effects: Initializes end_­ with ranges::end(parent.base_­).
constexpr sentinel(sentinel<!Const> s) requires Const && ConvertibleTo<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_­;
friend constexpr bool operator==(const sentinel& x, const iterator<Const>& y);
Effects: Equivalent to: return y == x;
friend constexpr bool operator!=(const iterator<Const>& x, const sentinel& y);
Effects: Equivalent to: return !(x == y);
friend constexpr bool operator!=(const sentinel& x, const iterator<Const>& y);
Effects: Equivalent to: return !(y == x);

23.8.7.5 view​::​join [range.join.adaptor]

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