我们的最终目标是在模板实例化的位置上捕捉并指出违反概念的错误。正如D&E[2]所指出的,可以通过在函数模板上测试所有要求来捕获错误。实际上如何测试要求(特定的有效表达式)是一件微妙的事情,因为我们希望代码被编译 --- 但不执行。我们的方法是在一个单独的函数中测试这些要求,这个函数被赋值给一个函数指针。这种情形下,编译器将实例化这个函数,但不会真正调用它。此外,一个优化的编译器还会将指针赋值作为"死代码"而删掉(虽然这个赋值操作在运行期的代价是非常微不足道的)。你可能会想编译器会不会一开始就跳过语义分析和约束函数的编译,从而使我们的函数指针技术不起作用。这是不可能的,由于删除无用代码和函数是编译器最后一步的工作。我们已经在 GNU C++, Microsoft Visual C++, 和多个基于 EDG 的编译器(KAI C++, SGI MIPSpro)上验证了函数指针的技术。以下代码示范了如何把这种技术应用于 std::stable_sort() 函数:
template <class RandomAccessIterator>
void stable_sort_constraints(RandomAccessIterator i)
{
typename std::iterator_traits<RandomAccessIterator>
::difference_type n;
i += n; // 检查 RandomAccessIterator 的要求
...
}
template <class RandomAccessIterator>
void stable_sort(RandomAccessIterator first, RandomAccessIterator last)
{
typedef void (*fptr_type)(RandomAccessIterator);
fptr_type x = &stable_sort_constraints;
...
}
通常会有一大堆要求需要检查,对于程序库的作者来说,为每一个公有函数编写象 stable_sort_constraints() 一样的约束函数是一件很烦琐的事情。为此,我们按照相应的概念的定义,将一组有效表达式集合起来。对于每个概念,定义一个概念检查类模板,以要检查的类型作为模板参数。这些类含有一个 contraints() 成员函数,负责测试概念所要求的所有有效表达式。约束函数中使用到的对象,如 n 和 i, 则声明为概念检查类的数据成员。
template <class Iter>
struct RandomAccessIteratorConcept
{
void constraints()
{
i += n;
...
}
typename std::iterator_traits<RandomAccessIterator>
::difference_type n;
Iter i;
...
};
我们还可以用这种函数指针机制来引起约束函数的实例化,只不过现在换成了成员函数指针。为了库更加易于执行概念检查,我们将成员函数指针机制包装在一个名为 function_requires() 的函数中。以下代码片断示范了如何使用 function_requires() 来确认迭代器是一个 RandomAccessIterator随机迭代器。
template <class Iter>
void stable_sort(Iter first, Iter last)
{
function_requires< RandomAccessIteratorConcept<Iter> >();
...
}
function_requires() 的定义如下。其中 Concept 为已实例化的概念检查类。我们将约束成员函数的地址赋值给函数指针 x, 这将引起约束函数的实例化,并检查概念的有效表达式。然后我们通过将 x 赋值给 x 来避免编译器的无用变量警告,并将这些都包装在一个 do-while 循环中以防止名字冲突。
template <class Concept>
void function_requires()
{
void (Concept::*x)() = BOOST_FPTR Concept::constraints;
ignore_unused_variable_warning(x);
}