概念(Concepts)是 C++20 引入的一项强大特性,它为模板参数提供了更直观、更安全的约束,使代码更易读、调试更友好。下面通过一个完整示例,展示如何定义和使用概念来优化模板代码。
1. 定义概念
概念本质上是一个可重用的布尔表达式,用来约束模板参数。我们先定义几个常见的概念:
#include <concepts>
#include <type_traits>
template <typename T>
concept Integral = std::is_integral_v <T>;
template <typename T, typename U>
concept ConvertibleTo = std::is_convertible_v<T, U>;
template <typename T>
concept DefaultConstructible = std::default_initializable <T>;
template <typename T>
concept CopyAssignable = std::copy_assignable <T>;
Integral:判断类型是否为整数类型。ConvertibleTo<T, U>:判断T是否可隐式转换为U。DefaultConstructible:判断类型是否可以默认构造。CopyAssignable:判断类型是否可被复制赋值。
2. 使用概念约束模板
下面的 SafeContainer 是一个泛型容器,内部存储 T 类型元素。我们用概念来限制 T 必须满足 DefaultConstructible 和 CopyAssignable,并且提供 push_back 时确保输入类型能转换为 T。
#include <vector>
template <typename T>
requires DefaultConstructible <T> && CopyAssignable<T>
class SafeContainer {
public:
void push_back(const T& value) {
data_.push_back(value);
}
template <typename U>
requires ConvertibleTo<U, T>
void push_back(U&& value) {
data_.push_back(static_cast <T>(std::forward<U>(value)));
}
const T& at(std::size_t idx) const {
return data_.at(idx);
}
std::size_t size() const { return data_.size(); }
private:
std::vector <T> data_;
};
关键点
requires关键字后面直接跟概念,避免了传统typename std::enable_if的冗长写法。- 通过
ConvertibleTo<U, T>,push_back能接受能隐式转换为T的类型,例如int能被转换为double。
3. 示例使用
int main() {
SafeContainer <int> intC;
intC.push_back(42); // OK
intC.push_back(100); // OK
SafeContainer <double> dblC;
dblC.push_back(3.14); // OK
dblC.push_back(5); // int 转 double,OK
// SafeContainer<std::string> strC; // 错误:string 不是 DefaultConstructible
}
4. 与传统 SFINAE 的对比
- 可读性:概念直接表达意图,SFINAE 需要写
typename std::enable_if_t<..., int> = 0,可读性差。 - 编译错误信息:概念提供更明确的错误提示,SFINAE 错误往往堆叠难以定位。
- 性能:编译时约束不影响运行时性能,SFINAE 同样如此,但编译器优化更易。
5. 小结
- 概念让模板约束表达更简洁、可维护。
- 通过
requires关键字直接约束模板参数,避免冗长的 SFINAE 代码。 - 结合标准库中的概念(如
std::integral、std::copy_constructible)可以大幅提升代码质量。
在实际项目中,建议在编写泛型代码时先考虑使用概念,既能让代码更安全,也能提升团队协作的效率。