使用 c++20 范围删除最后一个元素的最佳方法是什么

What is the best way to drop last element using c++20 ranges

有没有比反转两次更好的方法来使用 c++20 范围将最后一个元素放入容器中?

#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;
    }
}

您需要的是 views::drop_last,它来自 p2214,优先级为第 2 层。

作为 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?

因为 vector 是一个 random-access,大小范围,你可以做

for (const auto& d: foo | std::views::take(foo.size() - 1))
{
    std::cout << d << std::endl;
}

使用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';
  }
}

您可以通过以下方式进行很好的近似:

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;

那个lets you:

for (const auto& d: foo | drop_last)

这不是一个完美的范围适配器,因为你不能写类似 auto my_adaptor = transform(f) | drop_last; 的东西为了做到这一点,你需要 P2387,这是一个 C++23 库特性.在 C++23 中,你可以这样写:

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;    

现在这是一个功能齐全的范围适配器。当前特定于 libstdc++ 的版本看起来 like this(只是为了演示,实际上不要这样做——这不是你在 C++23 中这样做的方式)。


当然,这仅限于大小范围。这有各种各样的方向。您可以通过执行 std::ranges::distance(r) 来支持任何前向范围(以多次遍历为代价)。但是定制的实现可以做得更好。对于bidi+common,你只需要停在prev(end(r))。对于仅向前,您可以一次推进两个迭代器等。只是考虑一下。