6.1 概念的早期历史
1980 年,我猜想泛型编程可以通过 C 风格的宏来有效支持 [Stroustrup 1982]。然而我完全错了。一些有用的简单泛型抽象能通过这种方法表达,1980 年代的标准化之前的 C++ 通过 <generic.h>
中的一组宏为泛型编程提供支持,但宏在大型项目或广泛使用的情况下无法有效管理。尽管泛型编程在当时流行的面向对象的思想
中并没有一席之地,我确实发现了一个问题,需要解决它才能达到我对带类的 C
的目标。
大约在 1987 年,我尝试设计具有合适接口的模板 [Stroustrup 1994],但失败了。我需要三个基本属性来支持泛型编程:
- 全面的通用性/表现力——我明确不希望这些功能只能表达我想到的东西。
- 与手工编码相比,零额外开销——例如,我想构建一个能够与 C 语言的数组在时间和空间性能方面相当的 vector。
- 规范化的接口——我希望类型检查和重载的功能与已有的非泛型的代码相类似。
那时候没人知道如何做到全部三个方面,因此 C++ 所做到的是:
- 图灵完备性 [Veldhuizen 2003]
- 优于手动编码的性能
- 糟糕的接口(基本上是编译期鸭子类型),但仍然做到了静态类型安全
前两个属性使模板大获成功。
由于缺乏规范化的接口,我们在这些年里看到了极其糟糕的错误信息,到了 C++17 还仍然是这样。缺乏规范化的接口这一问题,让我和很多其他人困扰很多年。它让我非常困扰的原因是,模板无法满足 C++ 的根本的设计标准 [Stroustrup 1994]。我们(显然)需要一种简单的、没有运行期开销的方法来指定模板对其模板参数的要求。
多年以来,一些人(包括我)相信模板参数的要求可以在 C++ 本身中充分指定。1994 年,我在 [Stroustrup 1994] 中记录了基本的想法,并在我的网站上发布了示例 [Stroustrup 2004–2020]。自 2006 年以来,基于 Jeremy Siek 的作品,Boost 提供了该想法的一个变体,Boost 概念检查库 [Siek and Lumsdaine 2000–2007]。不知何故,它并未像我所希望的那样广泛流行。我怀疑原因是它不够通用、不够优雅(Boost 感到有义务使用宏隐藏细节),并且在标准中不受支持。许多人将其视为一种奇技淫巧。
为 C++ 定义的概念可以追溯到 Alex Stepanov 在泛型编程上的工作,这是 1970 年代末开始的,一开始用的名称是代数结构
[Kapur et al. 1981]。注意,那差不多比 Haskell 的类型类设计 [Wadler and Blott 1989] 要早十年,比我尝试解决 C++ 的类似问题要早 5 年。对于这种需求,Alex Stepanov 早在 1990 年代末期的讲座中就使用了概念
这一名称,并记录在 [Dehnert and Stepanov 2000]。我之所以提到这些,是因为许多人猜测概念是从 Haskell 类型类派生而来但被错误命名了。Alex 使用概念
这一名称是因为概念此处用来代表应用领域(如代数)中的基本概念。
目前把概念当作依靠使用模式来描述操作的类型谓词,这起源于二十一世纪初期 Bjarne Stroustrup 和 Gabriel Dos Reis 的工作,并记录在 [Dos Reis and Stroustrup 2005b, 2006; Stroustrup and Dos Reis 2003b, 2005a] 之中。这种方法在 1994 年的《设计和演化》[Stroustrup 1994] 一书也被提及,但是我不记得我第一次进行尝试的时间了。将概念建立于使用模式的主要原因是为了以一种简单而通用的方式处理隐式转换和重载。我们了解 Haskell 类型类,但它们对当前的 C++ 设计影响不大,因为我们认为它们太不灵活了。
精确指定并检查一个模板对于参数的要求曾经是 C++0x 的最出彩之处,会对泛型编程提供关键支持。可是,它最终甚至没能进入 C++17。
Bjarne Stroustrup 和 Gabriel Dos Reis 在 2003 年发表的论文 [Stroustrup 2003; Stroustrup and Dos Reis 2003a,b] 明确指出,概念是简化泛型编程的宏伟计划的一部分。例如,一个 concept
可以被定义为一组使用模式的约束,就是说,作为对某种类型有效的语言构件 [Stroustrup and Dos Reis 2003b]:
concept Value_type {
constraints(Value_type a)
{
Value_type b = a; // 拷贝初始化
a = b; // 拷贝赋值
Value_type v[] = {a}; // 不是引用
}
};
template<Value_type V>
void swap(V& a, V& b); // swap() 的参数必须是值类型
但是,当时的语法和语义还很不成熟。我们主要是试图建立设计标准 [Stroustrup and Dos Reis 2003a]。从现代(2018 年)的角度来看,[Stroustrup 2003; Stroustrup and Dos Reis 2003a,b] 有很多缺陷。但是,它们为概念提供了设计约束,并在以下方面提出了建议:
- 概念——用于指定对模板参数要求的编译期谓词。
- 根据使用模式来指定原始约束——以处理重载和隐式类型转换。
- 多参数概念——例如
Mergeable<In1,In2,Out>
。 - 类型和值概念——也就是说,概念既可以将值也可以将类型当作参数,例如
Buffer<unsigned char,128>
。 - 模板的
类型的类型
简略写法—例如template<Iterator Iter> …
。 模板定义的简化写法
——例如void f(Comparable&);
使泛型编程更接近于普通编程
。auto
作为函数参数和返回值中约束最少的类型。- 统一函数调用(§8.8.3)——减少泛型编程与面向对象编程之间的风格差异问题(例如
x.f(y)
、f(x,y)
和x+y
)。
奇怪的是,我们没有建议通用的 requires
子句(§6.2.2)。这些都是后面所有概念变体的一部分。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论