std::ranges::binary_search要求视图必须是已排序的随机访问范围;需满足元素有序且支持random_access_iterator,否则崩溃或结果错误,常见陷阱包括误用filter等仅提供前向迭代器的适配器。

std::ranges::binary_search 要求视图必须是已排序的随机访问范围
直接调用 std::ranges::binary_search 会崩溃或返回错误结果,如果传入的视图(比如 std::views::filter 或 std::views::transform)不满足两个硬性条件:一是元素已升序(或按指定比较器有序),二是底层支持 random_access_iterator。常见陷阱是误以为“只要能遍历就能二分”——其实 std::ranges::binary_search 内部依赖 std::ranges::size 和 O(1) 的迭代器偏移,而 std::views::drop_while、std::views::take 等多数适配器虽支持随机访问,但 std::views::filter 默认只提供前向迭代器,根本不能用于 binary_search。
- 确认视图类型:用
static_assert检查std::ranges::random_access_range<decltype></decltype> - 验证有序性:二分搜索不验证顺序,乱序数据返回结果不可靠,建议仅在构建视图时就保证排序(例如从
std::vector构建后用std::sort预处理) - 避免链式过滤后直接搜索:如
v | std::views::filter(...) | std::views::take(n)—— 即使take是随机访问,上游filter已降级整个管道为前向范围
正确构造可二分搜索的视图:优先用 vector + views::subrange 或 views::all
最稳妥的方式不是“改造任意视图”,而是从源头控制:先准备一个已排序的容器(如 std::vector),再用 std::ranges::subrange 或 std::views::all 包裹其某段连续内存。这样既保留随机访问,又避免视图组合带来的迭代器类别退化。
- 推荐写法:
auto sorted_vec = std::vector{1, 3, 5, 7, 9}; auto view = std::ranges::subrange(sorted_vec.begin(), sorted_vec.end()); - 若需切片(如后半段):
auto half = std::ranges::subrange(sorted_vec.begin() + sorted_vec.size()/2, sorted_vec.end());—— 仍保持随机访问 - 不要用
std::views::iota直接配binary_search:虽然iota_view是随机访问且有序,但搜索时需注意值域匹配(比如std::views::iota(10, 20)中搜15可行,搜5就永远失败)
调用 binary_search 时比较器必须与排序方式严格一致
如果原始数据是用自定义比较器排序的(比如降序),那么 std::ranges::binary_search 必须传入**完全相同的比较器**,否则行为未定义——它不会自动推断你当初怎么排的。
- 升序默认:
std::ranges::binary_search(view, 7)等价于std::ranges::binary_search(view, 7, std::less{}) - 降序场景:若用
std::sort(v.begin(), v.end(), std::greater{})排过,则必须写std::ranges::binary_search(view, 7, std::greater{}) - 结构体搜索:假设
struct Node { int key; }; auto cmp = [](const Node& a, const Node& b) { return a.key ,则排序和搜索都得传这个 lambda(或用 <code>std::less配合operator<)
性能关键:binary_search 本身不拷贝数据,但视图构造不当会引入隐式开销
std::ranges::binary_search 是纯算法,零拷贝、O(log n) 时间,但如果你写的视图表达式触发了多次 begin/end 计算(比如某些适配器在每次调用 size() 时重新遍历),实际耗时可能远超预期。尤其注意 std::views::counted 和 std::views::bounded 在某些标准库实现中尚未完全优化。
立即学习“C++免费学习笔记(深入)”;
- 实测建议:对同一视图反复搜索时,先用
auto r = std::ranges::ref_view{view};缓存引用,避免视图对象重复构造 - 警惕
std::views::common:它会强制 materialize 迭代器,对非随机访问源反而拖慢速度,这里完全不需要 - Release 模式下,Clang 15+ 和 GCC 13+ 对简单 subrange 能内联掉几乎所有开销;Debug 模式下建议关掉
_GLIBCXX_DEBUG,否则 range-check 开销显著
视图能否二分,不取决于“看起来像数组”,而取决于迭代器类别和排序契约是否被严格执行——漏掉任一环节,结果就只是碰巧对了而已。
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/jiquanzatan/124038.html