C++20的concepts和ranges是泛型编程领域的两大重要革新,前者为模板参数提供了明确的约束机制,后者重新设计了标准库的范围操作体系,两者结合可以大幅简化泛型代码的编写和维护成本。

concepts的基本使用
concepts的核心作用是约束模板参数的类型特征,避免模板实例化时出现晦涩的编译错误。我们可以通过concept关键字定义约束,也可以在模板参数中直接使用标准库预定义的concepts。
自定义concepts
自定义concepts需要指定要检查的类型特征,比如要求类型支持加法操作、是可比较类型等。以下是一个简单的自定义concepts示例:
#include <iostream>
#include <concepts>
// 定义一个concept,要求类型T支持+操作,且两个T相加的结果还是T类型
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
// 使用Addable约束模板参数
template <Addable T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(1, 2) << std::endl; // 正确,int满足Addable约束
// std::cout << add(std::cout, std::cout) << std::endl; // 错误,编译期会提示不满足Addable约束
return 0;
}
标准库预定义concepts
C++20标准库在<concepts>头文件中提供了大量常用的预定义concepts,比如std::integral(整数类型)、std::floating_point(浮点类型)、std::same_as(两个类型相同)等,可以直接使用减少重复定义。
#include <iostream>
#include <concepts>
// 使用标准库预定义的std::integral约束模板参数
template <std::integral T>
T square(T num) {
return num * num;
}
int main() {
std::cout << square(5) << std::endl; // 正确,int是整数类型
// std::cout << square(5.2) << std::endl; // 错误,double不满足std::integral约束
return 0;
}
ranges的基本使用
ranges对传统STL算法进行了封装,支持直接操作范围对象,并且可以实现链式调用,避免了传统算法需要传入迭代器起始和结束位置的繁琐写法。
基础范围操作
ranges算法可以直接接收容器作为操作对象,不需要手动获取begin和end迭代器。以下是一个简单的排序示例:
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
int main() {
std::vector<int> nums = {3, 1, 4, 1, 5, 9};
// 使用ranges的sort直接对容器排序
std::ranges::sort(nums);
for (int num : nums) {
std::cout << num << " ";
}
// 输出:1 1 3 4 5 9
return 0;
}
范围适配器与链式调用
ranges提供了大量范围适配器,比如std::views::filter(过滤)、std::views::transform(转换)、std::views::take(取前n个元素)等,这些适配器可以通过管道运算符|实现链式调用,让代码逻辑更清晰。
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9};
// 过滤出偶数,再将每个偶数乘以2,最后取前3个结果
auto result = nums
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * 2; })
| std::views::take(3);
for (int num : result) {
std::cout << num << " ";
}
// 输出:4 8 12
return 0;
}
concepts与ranges的结合使用
concepts可以对ranges操作中的参数进行约束,比如约束过滤函数的参数类型、转换函数的返回类型等,让范围操作的类型检查更严格。
以下示例定义了一个concepts约束函数对象,确保过滤函数接收整数并返回布尔值,再将其用于ranges的filter适配器:
#include <iostream>
#include <vector>
#include <ranges>
#include <concepts>
// 定义concept,约束函数对象接收int参数,返回bool
template <typename F>
concept IntFilter = requires(F f, int n) {
{ f(n) } -> std::convertible_to<bool>;
};
// 接收满足IntFilter约束的函数对象,对整数范围进行过滤
template <std::ranges::input_range R, IntFilter F>
auto filter_range(R&& range, F filter_func) {
return range | std::views::filter(filter_func);
}
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// 使用满足IntFilter约束的lambda进行过滤
auto result = filter_range(nums, [](int n) { return n > 3; });
for (int num : result) {
std::cout << num << " ";
}
// 输出:4 5
return 0;
}
使用注意事项
- concepts约束是编译期检查,不会影响运行时性能,建议在泛型模板中优先使用concepts替代传统的SFINAE写法。
- ranges的范围适配器返回的是惰性求值的范围对象,只有在实际遍历的时候才会执行操作,不需要担心中间过程产生额外的容器拷贝。
- 部分旧的编译器对C++20的concepts和ranges支持还不完善,使用前需要确认编译器的支持版本,比如GCC 10+、Clang 13+、MSVC 2019+才基本支持这两个特性。