Concepts 没有制定 C++0x 标准,但 Boost 仍然提供Boost Concept Check Library (BCCL)。我猜 BCCL 并没有涵盖 C++0x 标准中的所有内容。BCCL 和提议的 C++0x 解决方案有什么区别?
3 回答
检查模板定义
这些手动解决方案的概念的一个很大区别是概念允许对模板的定义进行类型检查,而无需做任何特殊的事情。概念检查库只允许对它的 *use* 进行类型检查(除非您手动编写测试实例化类型或为标准情况使用提供的类型,见下文)。例子:template<typename InputIterator>
int distance(InputIterator a, InputIterator b)
{ return b - a; }
您现在可以在该模板上添加概念检查和特征,但在编写该模板后您将永远不会出错 - 因为标准允许编译器延迟编译模板直到实例化。为了检查,您必须编写“原型”类,其中包含接口所需的那些操作,然后人工实例化它们。
阅读 BCCL 的文档,我发现它已经包含了常见的原型,例如“默认可构造”。但是,如果您编写自己的概念,则还必须提供自己的原型,这并不容易(您必须准确找到类型必须提供的最小功能)。例如,如果您的原型包含 a operator-
,那么使用该(不正确的)原型测试您的模板将成功,尽管这些概念不需要这样的运算符。
被拒绝的概念提案会根据指定和隐含的要求自动为您创建原型(例如T*
,参数中使用的指针类型将暗示 PointeeType 要求T
)。您不必关心这些东西 - 当然,当您的模板定义包含类型错误时除外。
检查语义要求
考虑这段代码,使用假设的概念检查
template<ForwardIterator I>
void f(I a, I b) {
// loop two times!
loopOverAToB(a, b);
loopOverAToB(a, b);
}
BCCL 手册说没有检查语义要求。仅检查语法要求和类型。考虑一个前向迭代器:存在可以在多遍算法中使用它的语义要求。仅语法检查将无法测试此要求(想想如果流迭代器意外通过该检查会发生什么!)
在被拒绝的提案中,您必须auto
在概念定义前面显式放置,以使编译器在语法检查后标记成功。如果auto
未指定,则类型必须明确定义概念图以表示它支持该概念。因此,流迭代器永远不会被用来通过 ForwardIterator 检查。
语法重映射
这是另一个特点。模板如template<InputIterator I>
requires OutputStreamable<I::value_type>
void f(I a, I b) {
while(a != b) std::cout << *a++ << " ";
}
可以像下面这样使用,如果用户将提供一个概念图,教编译器如何取消引用整数,以及整数如何满足 InputIterator 概念。
f(1, 10);
这是基于语言的解决方案的好处,我相信 BCCL 永远无法解决。
基于概念的重载
在快速阅读 BCCL 时,我也找不到任何允许这种情况发生的事情。概念匹配失败似乎会导致硬编译错误。被拒绝的提案允许以下内容:template<ForwardIterator I>
I::difference_type distance(I a, I b) {
I::difference_type d = 0; while(a != b) ++a, ++d;
return d;
}
template<RandomAccessIterator I>
I::difference_type distance(I a, I b) {
return b - a;
}
如果一个类型可以与两个模板一起使用,那么将使用第二个模板,因为它更专业:RandomAccessIterator
提炼ForwardIterator
概念。
C++0x 概念特性将是一个核心语言特性,其整个过程将由编译器完成。
Boost 概念检查库是几乎相同的功能,但使用 C++ 和宏作为库编写,以模拟某些功能。它不能完成最终语言功能中所需的所有功能(取决于最终功能定义),但为模板类型检查(和其他编译时检查)提供了一些等效的解决方案。
正如建议的那样,由于 C++0x 概念是一种语言特性,它将允许提供更优雅的语义,并允许编译器使用程序当前不可用的信息,从而允许在编译时出现更详细或智能的错误(作为概念第一目的是允许模板中的抽象类型检查)。
免责声明:即使我已经安装了 Boost,我也无法在过去 30 分钟内成功使用 BCCL。根据 Boost 1.37 的 BCCL 文档,您在下面看到的示例看起来不错,但没有工作。我想这算作劣势。
使用 BCCL,您只能获得静态断言之类的东西,而核心语言概念功能提供了完整的模块化类型检查,并且能够防止某些函数模板参与重载决议。使用本机概念,编译器可以立即检查受约束模板的主体,而 BCCL 不会使编译器在这方面检查任何内容。您必须使用“arche type”参数手动实例化您的模板,以查看模板是否使用了任何不可用的操作(例如,前向迭代器上的 operator--)。
至于重载决议,这里有一个例子:
template<typename Iter>
void foo(Iter,Iter) {
BOOST_CONCEPT_ASSERT((RandomAccessIterator<Iter>));
}
void foo(long, int);
int main() {
foo(2,3); // compile-time error
}
模板是更好的匹配,因为非模板需要从 int 转换为 long。但实例化失败,因为 int 不是迭代器。您会收到一条很好的错误消息来解释它,但这并不是很令人满意,是吗?使用本机概念,您可以编写
template<typename Iter>
requires RandomAccessIterator<Iter>
void foo(Iter,Iter) {}
void foo(long, int);
int main() {
foo(2,3); // OK, picks non-template foo
}
在这里,函数模板将不参与重载决策,因为不满足 T=int 的要求。伟大的!
我们仍然可以使用 SFINAE 技巧来约束函数模板。C++0x 将 SFINAE 扩展到表达式,以及我们可以编写的函数模板的 decltype 和默认模板参数
template<typename T> T&& make();
template<typename Iter, class = decltype( *make<Iter>() )>
void foo(Iter,Iter) {}
void foo(long, int);
int main() {
foo(2,3); // OK, picks non-template foo
}
在这种情况下,模板参数推导将静默失败,因为编译器不知道表达式 ∗make<Iter>() 应该是什么类型(我们不能取消引用 int)。通过一些模板元编程和一些宏,我们可以非常接近以一种可读的方式对模板参数施加任意结构约束。假设 REQUIRES 和 RandomAccessIterator 有适当的定义,它可能看起来像这样:
template <typename Iter
REQUIRES( RandomAccessIterator<Iter> )
>
void foo(Iter,Iter) {}
HTH, 小号