**C++20 范围(Ranges)与概念(Concepts)的强大组合:让代码更安全、更简洁**

C++20 引入了两个重磅特性——范围(Ranges)概念(Concepts)。这两个特性在一起使用,可以大幅提升代码的可读性、可维护性和类型安全。下面我们通过一个具体的例子来演示它们如何协同工作,并在实践中提供一些实用技巧。


1. 传统方式:std::sort + std::vector

#include <algorithm>
#include <vector>

std::vector <int> vec = {5, 2, 9, 1, 5, 6};
std::sort(vec.begin(), vec.end());  // 原始方式

虽然上述代码简洁,但在大型项目中常会出现:

  • 索引错误begin() / end() 误用导致越界。
  • 类型错误:传入不支持 operator< 的容器或元素。
  • 重复代码:同一容器多处排序实现细节重复。

2. 使用 Ranges 让代码更直观

#include <ranges>
#include <vector>

auto vec = std::vector{5, 2, 9, 1, 5, 6};
vec | std::ranges::sort;  // Ranges 语法

优点

  • 链式表达:像管道一样把操作串联,语义更清晰。
  • 不再手动传递迭代器begin() / end() 被隐藏。
  • 与算法组合更灵活:可以在同一链中添加 std::views::filterstd::views::transform 等。

3. 概念让调用更安全

概念是对模板参数的约束。例如,std::ranges::sortable 用来约束类型是否可排序。

#include <ranges>
#include <vector>
#include <concepts>

template <std::ranges::sortable R>
void my_sort(R&& r) {
    std::ranges::sort(r);
}

调用 my_sort 时,编译器会检查传入的 R 是否满足 sortable,若不满足会产生清晰的错误信息,而不是模糊的模板错误。


4. 结合 Ranges 与 Concepts 的实战案例

需求:对一个包含自定义类 Person 的容器按年龄升序排序,同时只保留年龄大于 20 的人。

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

struct Person {
    std::string name;
    int age;
};

auto persons = std::vector{
    Person{"Alice", 30},
    Person{"Bob", 18},
    Person{"Charlie", 25},
    Person{"David", 19}
};

// 用 lambda 作为概念约束:仅适用于可比较且具有 age 成员
auto age_greater_than_20 = std::views::filter([](const Person& p){ return p.age > 20; });

auto sorted_by_age = persons | age_greater_than_20 | std::ranges::sort;

for (auto& p : sorted_by_age) {
    std::cout << p.name << " (" << p.age << ")\n";
}

输出:

Charlie (25)
Alice (30)

解析

  1. std::views::filter 先过滤年龄 > 20 的元素。
  2. std::ranges::sort 在过滤结果上进行排序。
  3. 由于 std::ranges::sort 默认使用 operator<,而 Person 没有定义此运算符,编译器会报错。我们可以通过显式传递比较函数:
std::ranges::sort(sorted_by_age, [](const Person& a, const Person& b){
    return a.age < b.age;
});

或者为 Person 定义 operator<,实现更自然的语义。


5. 常见陷阱与解决方案

陷阱 解决方案
使用 views::transform 后忘记 to_vector() views 返回的是视图,若需要实际容器,使用 std::ranges::to<std::vector<>>(C++23)或手动复制。
多次排序导致副本生成 在链式调用中尽量保持视图链不产生副本;若需要持久化结果,使用 std::ranges::tostd::vector
概念约束过宽导致编译慢 在模板中使用更细粒度的概念,例如 std::ranges::input_rangestd::sortable 分开。
视图生命周期 视图引用外部容器,确保外部容器在视图使用期间不被销毁。

6. 小结

  • Ranges:让算法链式化、可组合,避免手动迭代器。
  • Concepts:在编译期对模板参数进行约束,提升错误信息的可读性与类型安全。
  • 两者结合:既能写出简洁的代码,又能在编译时捕获潜在错误,是现代 C++ 开发的强大工具。

通过上述示例,你可以将 Ranges 与 Concepts 逐步引入项目,提升代码质量与开发效率。祝编码愉快!

发表评论