You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效获取non-sized range的最后n个元素?(C++20/23或Range-v3)

获取非 Sized Range 最后 n 个元素的高效方法

问题示例代码

#include <range/v3/all.hpp>

namespace views3 = ranges::views;

int main()
{
    std::string_view sv = "Hello world! Goodbye world!";
    auto notBang = [](char ch) { return ch != '!'; };
    auto input = std::views::take_while(sv, notBang);
    static_assert(!std::ranges::sized_range<decltype(input)>);
    static_assert(!std::ranges::common_range<decltype(input)>);
    static_assert(std::ranges::contiguous_range<decltype(input)>);

    auto v1 = input | views3::take_last(3);  // 编译错误
    std::cout << (v1 | ranges::to<std::string>) << "\n";  // 期望输出:rld

    auto v2 = input | views3::drop_last(3);  // 正常工作
    std::cout << (v2 | ranges::to<std::string>) << "\n";  // 输出:Hello wo
}

问题分析

上述代码里的input是**连续(contiguous)、非公共(non-common)、非 Sized(non-sized)**的range。Range-v3的take_last(n)没法处理这类range,因为它的实现逻辑是构造时计算ranges::begin(input) + (ranges::size(input) - n),而非 Sized range没有ranges::size()方法。

但drop_last(n)可以正常处理非 Sized range——它通过维护两个间隔n个元素的迭代器,同步向前遍历。当drop_last遍历完成后,两个迭代器之间的范围恰好就是take_last(n)想要的结果,但没法直接获取这个范围。

解决方案

1. Range-v3 反转两次实现(适用于双向迭代器范畴以上的range)

如果你的range支持双向遍历,可以通过反转range取前n个元素再反转回来的方式实现,逻辑简单直观:

auto v1 = input | views3::reverse | views3::take(3) | views3::reverse;
std::cout << (v1 | ranges::to<std::string>) << "\n";  // 输出:rld

这种方法时间复杂度O(N),空间复杂度仅迭代器开销(O(1)),对于连续range来说反转迭代器的额外开销可以忽略。

2. 手动实现适配非 Sized range 的 take_last

参考drop_last的双迭代器思路,我们可以实现一个通用的take_last,支持所有输入迭代器范畴的range:

template <std::ranges::input_range R>
requires (!std::ranges::sized_range<R>)
auto take_last(R&& r, std::size_t n) {
    using namespace ranges;
    if (n == 0) return views::empty<typename range_value_t<R>>;
    
    auto it = begin(r);
    auto lead = it;
    // 先让领先迭代器前进n步
    for (std::size_t i = 0; i < n && lead != end(r); ++i) {
        ++lead;
    }
    // 同步前进两个迭代器,直到领先迭代器到达末尾
    while (lead != end(r)) {
        ++it;
        ++lead;
    }
    // 返回从当前迭代器到末尾的子range
    return views::subrange(it, end(r));
}

调用方式:

auto v1 = take_last(input, 3);
std::cout << (v1 | ranges::to<std::string>) << "\n";  // 输出:rld

这个实现和drop_last逻辑一致,时间复杂度O(N),空间复杂度O(1),适配所有非 Sized 的输入range。

3. C++23 标准库原生支持

如果你的编译器支持C++23,直接使用标准库的std::views::take_last即可——它已经适配了非 Sized range(只要range属于双向迭代器范畴以上):

auto v1 = input | std::views::take_last(3);

补充说明

Range-v3的take_last确实存在这个局限性,依赖range的size()方法导致无法处理非 Sized range。对比它仅66行的简化实现和drop_last的373行完整实现,这更像是早期的设计妥协,而非刻意的功能限制。

内容的提问来源于stack exchange,提问作者Quuxplusone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 18:51:20