以下信息已过时。需要根据最新的 Concepts Lite 草案进行更新。
the constraints proposal 的第 3 节对此进行了合理深入的介绍。
The concepts proposal 已经被搁置了一段时间,希望可以在更短的时间内充实和实施约束(即概念精简版),目前的目标是至少在 C++ 中有所作为14.约束提议旨在平滑过渡到后来的概念定义。约束是概念提案的一部分,并且是其定义中的必要组成部分。
在Design of Concept Libraries for C++ 中,Sutton 和 Stroustrup 考虑以下关系:
概念 = 约束 + 公理
快速总结它们的含义:
- 约束 - 一种类型的静态可评估属性的谓词。纯粹的语法要求。不是域抽象。
- 公理 - 假定为真的类型的语义要求。未静态检查。
- 概念 - 算法对其参数的一般抽象要求。根据约束和公理定义。
因此,如果您将公理(语义属性)添加到约束(句法属性),您就会得到概念。
概念-精简版
concepts-lite 提案只为我们带来了第一部分,即约束,但这是迈向成熟概念的重要且必要的一步。
约束
约束都与语法有关。它们为我们提供了一种在编译时静态识别类型属性的方法,以便我们可以根据其句法属性限制用作模板参数的类型。在当前的约束提议中,它们是用命题演算的子集表达的,使用逻辑连接词,如&& 和||。
让我们来看看一个实际的约束:
template <typename Cont>
requires Sortable<Cont>()
void sort(Cont& container);
这里我们定义了一个名为sort 的函数模板。新增的是 requires 子句。 requires 子句对该函数的模板参数给出了一些约束。特别是,这个约束表示Cont 类型必须是Sortable 类型。一个简洁的事情是它可以写成更简洁的形式:
template <Sortable Cont>
void sort(Cont& container);
现在,如果您尝试将不被视为Sortable 的任何内容传递给此函数,您将收到一个很好的错误,该错误会立即告诉您为T 推导的类型不是Sortable 类型。如果您在 C++11 中执行此操作,您将在sort 函数内部抛出一些对任何人都没有意义的可怕错误。
约束谓词与类型特征非常相似。它们采用一些模板参数类型并为您提供一些有关它的信息。约束试图回答以下关于类型的问题:
- 这种类型是否有某某运算符重载?
- 这些类型可以用作此运算符的操作数吗?
- 这种类型有这样那样的特点吗?
- 这个常量表达式等于那个吗? (对于非类型模板参数)
- 此类型是否有一个名为 yada-yada 的函数返回该类型?
- 此类型是否满足所有语法要求?
然而,约束并不意味着替换类型特征。相反,他们将携手合作。现在可以根据概念定义一些类型特征,而根据类型特征定义一些概念。
示例
所以关于约束的重要一点是它们一点也不关心语义。一些很好的约束示例如下:
Equality_comparable<T>:检查该类型是否有==,两个操作数都是相同类型。
Equality_comparable<T,U>:检查是否存在具有给定类型左右操作数的==
Arithmetic<T>:检查类型是否为算术类型。
Floating_point<T>:检查类型是否为浮点类型。
Input_iterator<T>:检查类型是否支持输入迭代器必须支持的语法操作。
Same<T,U>:检查给定类型是否相同。
您可以通过特殊的concepts-lite build of GCC 尝试所有这些。
Beyond Concepts-Lite
现在我们开始讨论概念精简版提案之外的所有内容。这比未来本身更具未来感。 从现在开始,一切都可能发生很大变化。
公理
公理都是关于语义的。它们指定关系、不变量、复杂性保证和其他类似的东西。让我们看一个例子。
虽然Equality_comparable<T,U> 约束会告诉您有一个operator== 采用T 和U 类型,但它并没有告诉您该操作的含义。为此,我们将拥有公理Equivalence_relation。这个公理说当这两种类型的对象与operator== 给出true 进行比较时,这些对象是等价的。这似乎是多余的,但肯定不是。您可以轻松地定义一个 operator==,它的行为类似于 operator<。你这样做是邪恶的,但你可以。
另一个例子是Greater 公理。说T 类型的两个对象可以与> 和< 运算符进行比较,这很好,但是它们是什么意思? Greater 公理表示如果 x 大于 y,则 y 小于 x。提议的规范这样一个公理看起来像:
template<typename T>
axiom Greater(T x, T y) {
(x>y) == (y<x);
}
所以公理回答以下类型的问题:
- 这两个运算符之间有这种关系吗?
- 这样那样的类型的操作符是这个意思吗?
- 对该类型的操作是否具有这种复杂性?
- 该运算符的这个结果是否暗示这是真的?
也就是说,它们完全关注类型的语义和对这些类型的操作。这些东西不能静态检查。如果需要对此进行检查,则类型必须以某种方式声明它遵守这些语义。
示例
以下是一些常见的公理示例:
Equivalence_relation:如果两个对象比较==,它们是等价的。
Greater:无论何时x > y,然后是y < x。
Less_equal:无论何时x <= y,然后是!(y < x)。
Copy_equality:对于x 和y 类型为T:如果x == y,则通过复制构造T{x} == y 和仍然x == y 创建相同类型的新对象(即是,它是非破坏性的)。
概念
现在概念很容易定义;它们只是约束和公理的组合。它们提供了对类型的语法和语义的抽象要求。
例如,考虑以下Ordered 概念:
concept Ordered<Regular T> {
requires constraint Less<T>;
requires axiom Strict_total_order<less<T>, T>;
requires axiom Greater<T>;
requires axiom Less_equal<T>;
requires axiom Greater_equal<T>;
}
首先要注意,T 的模板类型要为Ordered,它还必须满足Regular 概念的要求。 Regular 概念是一个非常基本的要求,即类型行为良好——它可以被构造、销毁、复制和比较。
除了这些要求之外,Ordered 还要求 T 满足一个约束和四个公理:
- 约束:
Ordered 类型必须有 operator<。这是静态检查的,所以它必须存在。
- 公理:对于
T 类型的 x 和 y:
-
x < y 给出了严格的总排序。
- 当
x 大于y 时,y 小于x,反之亦然。
- 当
x小于等于y时,y不小于x,反之亦然。
- 当
x大于等于y时,y不大于x,反之亦然。
像这样结合约束和公理可以为您提供概念。它们定义了用于算法的抽象类型的句法和语义要求。当前的算法必须假设所使用的类型将支持某些操作并表达某些语义。有了概念,我们就能确保满足要求。
在the latest concepts design 中,编译器只会检查模板参数是否满足概念的句法要求。公理没有被检查。由于公理表示不可静态评估(或通常无法完全检查)的语义,因此类型的作者必须明确声明其类型满足概念的所有要求。这在以前的设计中被称为概念图,但后来被删除了。
示例
以下是一些概念示例:
Regular 类型是可构造的、可破坏的、可复制的,并且可以进行比较。
Ordered 类型支持operator<,并具有严格的总排序和其他排序语义。
-
Copyable 类型是可复制构造的,可破坏的,如果 x 等于 y 并且复制了 x,则副本也将与 y 进行比较。
Iterator 类型必须具有关联类型 value_type、reference、difference_type 和 iterator_category,它们本身必须满足某些概念。它们还必须支持 operator++ 并且可以取消引用。
概念之路
约束是实现 C++ 完整概念功能的第一步。它们是非常重要的一步,因为它们提供了类型的静态可执行要求,因此我们可以编写更清晰的模板函数和类。现在我们可以避免std::enable_if 及其元编程朋友的一些困难和丑陋。
但是,约束提案没有做很多事情:
它不提供概念定义语言。
约束不是概念图。用户不需要专门注释他们的类型以满足某些约束。它们使用简单的编译时语言功能进行静态检查。
模板的实现不受其模板参数的约束。也就是说,如果你的函数模板对一个它不应该做的约束类型的对象做了任何事情,编译器就无法诊断。一个功能齐全的概念提案将能够做到这一点。
约束提案经过专门设计,因此可以在其之上引入完整的概念提案。运气好的话,这种过渡应该是相当顺利的。概念小组正在寻求为 C++14 引入约束(或在不久之后的技术报告中),而完整的概念可能会在 C++17 前后的某个时候开始出现。