26 Ranges library [ranges]

26.7 Range adaptors [range.adaptors]

26.7.15 Join with view [range.join.with]

26.7.15.1 Overview [range.join.with.overview]

join_­with_­view takes a view and a delimiter, and flattens the view, inserting every element of the delimiter in between elements of the view.
The delimiter can be a single element or a view of elements.
The name views​::​join_­with denotes a range adaptor object ([range.adaptor.object]).
Given subexpressions E and F, the expression views​::​join_­with(E, F) is expression-equivalent to join_­with_­view(E, F).
[Example 1: vector<string> vs = {"the", "quick", "brown", "fox"}; for (char c : vs | views::join_with('-')) { cout << c; } // The above prints the-quick-brown-fox — end example]

26.7.15.2 Class template join_­with_­view [range.join.with.view]

namespace std::ranges { template<class R, class P> concept compatible-joinable-ranges = // exposition only common_­with<range_value_t<R>, range_value_t<P>> && common_­reference_­with<range_reference_t<R>, range_reference_t<P>> && common_­reference_­with<range_rvalue_reference_t<R>, range_rvalue_reference_t<P>>; template<class R> concept bidirectional-common = bidirectional_­range<R> && common_­range<R>; // exposition only template<input_­range V, forward_­range Pattern> requires view<V> && input_­range<range_reference_t<V>> && view<Pattern> && compatible-joinable-ranges<range_reference_t<V>, Pattern> class join_with_view : public view_interface<join_with_view<V, Pattern>> { using InnerRng = range_reference_t<V>; // exposition only V base_ = V(); // exposition only non-propagating-cache<remove_cv_t<InnerRng>> inner_; // exposition only, present only // when !is_­reference_­v<InnerRng> Pattern pattern_ = Pattern(); // exposition only // [range.join.with.iterator], class template join_­with_­view​::​iterator template<bool Const> struct iterator; // exposition only // [range.join.with.sentinel], class template join_­with_­view​::​sentinel template<bool Const> struct sentinel; // exposition only public: join_with_view() requires default_­initializable<V> && default_­initializable<Pattern> = default; constexpr join_with_view(V base, Pattern pattern); template<input_­range R> requires constructible_­from<V, views::all_t<R>> && constructible_­from<Pattern, single_view<range_value_t<InnerRng>>> constexpr join_with_view(R&& r, range_value_t<InnerRng> e); 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<InnerRng> && simple-view<Pattern>; return iterator<use_const>{*this, ranges::begin(base_)}; } constexpr auto begin() const requires input_­range<const V> && forward_­range<const Pattern> && 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> && simple-view<Pattern>>{*this, ranges::end(base_)}; else return sentinel<simple-view<V> && simple-view<Pattern>>{*this}; } constexpr auto end() const requires input_­range<const V> && forward_­range<const Pattern> && is_reference_v<range_reference_t<const V>> { using InnerConstRng = range_reference_t<const V>; if constexpr (forward_­range<const V> && forward_­range<InnerConstRng> && common_­range<const V> && common_­range<InnerConstRng>) return iterator<true>{*this, ranges::end(base_)}; else return sentinel<true>{*this}; } }; template<class R, class P> join_with_view(R&&, P&&) -> join_with_view<views::all_t<R>, views::all_t<P>>; template<input_­range R> join_with_view(R&&, range_value_t<range_reference_t<R>>) -> join_with_view<views::all_t<R>, single_view<range_value_t<range_reference_t<R>>>>; }
constexpr join_with_view(V base, Pattern pattern);
Effects: Initializes base_­ with std​::​move(base) and pattern_­ with std​::​move(pattern).
template<input_­range R> requires constructible_­from<V, views::all_t<R>> && constructible_­from<Pattern, single_view<range_value_t<InnerRng>>> constexpr join_with_view(R&& r, range_value_t<InnerRng> e);
Effects: Initializes base_­ with views​::​all(std​::​forward<R>(r)) and pattern_­ with views​::​single(std​::​move(e)).

26.7.15.3 Class template join_­with_­view​::​iterator [range.join.with.iterator]

namespace std::ranges { template<input_­range V, forward_­range Pattern> requires view<V> && input_­range<range_reference_t<V>> && view<Pattern> && compatible-joinable-ranges<range_reference_t<V>, Pattern> template<bool Const> class join_with_view<V, Pattern>::iterator { using Parent = maybe-const<Const, join_with_view>; // exposition only using Base = maybe-const<Const, V>; // exposition only using InnerBase = range_reference_t<Base>; // exposition only using PatternBase = maybe-const<Const, Pattern>; // exposition only using OuterIter = iterator_t<Base>; // exposition only using InnerIter = iterator_t<InnerBase>; // exposition only using PatternIter = iterator_t<PatternBase>; // exposition only static constexpr bool ref-is-glvalue = is_reference_v<InnerBase>; // exposition only Parent* parent_ = nullptr; // exposition only OuterIter outer_it_ = OuterIter(); // exposition only variant<PatternIter, InnerIter> inner_it_; // exposition only constexpr iterator(Parent& parent, iterator_t<Base> outer); // exposition only constexpr auto&& update-inner(const OuterIter&); // exposition only constexpr auto&& get-inner(const OuterIter&); // exposition only constexpr void satisfy(); // exposition only public: using iterator_concept = see below; using iterator_category = see below; // not always present using value_type = see below; using difference_type = see below; iterator() requires default_­initializable<OuterIter> = default; constexpr iterator(iterator<!Const> i) requires Const && convertible_­to<iterator_t<V>, OuterIter> && convertible_­to<iterator_t<InnerRng>, InnerIter> && convertible_­to<iterator_t<Pattern>, PatternIter>; constexpr decltype(auto) operator*() const; constexpr iterator& operator++(); constexpr void operator++(int); constexpr iterator operator++(int) requires ref-is-glvalue && forward_­iterator<OuterIter> && forward_­iterator<InnerIter>; constexpr iterator& operator--() requires ref-is-glvalue && bidirectional_­range<Base> && bidirectional-common<InnerBase> && bidirectional-common<PatternBase>; constexpr iterator operator--(int) requires ref-is-glvalue && bidirectional_­range<Base> && bidirectional-common<InnerBase> && bidirectional-common<PatternBase>; friend constexpr bool operator==(const iterator& x, const iterator& y) requires ref-is-glvalue && equality_­comparable<OuterIter> && equality_­comparable<InnerIter>; friend constexpr decltype(auto) iter_move(const iterator& x) { using rvalue_reference = common_reference_t< iter_rvalue_reference_t<InnerIter>, iter_rvalue_reference_t<PatternIter>>; return visit<rvalue_reference>(ranges::iter_move, x.inner_it_); } friend constexpr void iter_swap(const iterator& x, const iterator& y) requires indirectly_­swappable<InnerIter, PatternIter> { visit(ranges::iter_swap, x.inner_it_, y.inner_it_); } }; }
iterator​::​iterator_­concept is defined as follows:
  • If ref-is-glvalue is true, Base models bidirectional_­range, and InnerBase and PatternBase each model bidirectional-common, then iterator_­concept denotes bidirectional_­iterator_­tag.
  • Otherwise, if ref-is-glvalue is true and Base and InnerBase 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, and Base and InnerBase each model forward_­range.
In that case, iterator​::​iterator_­category is defined as follows:
  • Let OUTERC denote iterator_­traits<OuterIter>​::​iterator_­category, let INNERC denote iterator_­traits<InnerIter>​::​iterator_­category, and let PATTERNC denote iterator_­-
    traits<PatternIter>​::​iterator_­category
    .
  • If is_lvalue_reference_v<common_reference_t<iter_reference_t<InnerIter>, iter_reference_t<PatternIter>>> is false, iterator_­category denotes input_­iterator_­tag.
  • Otherwise, if OUTERC, INNERC, and PATTERNC each model derived_­from<bidirectional_­iterator_­category> and InnerBase and PatternBase each model common_­range, iterator_­category denotes bidirectional_­iterator_­tag.
  • Otherwise, if OUTERC, INNERC, and PATTERNC each model derived_­from<forward_­iterator_­tag>, iterator_­category denotes forward_­iterator_­tag.
  • Otherwise, iterator_­category denotes input_­iterator_­tag.
iterator​::​value_­type denotes the type: common_type_t<iter_value_t<InnerIter>, iter_value_t<PatternIter>>
iterator​::​difference_­type denotes the type: common_type_t< iter_difference_t<OuterIter>, iter_difference_t<InnerIter>, iter_difference_t<PatternIter>>
constexpr auto&& update-inner(const OuterIter& x);
Effects: Equivalent to: if constexpr (ref-is-glvalue) return *x; else return parent_->inner_.emplace-deref(x);
constexpr auto&& get-inner(const OuterIter& x);
Effects: Equivalent to: if constexpr (ref-is-glvalue) return *x; else return *parent_->inner_;
constexpr void satisfy();
Effects: Equivalent to: while (true) { if (inner_it_.index() == 0) { if (std::get<0>(inner_it_) != ranges::end(parent_->pattern_)) break; auto&& inner = update-inner(outer_it_); inner_it_.emplace<1>(ranges::begin(inner)); } else { auto&& inner = get-inner(outer_it_); if (std::get<1>(inner_it_) != ranges::end(inner)) break; if (++outer_it_ == ranges::end(parent_->base_)) { if constexpr (ref-is-glvalue) inner_it_.emplace<0>(); break; } inner_it_.emplace<0>(ranges::begin(parent_->pattern_)); } }
[Note 1:
join_­with_­view iterators use the satisfy function to skip over empty inner ranges.
— end note]
constexpr iterator(Parent& parent, iterator_t<Base> outer);
Effects: Initializes parent_­ with addressof(parent) and outer_­it_­ with std​::​move(outer).
Then, equivalent to: if (outer_it_ != ranges::end(parent_->base_)) { auto&& inner = update-inner(outer_it_); inner_it_.emplace<1>(ranges::begin(inner)); satisfy(); }
constexpr iterator(iterator<!Const> i) requires Const && convertible_­to<iterator_t<V>, OuterIter> && convertible_­to<iterator_t<InnerRng>, InnerIter> && convertible_­to<iterator_t<Pattern>, PatternIter>;
Effects: Initializes outer_­it_­ with std​::​move(i.outer_­it_­) and parent_­ with i.parent_­.
Then, equivalent to: if (i.inner_it_.index() == 0) inner_it_.emplace<0>(std::get<0>(std::move(i.inner_it_))); else inner_it_.emplace<1>(std::get<1>(std::move(i.inner_it_)));
constexpr decltype(auto) operator*() const;
Effects: Equivalent to: using reference = common_reference_t<iter_reference_t<InnerIter>, iter_reference_t<PatternIter>>; return visit([](auto& it) -> reference { return *it; }, inner_it_);
constexpr iterator& operator++();
Effects: Equivalent to: visit([](auto& it){ ++it; }, inner_it_); satisfy(); return *this;
constexpr void operator++(int);
Effects: Equivalent to ++*this.
constexpr iterator operator++(int) requires ref-is-glvalue && forward_­iterator<OuterIter> && forward_­iterator<InnerIter>;
Effects: Equivalent to: iterator tmp = *this; ++*this; return tmp;
constexpr iterator& operator--() requires ref-is-glvalue && bidirectional_­range<Base> && bidirectional-common<InnerBase> && bidirectional-common<PatternBase>;
Effects: Equivalent to: if (outer_it_ == ranges::end(parent_->base_)) { auto&& inner = *--outer_it_; inner_it_.emplace<1>(ranges::end(inner)); } while (true) { if (inner_it_.index() == 0) { auto& it = std::get<0>(inner_it_); if (it == ranges::begin(parent_->pattern_)) { auto&& inner = *--outer_it_; inner_it_.emplace<1>(ranges::end(inner)); } else { break; } } else { auto& it = std::get<1>(inner_it_); auto&& inner = *outer_it_; if (it == ranges::begin(inner)) { inner_it_.emplace<0>(ranges::end(parent_->pattern_)); } else { break; } } } visit([](auto& it){ --it; }, inner_it_); return *this;
constexpr iterator operator--(int) requires ref-is-glvalue && bidirectional_­range<Base> && bidirectional-common<InnerBase> && bidirectional-common<PatternBase>;
Effects: Equivalent to: iterator tmp = *this; --*this; return tmp;
friend constexpr bool operator==(const iterator& x, const iterator& y) requires ref-is-glvalue && equality_­comparable<OuterIter> && equality_­comparable<InnerIter>;
Effects: Equivalent to: return x.outer_it_ == y.outer_it_ && x.inner_it_ == y.inner_it_;

26.7.15.4 Class template join_­with_­view​::​sentinel [range.join.with.sentinel]

namespace std::ranges { template<input_­range V, forward_­range Pattern> requires view<V> && input_­range<range_reference_t<V>> && view<Pattern> && compatible-joinable-ranges<range_reference_t<V>, Pattern> template<bool Const> class join_with_view<V, Pattern>::sentinel { using Parent = maybe-const<Const, join_with_view>; // exposition only using Base = maybe-const<Const, V>; // exposition only sentinel_t<Base> end_ = sentinel_t<Base>(); // exposition only constexpr explicit sentinel(Parent& parent); // exposition only public: sentinel() = default; 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_­it_­ == y.end_­;