You can do a good approximation via:
struct drop_last_t {
template <std::ranges::sized_range R>
requires std::ranges::viewable_range<R>
friend auto operator|(R&& r, drop_last_t) {
auto n = std::ranges::size(r);
return std::views::take(std::forward<R>(r), n > 0 ? n - 1 : 0);
}
};
inline constexpr drop_last_t drop_last;
That lets you:
for (const auto& d: foo | drop_last)
This isn't a perfect range adaptor, since you can't write something like auto my_adaptor = transform(f) | drop_last;
In order to do that, you need P2387, which is a C++23 library feature. In C++23, you'd write it this way:
struct drop_last_t : std::ranges::range_adaptor_closure<drop_last_t>
{
template <std::ranges::sized_range R>
requires std::ranges::viewable_range<R>
auto operator()(R&& r) const {
auto n = std::ranges::size(r);
return std::views::take(std::forward<R>(r), n > 0 ? n - 1 : 0);
}
};
inline constexpr drop_last_t drop_last;
And now this is a completely functional range adaptor. The current libstdc++-specific version looks like this (just to demonstrate, don't actually do this - this isn't how you'd do this in C++23).
Of course, this is limited to sized ranges. There's all sorts of directions this could go. You could support any forward range by doing std::ranges::distance(r)
instead (at the cost of multiple traversal). But a bespoke implementation could do better. For bidi+common, you just need to stop at prev(end(r))
. For forward only, you could advance two iterators at a time, etc. Just something to think about.