24 Ranges library [ranges]

24.7 Range adaptors [range.adaptors]

24.7.12 Join view [range.join]

24.7.12.1 Overview [range.join.overview]

join_­view flattens a view of ranges into a view.
The name views​::​join denotes a range adaptor object ([range.adaptor.object]).
Given a subexpression E, the expression views​::​join(E) is expression-equivalent to join_­view<views​::​all_­t<decltype((E))>>{E}.
[Example 1: vector<string> ss{"hello", " ", "world", "!"}; for (char ch : ss | views::join) cout << ch; // prints: hello world! — end example]

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

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>> { 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 non-propagating-cache<remove_cv_t<InnerRng>> inner_; // exposition only, present only when !is_­reference_­v<InnerRng> public: join_view() requires default_­initializable<V> = default; constexpr explicit join_view(V base); constexpr V base() const& requires copy_­constructible<V> { return base_; } constexpr V base() && { return std::move(base_); } constexpr auto begin() { constexpr bool use_const = simple-view<V> && is_reference_v<range_reference_t<V>>; return iterator<use_const>{*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<views::all_t<R>>; }
constexpr explicit join_view(V base);
Effects: Initializes base_­ with std​::​move(base).

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

namespace std::ranges { template<input_­range V> requires view<V> && input_­range<range_reference_t<V>> template<bool Const> struct join_view<V>::iterator { private: using Parent = maybe-const<Const, join_view>; // exposition only using Base = maybe-const<Const, V>; // exposition only using OuterIter = iterator_t<Base>; // exposition only using InnerIter = iterator_t<range_reference_t<Base>>; // exposition only static constexpr bool ref-is-glvalue = // exposition only is_reference_v<range_reference_t<Base>>; OuterIter outer_ = OuterIter(); // exposition only InnerIter inner_ = InnerIter(); // exposition only Parent* parent_ = nullptr; // exposition only constexpr void satisfy(); // exposition only public: using iterator_concept = see below; using iterator_category = see below; // not always present using value_type = range_value_t<range_reference_t<Base>>; using difference_type = see below; iterator() requires default_­initializable<OuterIter> && default_­initializable<InnerIter> = default; constexpr iterator(Parent& parent, OuterIter outer); constexpr iterator(iterator<!Const> i) requires Const && convertible_­to<iterator_t<V>, OuterIter> && convertible_­to<iterator_t<InnerRng>, InnerIter>; constexpr decltype(auto) operator*() const { return *inner_; } constexpr InnerIter operator->() const requires has-arrow<InnerIter> && copyable<InnerIter>; 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>> && common_­range<range_reference_t<Base>>; constexpr iterator operator--(int) requires ref-is-glvalue && bidirectional_­range<Base> && bidirectional_­range<range_reference_t<Base>> && common_­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_))) requires indirectly_­swappable<InnerIter>; }; }
iterator​::​iterator_­concept is defined as follows:
  • If ref-is-glvalue is true and Base and range_­reference_­t<Base> each model bidirectional_­range, then iterator_­concept denotes bidirectional_­iterator_­tag.
  • Otherwise, if ref-is-glvalue is true and Base and range_­reference_­t<Base> each model forward_­range, then iterator_­concept denotes forward_­iterator_­tag.
  • Otherwise, iterator_­concept denotes input_­iterator_­tag.
The member typedef-name iterator_­category is defined if and only if ref-is-glvalue is true, Base models forward_­range, and range_­reference_­t<Base> models forward_­range.
In that case, 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 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](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_ = InnerIter();
constexpr iterator(Parent& parent, OuterIter outer);
Effects: Initializes outer_­ with std​::​move(outer) and parent_­ with addressof(parent); then calls satisfy().
constexpr iterator(iterator<!Const> i) requires Const && convertible_­to<iterator_t<V>, OuterIter> && convertible_­to<iterator_t<InnerRng>, InnerIter>;
Effects: Initializes outer_­ with std​::​move(i.outer_­), inner_­ with std​::​move(i.inner_­), and parent_­ with i.parent_­.
constexpr InnerIter operator->() const requires has-arrow<InnerIter> && copyable<InnerIter>;
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>> && common_­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>> && common_­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_­))) requires indirectly_­swappable<InnerIter>;
Effects: Equivalent to: return ranges​::​iter_­swap(x.inner_­, y.inner_­);

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

namespace std::ranges { template<input_­range V> requires view<V> && input_­range<range_reference_t<V>> template<bool Const> struct join_view<V>::sentinel { private: using Parent = maybe-const<Const, join_view>; // exposition only using Base = maybe-const<Const, 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>>; template<bool OtherConst> requires sentinel_­for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr bool operator==(const iterator<OtherConst>& 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_­).
template<bool OtherConst> requires sentinel_­for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>> friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
Effects: Equivalent to: return x.outer_­ == y.end_­;