C++23 views::chunk_by (P2443R1) 详解

发布于:2025-05-10 ⋅ 阅读:(9) ⋅ 点赞:(0)

引言

在C++的发展历程中,每一个新版本都会带来一些令人期待的特性和改进。C++23作为C++20的增量更新,聚焦于简化代码、增强类型安全和填补功能缺口。其中,std::views::chunk_by 作为范围适配器的新成员,为我们处理数据序列提供了更加便捷和高效的方式。本文将详细介绍 std::views::chunk_by 的基本概念、特性、使用场景以及示例代码。

C++23 范围库概述

在深入了解 std::views::chunk_by 之前,我们先来简单回顾一下C++20引入的范围库(Ranges library)。范围库是C++20的一个主要组成部分,它提供了一种新的方法来处理容器中的元素序列,使得代码更加简洁和可读。范围库主要包含以下几个关键概念:

范围视图(Range Views)

范围视图提供了一种不修改原始数据的情况下,对容器进行变换、过滤和切片的能力。常见的视图包括 std::views::allstd::views::filterstd::views::transform 等。例如:

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

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    auto even_numbers = vec | std::views::filter([](int x) { return x % 2 == 0; });
    auto squared = even_numbers | std::views::transform([](int x) { return x * x; });
    auto first_three = squared | std::views::take(3);
    for (int x : first_three) {
        std::cout << x << " ";
    }
    // 输出: 4 16 36 
    return 0;
}

范围算法(Range Algorithms)

C++20引入了新的范围版本的算法,这些算法可以直接在范围对象上操作,而不需要显式循环。例如 std::ranges::sortstd::ranges::find 等。示例如下:

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

int main() {
    std::vector<int> vec = {5, 3, 1, 4, 2};
    std::ranges::sort(vec);
    std::cout << "Sorted vector: ";
    for (int x : vec) {
        std::cout << x << " ";
    }
    // 输出: Sorted vector: 1 2 3 4 5 
    auto min_val = std::ranges::min(vec);
    std::cout << "\nMinimum value: " << min_val << std::endl;
    // 输出: Minimum value: 1 
    return 0;
}

范围适配器(Range Adapters)

范围适配器类似于视图,但它们通常用于创建新的范围对象。常见的适配器包括 std::ranges::views::joinstd::ranges::views::chunk_by_size 等。

std::views::chunk_by 介绍

基本概念

std::views::chunk_by 是C++23中引入的一个范围适配器,它接受一个视图和一个可调用对象(二元谓词),并通过在每对相邻元素之间拆分底层视图来生成一个子范围(块)的视图。对于这些元素,谓词返回 false。每对元素中的第一个元素属于前一个块,第二个元素属于下一个块。

在标题 <ranges> 中定义如下:

template< ranges::forward_range V, std::indirect_binary_predicate<iterator_t<V>, ranges::iterator_t<V>> Pred >
    requires ranges::view<V> && std::is_object_v<Pred>
class chunk_by_view
    : public ranges::view_interface<chunk_by_view<V, Pred>> (since C++23)
namespace views {
    inline constexpr /* unspecified */ chunk_by = /* 未指定 */ ;
} (since C++23)

特性

  • 谓词而非等价关系:与D语言的 chunkBy 不同,std::views::chunk_by 不要求谓词是一个等价关系。例如,D的 chunkBy 要求谓词满足自反性、对称性和传递性,而 std::views::chunk_by 则没有这个限制。
  • 不支持输入范围:由于 chunk_by 需要对相邻元素计算谓词,因此它要求底层范围是前向范围(forward range)。缓存元素不是一种可行的方法,主要原因在 [P2321R2] 的第4.3.4节中有讨论。
  • 范围属性:如果底层范围是双向范围(bidirectional range),则 chunk_by 也是双向范围;否则,它是前向范围。如果底层范围是通用范围(common range),则 chunk_by 也是通用范围。它永远不会是借用范围(borrowed range)或有大小范围(sized range)。
  • 缓存机制:类似于 splitchunk_by 在其 begin 中计算第一个范围的末尾并进行缓存,以满足摊销的 O ( 1 ) O(1) O(1) 要求。这意味着它不支持常量迭代。

使用场景

std::views::chunk_by 适用于需要将一个序列按照某种规则进行分组的场景。例如,将一个整数序列按照奇偶性进行分组,或者将一个字符串序列按照长度进行分组等。

示例代码

简单示例

#include <iostream>
#include <vector>
#include <ranges>
#include <fmt/core.h>

int main() {
    std::vector<int> v = {1, 2, 2, 3, 0, 4, 5, 2};
    fmt::print("{}\n", v | std::views::chunk_by(std::ranges::less_equal{}));   // [[1, 2, 2, 3], [0, 4, 5], [2]]
    return 0;
}

在这个示例中,我们使用 std::views::chunk_bystd::ranges::less_equal{} 谓词将整数序列 v 进行分组。当相邻元素不满足小于等于关系时,就会进行分组。

自定义谓词示例

#include <iostream>
#include <vector>
#include <ranges>
#include <fmt/core.h>

int main() {
    std::vector<int> v = {1, 3, 5, 2, 4, 6, 7, 9};
    auto is_odd = [](int x) { return x % 2 == 1; };
    auto chunked = v | std::views::chunk_by([&is_odd](int a, int b) { return is_odd(a) == is_odd(b); });
    for (const auto& chunk : chunked) {
        for (int x : chunk) {
            std::cout << x << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}

在这个示例中,我们自定义了一个谓词 is_odd 来判断一个数是否为奇数。然后使用 std::views::chunk_by 将整数序列按照奇偶性进行分组。

总结

std::views::chunk_by 是C++23范围库中一个非常实用的范围适配器,它为我们处理数据序列提供了更加灵活和高效的方式。通过使用 std::views::chunk_by,我们可以轻松地将一个序列按照某种规则进行分组,而不需要手动编写复杂的循环和逻辑。同时,范围库的惰性求值特性也使得代码更加高效,避免了不必要的计算和内存消耗。在实际开发中,我们可以根据具体的需求灵活运用 std::views::chunk_by 来简化代码,提高开发效率。


网站公告

今日签到

点亮在社区的每一天
去签到