7

Is there any better way to drop last element in container using c++20 ranges than reverse it twice?

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> foo{1, 2, 3, 4, 5, 6};

    for (const auto& d: foo | std::ranges::views::reverse 
                            | std::ranges::views::drop(1) 
                            | std::ranges::views::reverse)
    {
        std::cout << d << std::endl;
    }
}
cr0pp
  • 73
  • 3

3 Answers3

8

What you need is views::drop_last which comes from p2214 and has a priority of Tier 2.

As the paper says:

We’ll go through the other potential range adapters in this family and discuss how they could be implemented in terms of existing adapters:

  • take_last(N) and drop_last(N). views::take_last(N) is equivalent to views::reverse | views::take(N) | views::reverse. But this is somewhat expensive, especially for non-common views. For random-access, sized ranges, we’re probably want r | views::take_last(N) to evaluate as r | views::drop(r.size() - N), and that desire is really the crux of this whole question — is the equivalent version good enough or should we want to do it right?

Since vector is a random-access, sized range, you can just do

for (const auto& d: foo | std::views::take(foo.size() - 1))
{
    std::cout << d << std::endl;
}
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
7

How about using span?

#include <iostream>
#include <span>
#include <vector>

int main() {
  std::vector<int> foo{1, 2, 3, 4, 5, 6};

  for (const auto& d : std::span(foo.begin(), foo.end() - 1)) {
    std::cout << d << '\n';
  }
}
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
2

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks for your addition, Barry. But should this `drop_last` name `head` as p2214 does? Since it's more like `drop_last(1)`. – 康桓瑋 Mar 31 '22 at 17:03
  • @康桓瑋 Shrug. I'm not sure which is the better name. – Barry Mar 31 '22 at 17:26
  • question regarding requires: can a "reasonable"(not intentionally broken to pass concept check) range be sized_range, but not viewable_range? Seems impossible, but maybe I am missing something... – NoSenseEtAl Apr 01 '22 at 21:43
  • "*can a "reasonable"(not intentionally broken to pass concept check) range be sized_range, but not viewable_range?*" Are there any dependencies between these two concepts? Why is it not possible? Isn't `std::initializer_list` just one? – 康桓瑋 Apr 02 '22 at 05:36
  • @康桓瑋 ah yes, did not think of that, I presume you mean something like this: https://godbolt.org/z/qzsqj86xe (nice error message from gcc btw ) – NoSenseEtAl Apr 03 '22 at 17:05
  • There's nothing special about the error message, it's just that range `operator|` is not a valid expression since the constraints are not satisfied, so the compiler is just trying to use `operator|` defined elsewhere in the standard library. – 康桓瑋 Apr 03 '22 at 17:59