引言
C++20 带来了一项强大的语言特性——概念(Concepts)。它为模板编程提供了静态约束,能在编译期显式说明模板参数的要求。概念的加入让代码更易读、错误更易定位,也提升了编译器的诊断能力。本文将从概念的定义、使用方法、典型示例以及与已有特性的关系展开阐述,并给出实用的编程技巧。
1. 概念的基本语法
template <typename T>
concept ConceptName = /* 条件表达式 */;
- 条件表达式:使用
requires子句或直接写 `std::is_integral_v ` 之类的逻辑表达式。 - 返回值:
true或false。 - 可组合性:概念可以通过
&&,||,!等运算符组合形成更复杂的约束。
1.1 示例:定义一个可迭代的概念
template <typename Iter>
concept Iterable = requires(Iter it) {
*it; // 解引用
++it; // 前置递增
{ it == it } -> std::convertible_to <bool>; // 可比较
};
2. 在模板中使用概念
概念可以放在模板参数列表中,用 typename T : ConceptName 或 typename T = Default : ConceptName 的形式约束。
template <typename Container>
requires Iterable<decltype(std::begin(std::declval<Container&>()))>
void printAll(const Container& c) {
for (auto&& val : c) {
std::cout << val << ' ';
}
std::cout << '\n';
}
或者使用更简洁的 requires 语法:
template <Iterable Iter>
void printAll(const Iter& container) {
for (const auto& v : container) {
std::cout << v << ' ';
}
std::cout << '\n';
}
3. 典型概念示例
3.1 数值类型概念
template <typename T>
concept Numeric = std::is_arithmetic_v <T> || std::is_same_v<T, std::complex<float>>;
3.2 关联容器概念
template <typename C>
concept AssociativeContainer = requires(C c) {
typename C::key_type;
typename C::mapped_type;
{ c.at(typename C::key_type{}) } -> std::convertible_to<typename C::mapped_type>;
};
4. 概念与SFINAE的关系
之前的可行做法是使用 SFINAE(Substitution Failure Is Not An Error)实现模板约束。概念使得约束更加直观、错误信息更友好:
- SFINAE:需要写大量的
std::enable_if_t、decltype等,错误提示往往不直观。 - 概念:直接在模板参数列表写约束,编译器会在不满足约束时给出清晰的报错信息。
5. 实用技巧
5.1 组合概念
template <typename T>
concept Integral = std::is_integral_v <T>;
template <typename T>
concept SignedIntegral = Integral <T> && std::is_signed_v<T>;
template <typename T>
concept UnsignedIntegral = Integral <T> && std::is_unsigned_v<T>;
5.2 定义范围约束
template <typename T>
concept SignedNumber = requires(T x) {
{ std::is_signed_v <T> } -> std::convertible_to<bool>;
};
template <SignedNumber T>
void process(T value) {
// ...
}
5.3 与 std::ranges 的配合
C++20 的 std::ranges 里大量使用概念,如 std::ranges::input_range, std::ranges::viewable_range 等。结合概念可以轻松实现范围算法。
#include <ranges>
template <std::ranges::input_range R>
void sumRange(const R& r) {
auto total = std::accumulate(std::ranges::begin(r), std::ranges::end(r), 0);
std::cout << "Sum: " << total << '\n';
}
6. 概念的局限与未来展望
- 限制:概念是静态约束,不能捕捉运行时状态;对类成员函数的动态多态支持不完善。
- 未来:C++23 正在改进
requires子句的可读性,并扩展对泛型编程的支持。还有可能出现更加细粒度的 约束模板 语法。
7. 小结
概念是 C++20 的一大亮点,它让模板编程更具表达力、错误信息更友好。通过熟练使用概念,可以让代码更易维护、可读性更高。推荐在新项目中从一开始就引入概念,逐步迁移旧代码,获得更安全、易调试的 C++ 代码基。
祝你在 C++ 的泛型世界中玩得开心,写出高质量、易维护的代码!