我们都遇到过这样的情况:当为类或函数模板提供错误的参数时,编译器会输出数百行错误信息。要从这些编译器错误中找到根本原因并不总是容易的。Concepts 允许编译器在某些类型约束不满足时输出更易读的错误消息。因此,为了获得有意义的语义错误,建议编写模拟语义要求的 Concepts。避免仅针对语法方面而没有任何语义意义的 Concepts 验证,例如,仅检查类型是否支持 operator+ 的 Concept。这样的 Concept 只会检查语法,而不是语义。例如 std::string 支持 operator+,但显然,它与整数的 operator+ 有完全不同的含义。另一方面,如 sortable(可排序)和 swappable(可交换)等 Concepts 是模拟一些语义意义的好例子。
注意:编写 Concepts 时,请确保它们模拟语义,而不仅仅是语法。
Concepts 的语法
定义 Concepts 的通用语法如下:
template concept concept-name = constraints-expression;
它以熟悉的 template<> 规范开始,但与类和函数模板不同,Concepts 永远不会被实例化。接下来,使用一个新关键字 concept,后跟 Concept 的名称。你可以使用任何你想要的名称。constraints-expression 可以是任何常量表达式,即任何可以在编译时评估的表达式。约束表达式必须产生布尔值。约束永远不会在运行时评估。约束表达式将在下一节中详细讨论。
Concept 表达式的语法如下:
concept-name
Concept 表达式评估为真或假。如果评估为真,则称给定的模板参数模拟了该 Concept。
Constraints Expression
1.常量表达式
可直接用作 Concept 定义约束的布尔常量表达式必须精确地计算为布尔值,不进行任何类型转换。例如:
template
concept C = sizeof(T) == 4;
2.Requires 表达式
Requires 表达式的语法如下:
requires (parameter-list) { requirements; }
参数列表是可选的。每个要求必须以分号结束。有四种类型的要求:简单要求、类型要求、复合要求和嵌套要求。
(1) 简单要求
简单要求是任意不以 requires 开头的表达式语句。例如,以下 Concept 定义规定了某种类型 T 必须支持后缀和前缀 ++ 操作符:
template
concept Incrementable = requires(T x) {
x++;
++x;
};
(2) 类型要求
类型要求验证某种类型是否有效。例如,以下 Concept 要求某种类型 T 具有 value_type 成员:
template
concept C = requires {
typename T::value_type;
};
(3) 复合要求
复合要求用于验证某事物不抛出异常,以及/或验证某个方法返回特定类型。例如,以下 Concept 验证给定类型具有标记为 noexcept 的 swap() 方法:
template
concept C = requires (T x, T y) {
{ x.swap(y) } noexcept;
};
(4) 嵌套要求
Requires 表达式可以包含嵌套要求。例如,这里是一个要求类型大小为 4 字节并支持前缀和后缀增量和减量操作的 Concept:
template
concept C = requires (T t) {
requires sizeof(t) == 4;
++t;
--t;
t++;
t--;
};
Requires 表达式可以有多个参数,并且可以由一系列要求组成。例如,以下 Concept 要求类型 T 的实例是可比较的:
template
concept Comparable = requires(const T a, const T b) {
{ a == b } -> convertible_to;
{ a < b } -> convertible_to;
// ... 对其他比较操作符的类似要求 ...
};
组合 Concept 表达式
使用逻辑运算符组合:
现有的 Concept 表达式可以通过使用逻辑运算符“与”(&&)和“或”(||)来组合。例如,假设您有一个类似于 Incrementable 的 Decrementable Concept;以下示例展示了一个要求类型同时具备增量和减量能力的 Concept:
template
concept IncrementableAndDecrementable = Incrementable && Decrementable;
预定义的标准 Concepts
(1) 标准库中的 Concepts
标准库定义了一系列预定义的 Concepts,分为多个类别。以下列表给出了每个类别中的一些示例 Concepts,所有这些都在
- 核心语言 Concepts:same_as、derived_from、convertible_to、integral、floating_point、copy_constructible 等。
- 比较 Concepts:equality_comparable、totally_ordered 等。
- 对象 Concepts:movable、copyable 等。
- 可调用 Concepts:invocable、predicate 等。
此外,
C++20 范围库还提供了一些标准 Concepts。第17章《理解迭代器和范围库》详细讨论了迭代器和范围,而第20章更深入地探讨了标准库提供的算法。
(2) 使用标准 Concepts
如果这些标准 Concepts 满足您的需求,您可以直接使用它们,无需自己实现。例如,以下 Concept 要求类型 T 是从类 Foo 派生的:
template
concept IsDerivedFromFoo = derived_from;
以下 Concept 要求类型 T 可以转换为 bool:
template
concept IsConvertibleToBool = convertible_to;
这些标准 Concepts 也可以组合成更具体的 Concepts。例如,以下 Concept 要求类型 T 既是默认构造的也是可拷贝构造的:
template
concept DefaultAndCopyConstructible = default_initializable && copy_constructible;
注意:编写完整且正确的 Concepts 并不总是容易的。如果可能,尝试使用现有的标准 Concepts 或它们的组合来约束您的类型。