概念和模板约束之间有什么区别?

104

我想知道C++全概念提案和模板约束(例如D语言中出现的约束条件或C++1y中的新概念轻量级提案)之间的语义差异是什么。

全面的概念能够做到什么,而模板约束不能做到什么?


2
这涉及到“约束提案”。 - chris
我在回想《4.8 设计概念》,但实际上并没有太多关于如何进一步约束概念的内容。如果有什么的话,现在通过谷歌搜索概念可能会在从提案中更好地理解约束后揭示一些容易发现的差异。 - chris
在我看来,概念可以提高可读性并提供更清晰的编程能力,正如亚历山大·斯特潘诺夫在《程序设计基础》中所要求的那样。轻量级提案只是为了向这个方向迈进,以减轻目前所需的奇怪enable_if类型约束的负担。越早实现泛型编程越好。 - dirvine
3个回答

141
以下信息已过时,需要根据最新的Concepts Lite草案进行更新。 约束条件提案的第3节对此有较为深入的探讨。 概念提案已经暂停了一段时间,希望在更短的时间内实现约束条件(即Concepts Lite),目前至少在C++14中达成某些目标。约束条件提案旨在作为后续概念定义的平稳过渡。约束条件是概念提案的一部分,并且是其定义中必要的构建块。
在《为C++设计概念库》中,Sutton和Stroustrup考虑了以下关系:

概念=约束条件+公理

简单概括它们的含义:
  1. 约束 - 对类型的静态可评估属性的谓词。纯语法要求,不是领域抽象。
  2. 公理 - 假设为真的类型的语义要求。不进行静态检查。
  3. 概念 - 算法对其参数的一般、抽象要求。以约束和公理为基础定义。

因此,如果您将公理(语义属性)添加到约束(语法属性)中,则会得到概念。


Concepts-Lite

概念轻量级提案仅提供了第一部分的限制条件,但这是实现完整概念所必需的重要步骤。

限制条件

限制条件都与语法有关。它们为我们提供了一种在编译时静态区分类型属性的方式,以便我们可以基于其语法属性限制用作模板参数的类型。在当前的限制条件提案中,它们使用命题演算的子集来表示,使用逻辑连接词如&&||

让我们看一个限制条件的示例:

template <typename Cont>
  requires Sortable<Cont>()
void sort(Cont& container);

在这里,我们定义了一个名为sort的函数模板。新添加的内容是requires clause。要求条款对此函数的模板参数施加了一些约束。特别地,该约束表示类型Cont必须是可排序的类型Sortable。一个好处是它可以以更简洁的形式编写:
template <Sortable Cont>
void sort(Cont& container);

现在,如果您尝试将任何未被视为“Sortable”的东西传递给此函数,您将得到一个漂亮的错误,立即告诉您推断出的T的类型不是“Sortable”类型。如果您在C++11中执行此操作,您将从“sort”函数内部抛出一些可怕的错误,对任何人都没有意义。
约束谓词非常类似于类型特性。它们获取一些模板参数类型并向您提供有关该类型的一些信息。 约束试图回答有关类型的以下问题:
1.是否重载了某种运算符?
2.这些类型能否用作此运算符的操作数?
3.该类型是否具有某种特性?
4.此常量表达式是否等于那个?(对于非类型模板参数)
5.此类型是否具有返回该类型的yada-yada函数?
6.此类型是否满足用作该类型的所有语法要求?
但是,约束并不意味着取代类型特性。相反,它们将齐头并进地发挥作用。 现在可以使用某些类型特征来定义概念,以及使用某些概念来定义类型特征。

示例

因此,限制条件的重要之处在于它们一点也不关心语义。一些好的示例包括:

  • Equality_comparable<T>:检查该类型是否具有使用该相同类型的两个操作数进行==比较。

  • Equality_comparable<T,U>:检查是否存在使用给定类型的左右操作数进行==比较。

  • Arithmetic<T>:检查该类型是否为算术类型。

  • Floating_point<T>:检查该类型是否为浮点类型。

  • Input_iterator<T>:检查该类型是否支持输入迭代器必须支持的语法操作。

  • Same<T,U>:检查给定类型是否相同。

您可以尝试使用特殊的concepts-lite build of GCC进行所有这些操作。


概念超越-Lite

现在我们进入了超越概念-Lite提案的一切。这比未来本身更具有未来感。 从这里开始的一切都很可能会发生很大的变化。

公理

公理与语义学有关。它们指定关系、不变量、复杂性保证和其他类似的事情。让我们看一个例子。

虽然Equality_comparable<T,U>约束将告诉您存在一个接受类型TUoperator==,但它并没有告诉您该操作的含义。因此,我们将拥有公理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);
}

