通过普通参数(此处是参数T)来表达模板参数之间必要的关系,很不幸没有强大的表现能力,导致我们添加模板参数,并且间接地(无法直接地)表达需求。例如,上面的例子不能说明把*first的结果作为参数传递给pred一定可行。其实,它说明的是Forward_iterator和Predicate共享了一个模板参数类型。为了处理这类问题,我们正在研究直接表达模板参数之间关系的可能性。例如:
template<Forward_iterator In, Predicate Pred> where (assignable<In::value_type, Pred::argument_type>) In find_if(In first, In last, Pred pred); |
这种方法也有自己的问题,例如它的要求(where子句)趋向于增加模板定义本身的复杂性,并且流行的迭代子(例如int*)并不拥有成员类型(例如value_type)。
概念的一种可能的表达方式是直接支持我们过去使用的约束类这种表达方式。例如,我们采用如下的方式来定义前面例子中使用的Forward_iterator:
template <class T> concept Forward_iterator { // 参数化的概念 Forward_iterator a; ++a; a++; // 可以增加 Forward_iterator b = a; b = a; // 可以复制 *b = *a; // 可以废除和复制结果 T x = *a; *a = x; // 可以认为结果是T类型的 }; |
或者
concept Forward_iterator { // 概念没有用参数表示 Forward_iterator a; ++a; a++; //可以增加 Forward_iterator b = a; b = a; //可以复制 *b = *a; // 可以废除和复制结果 }; |
参数化的概念定义可用于find_if的第一种声明,不带参数的用于第二种。它们表现了可替换使用的语言设计。我们在这个领域还会提供一些设计选择。但是,看看下面的情形:
int x = find_if(1,2,Less_than<int>(7)); |
这是不合格的,因为1和2是int型的,而int不支持*。如果我们使用参数化的概念设计,它也是不合格的,因为int不是一个能够与Forward_iterator<T>匹配的参数化类型。另一方面,看下面的例子:
void f(vector<int>& v, int* p, int n) { vector<int>::iterator q = find_if(v.begin(),v.end(),Less_than<int>(7)); int* q2 = find_if(p,p+n,Less_than<int>(7)); // … } |
很明显,我是在报告目前正在进行的工作,但是某种形式的概念成为C++0x的基石是很可能的。模板已经成为多数有效的(和高效的)C++编程样式的要素,但是它遭受很多困扰:大量的、无用的错误消息,缺乏基于模板参数重载模板的工具,分开编译很差。概念直接解决了所有这些问题,同时还没有基于方法的抽象基类的主要缺陷--通过虚拟函数调用的运行时解析的性能开销。重要的是,概念不依赖于显式声明的子类型层次,因此不需要逻辑冗余的层次关系,并且可以认为内建类型与类是平等的。
现在以概念和它与其它语言中相似的构造之间可能的关系为主题的论文很广泛。Matt Austern、Jaako J?rvi、Mich Marcus、Gabriel Dos Reis、Jeremy Siek、Alex Stepanov和我都活跃在这个设计问题的领域。
2、泛化的初始化器
C++的一个基本的想法是"对用户定义类型的支持如同内建类型一样好"。但是,看看下面的情形:
double vd[ ] = { 1.2, 2.3, 3.4, 4.5, 5.6 }; vector<double> v(vd, vd+5); |
我们可以直接使用初始化器列表来初始化该数组,然而对vector来说,我们做得最好(指坏处最少)的方式就是建立一个数组并用该数组来初始化vector。如果只有少量几个初始化器值,我甚至于可能使用下面的方式来避免明确地说明初始化器值的数量(在上面的例子中是 5):
vector<double> v; v.push_back(1.2); v.push_back(2.3); v.push_back(3.4); v.push_back(4.5); v.push_back(5.6); |
我认为谁也无法适当地调用上面的任何解决方案。为了得到最容易维护的代码,并且不让内建(并且是天生危险的)数组受到的"宠爱"比推荐的用户定义类型多vector,我们可以编写下面的代码:
vector<double> v = { 1.2, 2.3, 3.4, 4.5, 5.6 }; |
或者
vector<double> v ({ 1.2, 2.3, 3.4, 4.5, 5.6 }); |
由于参数传递是在初始化过程中定义的,因此对于带有vector的函数来说,这也是可行的:
void f(const vector<double>& r); // … f({ 1.2, 2.3, 3.4, 4.5, 5.6 }); |
我相信这种初始化器的泛化会成为C++0x的一部分。它将成为构造函数检查工作的一部分,因为人们发现的很多缺陷都似乎可以通过构造函数的泛化(例如转发构造函数、有保障的编译期构造函数、继承的构造函数)来解决。