简介
自Boost.MPL
首发以来,它通过发布大量的模板接口为C++程序员
进行元编程提供了便利,这个突破极大地促进了C++模板元编程
的主流化,如今模板元编程已经深植于各种项目之中了。近期以来,C++11
和C++14
对语言带来了许多重大变化,其中一些使元编程更加容易,其它一些也大大拓宽了库的设计空间。由此,一些问题自然而来:是否仍然希望有元编程的抽象?如果是,有哪些?在考察了不同选择,如MPL11
之后,最终答案是Hana
库。对Hana
的关键洞察是,类型和值的计算是一体两面的。通过统一这两个概念,元编程变得更为容易,新的令人兴奋的可能出现在我们面前了。
C++
计算分类(四个象限)
但是要真正了解Hana
是什么,有必要理解C++
中的不同类型的计算。 先不管可能的更细粒度的区分,我们将把注意力集中在四种不同类型的计算上。首先,是运行时计算
,这是我们通常在C++
中使用的计算方式。在运行时世界中,我们有运行时容器,运行时函数和运行时算法:
auto f=[](int i)->std::string {
return std::to_string(i * i);
};
std::vector<int> ints{1,2,3,4};
std::vector<std::string> strings;
std::transform(ints.begin(),ints.end(),std::back_inserter(strings),f);
assert((strings==std::vector<std::string>{"1","4","9","16"}));
这个象限中的计算,通常以C++标准库
为工具,C++标准库
提供运行时可重用的算法和容器。自C++11
以来,第二种计算成为可能:constexpr计算
。这种计算中,我们用constexpr
容器,constexpr
函数及constexpr
算法:
constexpr int factorial(int n){
return n==0 ? 1 : n * factorial(n-1);
}
template<typename T,std::size_t N,typename F>
constexpr std::array<std::result_of_t<F(T)>,N> transform(std::array<T,N> arr,F f){
// ...
}
constexpr std::array<int,4> ints{{1,2,3,4}};
constexpr std::array<int,4> facts=transform(ints,factorial);
static_assert(facts==std::array<int,4>{{1,2,6,24}},"");
注意: 若使以上代码可执行,需要确保
std::array
的operator==
操作符标记为constexpr
,在C++14
下,这不是问题。
基本上,constexpr计算
与运行时计算
的不同之处在于它足够简单,可以被编译器解析执行。一般来说,任何不对编译器的求值程序过于不友好的函数(像抛出异常或者分配内存等),都可以标记为constexpr
,而无需作出修改。constexpr计算
与运行时计算
类似,除了constexpr计算
更受限制,并需要获得编译时执行的能力之外。不幸的是,没有常用于constexpr计算
的工具集,即没有广泛采用的用于constexpr
编程的标准库。也许,对constexpr
编程感兴趣的人可以去了解一下Sprout库。
第三种计算是异构计算
。异构计算
不同于普通的计算方式,因为异构计算
不使用存储同类对象(所有对象具有相同类型)的容器,而是使用异构容器。异构容器可以保存类型不同的对象。 此外,在这个计算象限中的函数是异构函数,这是一种讨论模板函数的复杂方式。类似地,我们由异构算法操作异构容器和函数:
auto to_string=[](auto t){
std::stringstream ss;
ss<<t;
return ss.str();
};
fusion::vector<int,std::string,float> seq{1,"abc",3.4f};
fusion::vector<std::string,std::string,std::string> strings=fusion::transform(seq,to_string);
assert(strings==funsion::make_vector("1"s,"abc"s,"3.4"s));
如果你觉得操作异构容器很奇怪的话,不妨把它想像成操作std::tuple
。在C++03
的世界中,用于进行此计算的库是Boost.Fusion,它提供了一些操作异构数据的结构和算法的集合。我们将考察的第四个计算象限的计算是类型计算
。在这个象限中,我们有类型容器,类型函数(通常称为元函数)和类型算法。在这里,针对任意类型进行操作:容器存储的是类型、元函数接受类型作为参数返回的结果也是类型。
template<typename T>
struct add_const_pointer{
using type=T const*;
};
using types=mpl::vector<int,char,float,void>;
using pointers=mpl::transform<types,add_const_pointer<mpl::_1>>::type;
static_assert(mpl::equal<pointers,
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
提供了一种将任何类型计算
转换为其等效的异构计算
的方法。这就允许异构计算
的全部机制重用于类型计算
,而且没有任何代码重复。当然,这种统一的最大优点将是用户可见的。