闲话小叙:C++11的发布让C++迎来了2.0时代,引入了很多有用的新特性。然而这是一个隔了10多年才发布的2.0版本,其他的语言都发布N代了,所以C++11的一些新特性对别的语言来说已经是旧特性了,一点都不奇怪。比如auto、regex、lambda、tuple等等。幸而C++标准委员会已经决定要以三年一小改六年一大改的速度更新了,所以下一个时代的C++就是C++17,里面会有什么令人期待的让C++重新领先的理念呢。也许概念检查(Concept Check)就是其中一个吧。

概念检查是C++模板编程中很有用的工具,可以把错误从运行期提前到编译期。Boost以库的方式提供了这个功能,比如我要写一个针对容器的函数,就要这样用:

template<class Con>
void print(Con C)
{
    BOOST_CONCEPT_ASSERT((Container<Con>));
    .....
}

当传入的类型不符合std容器的定义时,就会编译报错。研究一下它的源代码,发现是这样的:

 1 BOOST_concept(Container,(C))
 2     : Assignable<C>
 3   {
 4     typedef typename C::value_type value_type;
 5     typedef typename C::difference_type difference_type;
 6     typedef typename C::size_type size_type;
 7     typedef typename C::const_reference const_reference;
 8     typedef typename C::const_pointer const_pointer;
 9     typedef typename C::const_iterator const_iterator;
10 
11       BOOST_CONCEPT_USAGE(Container)
12       {
13           BOOST_CONCEPT_ASSERT((InputIterator<const_iterator>));
14           const_constraints(c);
15       }
16 
17    private:
18       void const_constraints(const C& cc) {
19           i = cc.begin();
20           i = cc.end();
21           n = cc.size();
22           n = cc.max_size();
23           b = cc.empty();
24       }
25       C c;
26       bool b;
27       const_iterator i;
28       size_type n;
29   };

其本质就是检查一下传入的类型有没有定义value_type、size_type等类型,然后调用一下begin()、end()等函数。

原理并不复杂,然而一旦出错,那错误信息简直是乱七八遭。因此C++17的提案里虽然有概念检查,但并不是基于Boost的方式,它重新设计,然后以语法的方式实现了。同样的理念,以语法的方式实现是坠吼的了。

新的概念检查可以见这里:http://en.cppreference.com/w/cpp/language/constraints。

通过它,就可以写出下面这样的代码,非常酷。第一眼看到时简直不敢相信这还是C++!

auto f(Container) -> Sortable; 

Sortable x = f(y); 
 
void g1(const EqualityComparable*, Incrementable&);
// 隐式模板,等价于:
// template<EqualityComparable T, Incrementable U>
// void g1(const T*, U&);

 

程序员可以定义一些概念,比如(Sortable、Numeric),然后把它们像"typename"一样用。不同的是typename指代任何类型,而这些概念只代表符合某种要求的类型,当不符合时会编译不通过。而且诊断信息会更明确,比如:

std::list<int> l = {3,-1,10};
std::sort(l.begin(), l.end()); 
//Typical compiler diagnostic without concepts:
//  invalid operands to binary expression ('std::_List_iterator<int>' and
//  'std::_List_iterator<int>')
//                           std::__lg(__last - __first) * 2);
//                                     ~~~~~~ ^ ~~~~~~~
// ... 50 lines of output ...
//
//Typical compiler diagnostic with concepts:
//  error: cannot call std::sort with std::_List_iterator<int>
//  note:  concept RandomAccessIterator<std::_List_iterator<int>> was not satisfied

使用concepts后编译器会直接告诉你std::list的Iterator不符合随机迭代器的要求。

我看到后兴奋地与果粉讨论,他问:“如果传给Numeric的是自定义类呢?”。我一愣,说:“可以让自定义类定义类型转换运行符operator double()啊!”。这才意识到概念检查毕竟还没有发布,还是有一些需要完善的地方。仔细一想,如果能在定义类的时候也用上概念检查就好了,比如:

template<class T>
class my_num : Comparable,Numeric
{
//    实现operator= 与 operator<
   // 实现operator double
}

这看起来很像接口,没错,就是这样,也许C++应该引入接口了。而且事实上C++标准里已经有东西很像接口了,比如enable_shared_from_this。