c++ constraints与concepts使用笔记
-
-
- [1. 模板参数缺乏约束的问题](#1. 模板参数缺乏约束的问题)
- [2. Concepts 基本概念](#2. Concepts 基本概念)
- [3. Concept 的定义与使用](#3. Concept 的定义与使用)
- [4. requires 表达式详解](#4. requires 表达式详解)
- [5. requires 从句 vs requires 表达式](#5. requires 从句 vs requires 表达式)
- 完整示例:约束矩阵运算
-
1. 模板参数缺乏约束的问题
问题分析:
- 传统模板参数没有语法层面的约束,需要程序员自行通过代码逻辑理解参数要求
- 编译器错误信息不友好,尤其在传递非法参数时(如
vector<int&>
) - 类型检查发生在模板实例化时,而非声明时
示例:
cpp
template<typename T>
class Container {
T data[10];
public:
void copy_from(const Container& other) {
std::copy(std::begin(other.data), std::end(other.data), data);
}
};
struct NonCopyable {
NonCopyable(const NonCopyable&) = delete;
};
Container<NonCopyable> c; // 编译错误出现在实例化时的copy操作,而非类定义处
2. Concepts 基本概念
核心特性:
- C++20 引入的编译期谓词机制
- 通过
requires
子句显式约束模板参数 - 提升代码可读性和编译器错误信息质量
示例:
cpp
template<typename T>
concept Arithmetic = std::is_arithmetic_v<T>;
template<Arithmetic T> // 约束T必须是算术类型
T add(T a, T b) { return a + b; }
add(3, 5); // OK
add("a", "b"); // 明确的编译错误:不满足Arithmetic约束
3. Concept 的定义与使用
(1) 单参数 Concept
cpp
template<typename T>
concept HasSize = requires(T v) {
{ v.size() } -> std::convertible_to<size_t>;
};
template<HasSize T>
void print_size(T obj) {
std::cout << obj.size() << "\n";
}
std::vector v{1,2,3};
print_size(v); // OK
print_size(42); // 错误:int没有size()方法
(2) 多参数 Concept
cpp
template<typename T, typename U>
concept SameAs = std::is_same_v<T, U>;
template<typename T>
concept AddableToInt = requires(T a) {
{ a + 1 } -> SameAs<int>; // 使用两参数Concept
};
AddableToInt auto result = 'A' + 1; // OK,char + int 返回int
4. requires 表达式详解
(1) 简单表达式 表明可以接收的操作
cpp
template<typename T>
concept Incrementable = requires(T v) {
++v; // 检查前置++
v++; // 检查后置++
};
static_assert(Incrementable<int>); // 通过
static_assert(Incrementable<std::string>); // 失败
(2) 类型表达式 表明是一个有效的类型
cpp
template<typename T>
concept HasValueType = requires {
typename T::value_type; // 检查嵌套类型是否存在
};
static_assert(HasValueType<std::vector<int>>); // 通过
static_assert(HasValueType<int>); // 失败
(3) 复合表达式 表明操作的有效性,以及操作返回类型的特性
cpp
template<typename T>
concept StringConvertible = requires(T obj) {
{ std::to_string(obj) } -> std::same_as<std::string>;
};
static_assert(StringConvertible<int>); // 通过
static_assert(StringConvertible<void*>); // 失败
(4) 嵌套表达式 包含其它的限定表达式
cpp
template<typename T>
concept CompleteType = requires {
sizeof(T); // 检查类型完整性
requires !std::is_void_v<T>; // 组合多个条件
};
static_assert(CompleteType<int>); // 通过
static_assert(CompleteType<void>); // 失败
5. requires 从句 vs requires 表达式
关键区别:
cpp
// requires 从句(用于模板参数约束)
template<typename T>
requires std::is_integral_v<T> // ← 这是requires从句
void process(T value) { /*...*/ }
// requires 表达式(用于定义Concept的约束条件)
template<typename T>
concept Streamable = requires(T v, std::ostream& os) {
{ os << v } -> std::same_as<std::ostream&>;
};
完整示例:约束矩阵运算
cpp
template<typename T>
concept Numeric = std::is_arithmetic_v<T> && !std::is_same_v<T, bool>;
template<typename M>
concept Matrix = requires(const M& mat, size_t i, size_t j) {
{ mat.rows() } -> std::convertible_to<size_t>;
{ mat.cols() } -> std::convertible_to<size_t>;
{ mat(i,j) } -> Numeric;
typename M::value_type;
requires Numeric<typename M::value_type>;
};
template<Matrix A, Matrix B>
auto multiply(const A& a, const B& b) {
using T = std::common_type_t<typename A::value_type, typename B::value_type>;
std::vector<std::vector<T>> result(a.rows(), std::vector<T>(b.cols()));
// ... 矩阵乘法实现
return result;
}
以下是对上述代码详细分步解释:
- Numeric概念定义
cpp
template<typename T>
concept Numeric = std::is_arithmetic_v<T> && !std::is_same_v<T, bool>;
- 作用:定义数值类型约束,排除布尔类型。
- 机制 :
std::is_arithmetic_v<T>
检查T
是否为算术类型(整型/浮点型,包括bool
)。!std::is_same_v<T, bool>
排除bool
类型。
- 合法类型示例 :
int
,double
,float
。 - 排除类型示例 :
bool
,std::string
。
- Matrix概念定义
cpp
template<typename M>
concept Matrix = requires(const M& mat, size_t i, size_t j) {
{ mat.rows() } -> std::convertible_to<size_t>;
{ mat.cols() } -> std::convertible_to<size_t>;
{ mat(i,j) } -> Numeric;
typename M::value_type;
requires Numeric<typename M::value_type>;
};
- 作用:定义矩阵类型的编译期接口约束。
- 要求 :
- 维度接口 :必须提供返回
size_t
的rows()
和cols()
方法。 - 元素访问 :支持
operator(i,j)
且返回值满足Numeric
。 - 元素类型 :必须通过
value_type
公开元素类型,且该类型满足Numeric
。
- 维度接口 :必须提供返回
- 示例合规类型:包含上述方法和嵌套类型的自定义矩阵类。
- 矩阵乘法函数模板
cpp
template<Matrix A, Matrix B>
auto multiply(const A& a, const B& b) {
using T = std::common_type_t<typename A::value_type, typename B::value_type>;
std::vector<std::vector<T>> result(a.rows(), std::vector<T>(b.cols()));
// 矩阵乘法实现(待填充)
return result;
}
- 模板约束 :
A
和B
必须满足Matrix
概念。 - 实现步骤 :
- 公共类型计算 :
std::common_type_t
推导两种元素类型的公共可兼容类型(如int+double→double
)。 - 结果容器初始化 :创建
a.rows()×b.cols()
的二维向量,元素默认初始化为T()
。 - 乘法逻辑(需补充):典型的三重循环遍历行、列,进行点积运算。
- 公共类型计算 :
优势:
- 显式约束矩阵类型必须具有
rows()
,cols()
方法 - 元素访问操作
operator()
必须返回数值类型 - 矩阵元素类型必须满足
Numeric
约束 - 编译错误会明确指出具体违反的约束条件
通过合理使用 Concepts 和 requires 表达式,可以显著提升模板代码的可维护性和错误信息的可读性,同时增强接口的自我描述能力。