所以公理回答以下类型的问题:

  1. 这两个运算符是否具有这种关系?
  2. 这种类型的运算符是否意味着这个?
  3. 对该类型的此操作是否具有此复杂度?
  4. 该运算符的结果是否意味着这是正确的?

也就是说,它们完全关注类型和对这些类型的操作的语义。这些事情不能静态检查。如果需要检查这一点,则某种方式必须声明类型遵守这些语义。

示例

以下是一些常见的公理示例:

  • 等价关系: 如果两个对象比较 ==,它们是等同的。

  • 大于: 每当 x > y,那么 y < x

  • 小于等于: 每当 x <= y,那么 !(y < x)

  • 复制相等性: 对于类型 Txy: 如果 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<。这是静态检查的,所以它必须存在。
  • 公理:对于类型Txy
    • x < y给出了一个严格的全序关系。
    • x大于y时,y小于x,反之亦然。
    • x小于或等于y时,y不小于x,反之亦然。
    • x大于或等于y时,y不大于x,反之亦然。

将约束和公理结合起来,就可以得到概念。它们定义了用于算法的抽象类型的语法和语义要求。目前算法必须假设使用的类型将支持某些操作并表达某些语义。通过概念,我们将能够确保满足要求。

最新的概念设计中,编译器仅检查模板参数是否满足概念的语法要求。公理未经检查。由于公理表示静态无法评估(或往往完全无法检查)的语义,类型的作者必须明确声明其类型满足概念的所有要求。这在以前的设计中被称为概念映射,但现已被删除。

示例

以下是一些概念的示例:
  • Regular 类型是可构造的、可销毁的、可复制的,并且可以进行比较。

  • Ordered 类型支持 operator<,并具有严格的总排序和其他排序语义。

  • Copyable 类型是可复制构造的、可销毁的,如果 x 等于 y 并且对 x 进行复制,则复制也将与 y 相等。

  • Iterator 类型必须具有关联类型 value_typereferencedifference_typeiterator_category,这些类型本身必须满足某些概念。它们还必须支持 operator++ 并且可解引用。

通向概念的道路

约束条件是 C++ 具备完整概念特性的第一步。它们是非常重要的一步,因为它们提供了类型的静态强制要求,使我们能够编写更清晰的模板函数和类。现在我们可以避免一些困难和丑陋的 std::enable_if 及其元编程朋友的问题。

然而,约束提案有一些限制:
  1. 它不提供概念定义语言。

  2. 约束不是概念映射。用户不需要将其类型明确注释为满足某些约束。它们是使用简单的编译时语言特性进行静态检查。

  3. 模板的实现不受其模板参数的约束限制。也就是说,如果您的函数模板对受限类型的对象执行任何不应该执行的操作,则编译器无法诊断出来。完整的概念提案可以解决这个问题。

约束提案是专门设计的,以便在其基础上引入完整的概念提案。希望这个转换过程会比较顺利。概念小组正在寻求在C++14中引入约束(或在技术报告中很快引入),而完整的概念可能会在C++17左右开始出现。


5
需要注意的是,Concepts Lite不会检查约束是否符合模板实现本身。因此,你可以声称可以使用任何DefaultConstructable,但编译器不会阻止你意外使用拷贝构造函数。一个更完整的Concepts提案将会解决这个问题。 - Nicol Bolas
24
那是第一版草稿?! - Nicol Bolas
2
@sftrabbit,非常好的回答。但我有一个问题:编译器如何检查类型是否满足概念的语义要求? - Rayniery
1
无法检查公理的原因是什么? - ScarletAmaranth
4
因为目的是在有限的时间内自动证明一个定理。其中存在两个障碍:1.在理论上,证明任何定理都非常困难,并且使用当前技术不可能。2.你不能证明一个公理,除非这个证明基于其他公理。(在这种情况下,从数学上讲,“不是公理”)这些公理旨在告诉编译器“当然,如果你认为它有用,你可以反转比较......”,或者“是的,在交集中使用EmptySet总是会给出相同的结果”。 - Laurent LA RIZZA
显示剩余9条评论

22

4

我的意见:

  1. Concepts-lite提案不是用来对模板实现进行“类型检查”的。即,概念轻量级将确保(理论上)接口在模板实例化位置的兼容性。引用论文:“概念轻量级是C++的一个扩展,允许使用谓词来约束模板参数”。就是这样。它并没有说模板主体将被检查(隔离)与谓词相比。如果我记得正确,这可能意味着当你谈论概念-重度提议时,并没有一流的原型概念。原型概念是指在实现模板时提供满足标准的类型,既不多也不少。

  2. Concepts-lite使用了由编译器支持的具有一些语法技巧的虚荣constexpr函数。查找规则没有改变。

  3. 程序员不需要编写概念映射。

  4. 最后再次引用:“约束提案并不直接涉及语义的规范或使用;它仅针对语法检查。”这意味着公理不在范围内(到目前为止)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接