简介

Boost.MPL首发以来,它通过发布大量的模板接口为C++程序员进行元编程提供了便利,这个突破极大地促进了C++模板元编程的主流化,如今模板元编程已经深植于各种项目之中了。近期以来,C++11C++14对语言带来了许多重大变化,其中一些使元编程更加容易,其它一些也大大拓宽了库的设计空间。由此,一些问题自然而来:是否仍然希望有元编程的抽象?如果是,有哪些?在考察了不同选择,如MPL11之后,最终答案是Hana库。对Hana的关键洞察是,类型和值的计算是一体两面的。通过统一这两个概念,元编程变得更为容易,新的令人兴奋的可能出现在我们面前了。

C++计算分类(四个象限)

但是要真正了解Hana是什么,有必要理解C++中的不同类型的计算。 先不管可能的更细粒度的区分,我们将把注意力集中在四种不同类型的计算上。首先,是运行时计算,这是我们通常在C++中使用的计算方式。在运行时世界中,我们有运行时容器,运行时函数和运行时算法:

  1. auto f=[](int i)->std::string {
  2. return std::to_string(i * i);
  3. };
  4. std::vector<int> ints{1,2,3,4};
  5. std::vector<std::string> strings;
  6. std::transform(ints.begin(),ints.end(),std::back_inserter(strings),f);
  7. assert((strings==std::vector<std::string>{"1","4","9","16"}));

这个象限中的计算,通常以C++标准库为工具,C++标准库提供运行时可重用的算法和容器。自C++11以来,第二种计算成为可能:constexpr计算。这种计算中,我们用constexpr容器,constexpr函数及constexpr算法:

  1. constexpr int factorial(int n){
  2. return n==0 ? 1 : n * factorial(n-1);
  3. }
  4. template<typename T,std::size_t N,typename F>
  5. constexpr std::array<std::result_of_t<F(T)>,N> transform(std::array<T,N> arr,F f){
  6. // ...
  7. }
  8. constexpr std::array<int,4> ints{{1,2,3,4}};
  9. constexpr std::array<int,4> facts=transform(ints,factorial);
  10. static_assert(facts==std::array<int,4>{{1,2,6,24}},"");

注意: 若使以上代码可执行,需要确保std::arrayoperator==操作符标记为constexpr,在C++14下,这不是问题。

基本上,constexpr计算运行时计算的不同之处在于它足够简单,可以被编译器解析执行。一般来说,任何不对编译器的求值程序过于不友好的函数(像抛出异常或者分配内存等),都可以标记为constexpr,而无需作出修改。constexpr计算运行时计算类似,除了constexpr计算更受限制,并需要获得编译时执行的能力之外。不幸的是,没有常用于constexpr计算的工具集,即没有广泛采用的用于constexpr编程的标准库。也许,对constexpr编程感兴趣的人可以去了解一下Sprout库。

第三种计算是异构计算异构计算不同于普通的计算方式,因为异构计算不使用存储同类对象(所有对象具有相同类型)的容器,而是使用异构容器。异构容器可以保存类型不同的对象。 此外,在这个计算象限中的函数是异构函数,这是一种讨论模板函数的复杂方式。类似地,我们由异构算法操作异构容器和函数:

  1. auto to_string=[](auto t){
  2. std::stringstream ss;
  3. ss<<t;
  4. return ss.str();
  5. };
  6. fusion::vector<int,std::string,float> seq{1,"abc",3.4f};
  7. fusion::vector<std::string,std::string,std::string> strings=fusion::transform(seq,to_string);
  8. assert(strings==funsion::make_vector("1"s,"abc"s,"3.4"s));

如果你觉得操作异构容器很奇怪的话,不妨把它想像成操作std::tuple。在C++03的世界中,用于进行此计算的库是Boost.Fusion,它提供了一些操作异构数据的结构和算法的集合。我们将考察的第四个计算象限的计算是类型计算。在这个象限中,我们有类型容器,类型函数(通常称为元函数)和类型算法。在这里,针对任意类型进行操作:容器存储的是类型、元函数接受类型作为参数返回的结果也是类型。

  1. template<typename T>
  2. struct add_const_pointer{
  3. using type=T const*;
  4. };
  5. using types=mpl::vector<int,char,float,void>;
  6. using pointers=mpl::transform<types,add_const_pointer<mpl::_1>>::type;
  7. static_assert(mpl::equal<pointers,
  8. mpl::vecotr<int const*,char const*,float const*,void const*>>::value,"");

类型计算的领域已经被相当广泛地探索了,并且C++03类型计算的事实解决方案是一个名为Boost.MPL的库,它提供类型容器和算法。对于低级类型转换,C++11中也可以使用<type_traits>标准头文件提供的元函数。

Hana库是干什么的

以上所有计算都做地很好了,那么,Hana库又是干什么的?现在我们已经知道了C++的各种计算类型,可以简单地回答这个问题了。Hana的目的是合并第三和第四象限的计算,具体来说,Hana经过长期构建证明,异构计算类型计算更强大。我们可以通过等效的异构计算来表达任何类型计算。这种构造在两个步骤中完成。首先,Hana是一个功能齐全的异构算法和容器库,有点像现代化的Boost.Fusion。其次,Hana提供了一种将任何类型计算转换为其等效的异构计算的方法。这就允许异构计算的全部机制重用于类型计算,而且没有任何代码重复。当然,这种统一的最大优点将是用户可见的。