views::take_last and views::drop_last| Document #: | P4294R0 |
|---|---|
| Date: | 2026-07-06 |
| Project: | Programming Language C++ |
| Audience: | SG9, LEWG, LWG |
| Reply-to: | Hewill Kang <hewillk@gmail.com> |
This paper proposes two new range adaptors, views::take_last and
views::drop_last, that respectively produce the last N
elements of a range and all-but-the-last N elements of a range.
They mirror the shape of the existing views::take /
views::drop adaptors and fill an obvious gap in the standard
range adaptor set.
C++20 introduced views::take and views::drop to
select or discard a prefix of a range. There is currently no direct
adaptor that operates on the suffix of a range. Users have to fall
back to composition:
r | views::reverse | views::take(n) | views::reverse // last n elements
r | views::reverse | views::drop(n) | views::reverse // drop last n
which:
bidirectional_range (excludes forward-only sized ranges).
For an input that is a sized_range but not
bidirectional_range (for example a user-defined sized forward
range, or a sized forward range synthesized by an adaptor pipeline), the
reverse-based workaround simply does not compile.
Both operations are well-established in range-v3
(views::take_last, views::drop_last), in Python
(r[-n:], r[:-n]), in Kotlin
(takeLast, dropLast), etc.
| Library / Language | take_last |
drop_last |
|---|---|---|
| range-v3 | views::take_last |
views::drop_last |
| Python | seq[-n:] |
seq[:-n] |
| Kotlin / Scala | takeLast(n) |
dropLast(n) |
| D (Phobos) | takeLast(n) |
dropBackN(n) |
Both adaptors require:
template<view V>
requires forward_range<V> || sized_range<V>
Rationale:
V is sized_range we know the size up front and can
jump to the correct offset (take_last) or compute the correct end
(drop_last) directly.V is only forward_range we can still make it work with a
two-iterator "probe" technique that traverses the range once, provided we are
allowed to re-visit elements (i.e. forward_iterator).take_last: what begin() and end() return
take_last_view never introduces a new iterator type; it always
yields the underlying iterator_t<V> (or
iterator_t<const V>). The interesting question is how
begin() is computed. Five cases are handled, listed from cheapest to
most expensive:
| # | Category of V |
begin() |
end() |
Complexity of begin() |
|---|---|---|---|---|
| 1 | sized_range && random_access_range |
begin(b) + (size − min(size,count)) |
end(b) |
O(1) |
| 2 | sized_range && bidirectional_range && common_range |
pick the shorter of the two walks | end(b) |
O(min(count, size−count)) |
| 3 | sized_range (not in 1 or 2) |
next(begin(b), size − count) |
end(b) |
O(size − count) |
| 4 | bidirectional_range && common_range (not sized) |
prev(end(b), count, begin(b)) |
end(b) |
O(count) |
| 5 | Otherwise (forward, not sized, not common-bidi) | two-iterator probe | end(b) |
O(size) |
Case 2 is a nice runtime optimisation: with
n = min(size, count) and from_begin = size − n, we
compare n and from_begin and walk from whichever end
is closer. The cost becomes O(min(n, from_begin)).
This optimization is intentionally not reflected as a separate normative case in the
wording. The wording specifies the iterator that begin() returns,
not the particular path used to compute it. For a sized bidirectional common
range, an implementation can compute the required iterator either by advancing
from begin(base_) or by retreating from end(base_),
whichever is cheaper, while still returning the iterator denoted by
ranges::next(ranges::begin(base_), size - n).
Case 5 uses the classical "runner" trick:
auto it = ranges::begin(base_);
auto probe = ranges::next(it, count_, ranges::end(base_));
while (probe != ranges::end(base_)) {
++it;
++probe;
}
return it;
drop_last: what begin() and end() return
Unlike take_last, drop_last genuinely needs a new
iterator type in the worst case, because the "logical end" of the range is
count steps before the physical end and we may not be able to represent
that with the underlying sentinel. Four cases:
| # | Category of V |
begin() |
end() |
Iterator returned |
|---|---|---|---|---|
| 1 | sized_range && random_access_range |
begin(b) |
begin(b) + (size − n) |
iterator_t<V> |
| 2 | sized_range (not random-access) |
counted_iterator(begin(b), size − n) |
default_sentinel |
counted_iterator<iterator_t<V>> |
| 3 | bidirectional_range && common_range (not sized) |
begin(b) |
prev(end(b), count, begin(b)) |
iterator_t<V> |
| 4 | Otherwise (forward, not sized, not common-bidi) | iterator(begin(b), next(begin(b), count, end(b))) |
end(b) (a sentinel_t<V>) |
iterator |
Cases 1 and 3 reuse iterator_t<V> verbatim.
Case 2 uses counted_iterator so that end() can be
default_sentinel, avoiding an O(size) walk in end().
Only case 4 requires a bespoke iterator; see the next section.
drop_last (case 4)The iterator carries two copies of iterator_t<V>:
iterator_t<V> current_; // logical position
iterator_t<V> probe_; // current_ advanced by count_ (clamped to end)
Both advance together on every operation. When probe_ reaches the
underlying sentinel, iteration is complete:
friend constexpr bool
operator==(const iterator& x, const sentinel_t<V>& y) { return x.probe_ == y; }
The bespoke iterator intentionally does not provide subtraction with
sentinel_t<V>. This iterator is only used in the fallback case
where V is forward but not sized, and where the logical end cannot
be represented directly by an iterator into the underlying range. If
sentinel_t<V> and iterator_t<V> were sized sentinels
for each other, then the underlying forward range would already be a
sized_range, and the adaptor would use the sized-range case instead
of this iterator.
When the underlying iterator supports stronger iterator concepts, the wrapper
preserves those operations by applying the same movement to both
current_ and probe_. This maintains the invariant that
probe_ denotes the position corresponding to
current_ advanced by count_, and allows the fallback
iterator to preserve bidirectional, random-access, and contiguous capabilities
of the underlying iterator where available.
begin()
Per [range.range], ranges::begin(r) must be
amortized O(1). This matters for the cases where the naive
begin() is not O(1):
take_last cases 3, 4, 5.drop_last case 4.drop_last case 3 has an expensive end()
(not begin()), which we cache symmetrically so that
repeated iteration does not repeatedly walk backwards.The specification uses the standard "cache the value on the first call and return it unchanged on subsequent calls" formulation.
end() does not reuse begin()'s work
An attractive optimisation for take_last case 5 is to
observe that at the end of the two-iterator loop, probe_
equals ranges::end(base_) already, and could conceivably
be returned by end() to avoid recomputing it. We deliberately
reject this for two reasons:
end() may be called before begin().
Reusing the cache would then force end() to run the O(size)
walk, which is exactly the cost we are trying to avoid, and would
silently pessimise algorithms that only ever query end()
(ranges::empty, comparisons against a stored sentinel, etc.).ranges::end(base_) is itself O(1); nothing is gained by
caching it.
The same reasoning applies to drop_last case 4: even though
begin()'s computation produces the correct sentinel-equivalent
iterator, end() still returns the underlying
sentinel_t<V> directly, and the comparison uses
probe_.
countFollowing take_view / drop_view, both constructors add:
Preconditions:count >= 0istrue.
size()take_last: min(size(base_), count_) — always ≤ count_.drop_last: size(base_) > count_ ? size(base_) - count_ : 0 — never negative.
Both are provided on the const overload
iff sized_range<const V>.
reserve_hint()
take_last_view always provides reserve_hint(),
because its cardinality is bounded above by count_.
drop_last_view provides reserve_hint() only when the base range is approximately-sized.
This lets
ranges::to<C> allocate the right capacity even when the
exact size cannot be obtained in O(1). The rules generalise size():
take_last_view::reserve_hint() returns
min(ranges::reserve_hint(base_), count_) when the base range is
approximately-sized, and count_ otherwise.
drop_last_view::reserve_hint() returns max(ranges::reserve_hint(base_) - count_, 0).
When V also models sized_range (which refines
approximately_sized_range), these coincide with size()
and yield the exact count. When V is only approximately-sized (e.g.
an istream_view with a hint attached), the returned value is a
best-effort estimate, exactly as intended by
ranges::reserve_hint.
views::drop / views::takeFor a wide range of well-known random-access-sized view types, both adaptors have simple closed-form reductions:
views::take_last(E, F) ≡ E | views::drop(distance(E) - min(distance(E), F))views::drop_last(E, F) ≡ E | views::take(max(distance(E) - F, 0))
For types where views::drop / views::take are
themselves specialised to return the same type (i.e.
empty_view, span, basic_string_view,
optional, repeat_view, iota_view,
subrange), this reduction produces the same-typed result
without introducing a wrapper take_last_view /
drop_last_view.
Both views are borrowed iff V is:
template<class T>
constexpr bool enable_borrowed_range<take_last_view<T>> = enable_borrowed_range<T>;
template<class T>
constexpr bool enable_borrowed_range<drop_last_view<T>> = enable_borrowed_range<T>;
These adaptors are suffix operations and therefore are meaningful for ranges
with a reachable end. The constraints cannot, in general, express finiteness.
For a non-sized forward range, take_last_view::begin() must reach
the physical end of the base range, and therefore might not terminate for an
unbounded range. Similarly, drop_last_view on an unbounded range
has no mathematical “last N elements” to remove.
This is also why the closed-form reductions for well-known view types are
restricted to cases where the relevant distance is known. For example,
repeat_view can be either bounded or unbounded; only the bounded
form models sized_range. Therefore the reductions involving
repeat_view are intentionally limited to the sized case.
This wording is relative to N5046.
namespace std::ranges {
[…]
namespace views { inline constexpr unspecified drop_while = unspecified; }
// [range.take.last], take last view
template<view V>
requires forward_range<V> || sized_range<V>
class take_last_view;
template<class T>
constexpr bool enable_borrowed_range<take_last_view<T>> =
enable_borrowed_range<T>;
namespace views { inline constexpr unspecified take_last = unspecified; }
// [range.drop.last], drop last view
template<view V>
requires forward_range<V> || sized_range<V>
class drop_last_view;
template<class T>
constexpr bool enable_borrowed_range<drop_last_view<T>> =
enable_borrowed_range<T>;
namespace views { inline constexpr unspecified drop_last = unspecified; }
[…]
}
take_last_viewproduces a view of the last N elements of another view.The name
views::take_lastdenotes a range adaptor object ([range.adaptor.object]). LetEandFbe expressions, letTberemove_cvref_t<decltype((E))>, and letDberange_difference_t<decltype((E))>. Ifdecltype((F))does not modelconvertible_to<D>,views::take_last(E, F)is ill-formed. Otherwise, the expressionviews::take_last(E, F)is expression-equivalent to:
- Preconditions:
static_cast<D>(F) >= 0istrue.Drafting note: The precondition is attached to the adaptor object so that it also applies to the expression-equivalent reduction cases. Otherwise, a negative count could avoid the constructor precondition by taking one of the reductions to
views::droporviews::take.- If
Tis a specialization ofempty_view,spanorbasic_string_view, orTmodelsviewand is a specialization ofoptional, orTmodelsrandom_access_rangeandsized_rangeand is a specialization ofrepeat_view,iota_view, orsubrange, thenE | views::drop(ranges::distance(E) - std::min(ranges::distance(E), static_cast<D>(F))), except thatEis evaluated only once.- Otherwise,
take_last_view(E, F).[Example:
for (int i : views::iota(0, 10) | views::take_last(3)) print("{} ", i); // prints 7 8 9— end example]
take_last_view [range.take.last.view]namespace std::ranges {
template<view V>
requires forward_range<V> || sized_range<V>
class take_last_view : public view_interface<take_last_view<V>> {
V base_ = V(); // exposition only
range_difference_t<V> count_ = 0; // exposition only
public:
take_last_view() requires default_initializable<V> = default;
constexpr explicit take_last_view(V base, range_difference_t<V> count);
constexpr V base() const & requires copy_constructible<V> { return base_; }
constexpr V base() && { return std::move(base_); }
constexpr auto begin()
requires (!(simple-view<V> &&
random_access_range<const V> && sized_range<const V>));
constexpr auto begin() const
requires random_access_range<const V> && sized_range<const V> {
const auto sz = ranges::distance(base_);
const auto n = std::min(sz, count_);
return ranges::next(ranges::begin(base_), sz - n);
}
constexpr auto end() { return ranges::end(base_); }
constexpr auto end() const requires range<const V> { return ranges::end(base_); }
constexpr auto size() requires sized_range<V> {
const auto sz = ranges::size(base_);
return std::min(sz, static_cast<decltype(sz)>(count_));
}
constexpr auto size() const requires sized_range<const V> {
const auto sz = ranges::size(base_);
return std::min(sz, static_cast<decltype(sz)>(count_));
}
constexpr auto reserve_hint() {
if constexpr (approximately_sized_range<V>) {
auto sz = static_cast<range_difference_t<V>>(ranges::reserve_hint(base_));
return to-unsigned-like(std::min(sz, count_));
}
return to-unsigned-like(count_);
}
constexpr auto reserve_hint() const {
if constexpr (approximately_sized_range<const V>) {
auto sz = static_cast<range_difference_t<const V>>(ranges::reserve_hint(base_));
return to-unsigned-like(std::min(sz, count_));
}
return to-unsigned-like(count_);
}
};
template<class R>
take_last_view(R&&, range_difference_t<R>) -> take_last_view<views::all_t<R>>;
}
constexpr explicit take_last_view(V base, range_difference_t<V> count);
Preconditions:
count >= 0istrue.Effects: Initializes
base_withstd::move(base)andcount_withcount.
constexpr auto begin()
requires (!(simple-view<V> &&
random_access_range<const V> && sized_range<const V>));
Returns:
- If
Vmodelssized_range, letszberanges::distance(base_)andnbestd::min(sz, count_),ranges::next(ranges::begin(base_), sz - n).- Otherwise, if
Vmodelsbidirectional_rangeandcommon_range,ranges::prev(ranges::end(base_), count_, ranges::begin(base_)).- Otherwise, the first iterator
ireachable fromranges::begin(base_)such thatbool(ranges::next(i, count_, ranges::end(base_)) == ranges::end(base_))istrue.Remarks: In order to provide the amortized constant-time complexity required by the
rangeconcept whentake_last_viewmodelsforward_range, this function caches the result within thetake_last_viewfor use on subsequent calls.
drop_last_viewproduces a view of a range with its last N elements removed.The name
views::drop_lastdenotes a range adaptor object ([range.adaptor.object]). LetEandFbe expressions, letTberemove_cvref_t<decltype((E))>, and letDberange_difference_t<decltype((E))>. Ifdecltype((F))does not modelconvertible_to<D>,views::drop_last(E, F)is ill-formed. Otherwise, the expressionviews::drop_last(E, F)is expression-equivalent to:
- Preconditions:
static_cast<D>(F) >= 0istrue.- If
Tis a specialization ofempty_view,spanorbasic_string_view, orTmodelsviewand is a specialization ofoptional, orTmodelsrandom_access_rangeandsized_rangeand is a specialization ofrepeat_view,iota_view, orsubrange, thenE | views::take(std::max(ranges::distance(E) - static_cast<D>(F), D(0))), except thatEis evaluated only once.- Otherwise,
drop_last_view(E, F).[Example:
for (int i : views::iota(0, 10) | views::drop_last(3)) print("{} ", i); // prints 0 1 2 3 4 5 6— end example]
drop_last_view [range.drop.last.view]namespace std::ranges {
template<view V>
requires forward_range<V> || sized_range<V>
class drop_last_view : public view_interface<drop_last_view<V>> {
V base_ = V(); // exposition only
range_difference_t<V> count_ = 0; // exposition only
// 24.7.?.3, class drop_last_view::iterator
class iterator; // exposition only
public:
drop_last_view() requires default_initializable<V> = default;
constexpr explicit drop_last_view(V base, range_difference_t<V> count);
constexpr V base() const & requires copy_constructible<V> { return base_; }
constexpr V base() && { return std::move(base_); }
constexpr auto begin()
requires (!(simple-view<V> && sized_range<const V>));
constexpr auto begin() const requires sized_range<const V> {
if constexpr (random_access_range<const V>)
return ranges::begin(base_);
else {
const auto sz = ranges::distance(base_);
const auto n = std::min(sz, count_);
return counted_iterator(ranges::begin(base_), sz - n);
}
}
constexpr auto end()
requires (!(simple-view<V> && sized_range<const V>));
constexpr auto end() const requires sized_range<const V> {
if constexpr (random_access_range<const V>) {
const auto sz = ranges::distance(base_);
const auto n = std::min(sz, count_);
return ranges::next(ranges::begin(base_), sz - n);
} else
return default_sentinel;
}
constexpr auto size() requires sized_range<V> {
const auto s = ranges::size(base_);
const auto c = static_cast<decltype(s)>(count_);
return s < c ? 0 : s - c;
}
constexpr auto size() const requires sized_range<const V> {
const auto s = ranges::size(base_);
const auto c = static_cast<decltype(s)>(count_);
return s < c ? 0 : s - c;
}
constexpr auto reserve_hint() requires approximately_sized_range<V> {
const auto s = static_cast<range_difference_t<V>>(ranges::reserve_hint(base_));
return to-unsigned-like(s < count_ ? 0 : s - count_);
}
constexpr auto reserve_hint() const requires approximately_sized_range<const V> {
const auto s = static_cast<range_difference_t<const V>>(ranges::reserve_hint(base_));
return to-unsigned-like(s < count_ ? 0 : s - count_);
}
};
template<class R>
drop_last_view(R&&, range_difference_t<R>) -> drop_last_view<views::all_t<R>>;
}
constexpr explicit drop_last_view(V base, range_difference_t<V> count);
Preconditions:
count >= 0istrue.Effects: Initializes
base_withstd::move(base)andcount_withcount.
constexpr auto begin()
requires (!(simple-view<V> && sized_range<const V>));
Returns:
- If
Vmodelssized_range, letsz = ranges::distance(base_)andn = std::min(sz, count_).
- If
Vmodelsrandom_access_range,ranges::begin(base_).- Otherwise,
counted_iterator(ranges::begin(base_), sz - n).- Otherwise, if
Vmodelsbidirectional_rangeandcommon_range,ranges::begin(base_).- Otherwise,
iterator(ranges::begin(base_), ranges::next(ranges::begin(base_), count_, ranges::end(base_))).Remarks: In order to provide the amortized constant-time complexity required by the
rangeconcept whendrop_last_viewmodelsforward_range, this function caches the result within thedrop_last_viewfor use on subsequent calls.
constexpr auto end()
requires (!(simple-view<V> && sized_range<const V>));
Returns:
- If
Vmodelssized_range, letsz = ranges::distance(base_)andn = std::min(sz, count_).
- If
Vmodelsrandom_access_range,ranges::next(ranges::begin(base_), sz - n).- Otherwise,
default_sentinel.- Otherwise, if
Vmodelsbidirectional_rangeandcommon_range,ranges::prev(ranges::end(base_), count_, ranges::begin(base_)).- Otherwise,
ranges::end(base_).Remarks: In order to provide the amortized constant-time complexity required by the
rangeconcept whendrop_last_viewmodelsforward_range, this function caches the result within thedrop_last_viewfor use on subsequent calls.
drop_last_view::iterator [range.drop.last.iterator]namespace std::ranges {
template<view V>
requires forward_range<V> || sized_range<V>
class drop_last_view<V>::iterator {
iterator_t<V> current_ = iterator_t<V>(); // exposition only
iterator_t<V> probe_ = iterator_t<V>(); // exposition only
constexpr iterator(iterator_t<V> current, iterator_t<V> probe); // exposition only
public:
using iterator_concept = see below;
using iterator_category = see below;
using value_type = range_value_t<V>;
using difference_type = range_difference_t<V>;
iterator() = default;
constexpr iterator_t<V> base() const;
constexpr range_reference_t<V> operator*() const;
constexpr auto operator->() const noexcept requires contiguous_range<V>;
constexpr iterator& operator++();
constexpr iterator operator++(int) = default;
constexpr iterator& operator--() requires bidirectional_range<V>;
constexpr iterator operator--(int) requires bidirectional_range<V> = default;
constexpr iterator& operator+=(difference_type n) requires random_access_range<V>;
constexpr iterator& operator-=(difference_type n) requires random_access_range<V>;
constexpr range_reference_t<V> operator[](difference_type n) const
requires random_access_range<V>;
friend constexpr bool operator==(const iterator& x, const iterator& y);
friend constexpr bool operator==(const iterator& x, const sentinel_t<V>& y);
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires random_access_range<V>;
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires random_access_range<V>;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires random_access_range<V>;
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires random_access_range<V>;
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires random_access_range<V> && three_way_comparable<iterator_t<V>>;
friend constexpr iterator operator+(const iterator& x, difference_type n)
requires random_access_range<V>;
friend constexpr iterator operator+(difference_type n, const iterator& x)
requires random_access_range<V>;
friend constexpr iterator operator-(const iterator& x, difference_type n)
requires random_access_range<V>;
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires sized_sentinel_for<iterator_t<V>, iterator_t<V>>;
friend constexpr decltype(auto) iter_move(const iterator& i)
noexcept(noexcept(ranges::iter_move(i.current_)));
friend constexpr void iter_swap(const iterator& x, const iterator& y)
noexcept(noexcept(ranges::iter_swap(x.current_, y.current_)))
requires indirectly_swappable<iterator_t<V>>;
};
}
The member typedef-name iterator_concept is defined as follows:
V models contiguous_range, then iterator_concept denotes
contiguous_iterator_tag.V models random_access_range, then iterator_concept denotes
random_access_iterator_tag.V models bidirectional_range, then iterator_concept denotes
bidirectional_iterator_tag.iterator_concept denotes forward_iterator_tag.
The member typedef-name iterator_category is defined as follows:
C be iterator_traits<iterator_t<V>>::iterator_category.C models derived_from<random_access_iterator_tag>, then
iterator_category denotes random_access_iterator_tag.C models derived_from<bidirectional_iterator_tag>, then
iterator_category denotes bidirectional_iterator_tag.iterator_category denotes C.constexpr iterator(iterator_t<V> current, iterator_t<V> probe);
Effects: Initializes
current_withcurrentandprobe_withprobe.
constexpr iterator_t<V> base() const;
Effects: Equivalent to:
return current_;
constexpr range_reference_t<V> operator*() const;
Effects: Equivalent to:
return *current_;
constexpr auto operator->() const noexcept requires contiguous_range<V>;
Effects: Equivalent to:
return to_address(current_);
constexpr iterator& operator++();
Effects: Equivalent to:
++current_; ++probe_; return *this;
constexpr iterator& operator--() requires bidirectional_range<V>;
Effects: Equivalent to:
--current_; --probe_; return *this;
constexpr iterator& operator+=(difference_type n) requires random_access_range<V>;
Effects: Equivalent to:
current_ += n; probe_ += n; return *this;
constexpr iterator& operator-=(difference_type n) requires random_access_range<V>;
Effects: Equivalent to:
current_ -= n; probe_ -= n; return *this;
constexpr range_reference_t<V> operator[](difference_type n) const
requires random_access_range<V>;
Effects: Equivalent to:
return current_[n];
friend constexpr bool operator==(const iterator& x, const iterator& y);
Returns:
x.current_ == y.current_.
friend constexpr bool operator==(const iterator& x, const sentinel_t<V>& y);
Returns:
x.probe_ == y.
friend constexpr bool operator<(const iterator& x, const iterator& y)
requires random_access_range<V>;
friend constexpr bool operator>(const iterator& x, const iterator& y)
requires random_access_range<V>;
friend constexpr bool operator<=(const iterator& x, const iterator& y)
requires random_access_range<V>;
friend constexpr bool operator>=(const iterator& x, const iterator& y)
requires random_access_range<V>;
friend constexpr auto operator<=>(const iterator& x, const iterator& y)
requires random_access_range<V> && three_way_comparable<iterator_t<V>>;
Let op be the operator.
Effects: Equivalent to:
return x.current_ op y.current_;
friend constexpr iterator operator+(const iterator& x, difference_type n)
requires random_access_range<V>;
friend constexpr iterator operator+(difference_type n, const iterator& x)
requires random_access_range<V>;
Effects: Equivalent to:
auto tmp = x; tmp += n; return tmp;
friend constexpr iterator operator-(const iterator& x, difference_type n)
requires random_access_range<V>;
Effects: Equivalent to:
auto tmp = x; tmp -= n; return tmp;
friend constexpr difference_type operator-(const iterator& x, const iterator& y)
requires sized_sentinel_for<iterator_t<V>, iterator_t<V>>;
Returns:
x.current_ - y.current_.
friend constexpr decltype(auto) iter_move(const iterator& i)
noexcept(noexcept(ranges::iter_move(i.current_)));
Effects: Equivalent to:
return ranges::iter_move(i.current_);
friend constexpr void iter_swap(const iterator& x, const iterator& y)
noexcept(noexcept(ranges::iter_swap(x.current_, y.current_)))
requires indirectly_swappable<iterator_t<V>>;
Effects: Equivalent to
ranges::iter_swap(x.current_, y.current_).
The author implemented views::take_last and views::drop_last based on libstdc++, see here.
Add to [version.syn]:
#define __cpp_lib_ranges_take_last 20XXXXL // freestanding, also in <ranges>
#define __cpp_lib_ranges_drop_last 20XXXXL // freestanding, also in <ranges>