模板元编程(编译期计算)初探
编译期求Fib
什么是编译期计算:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| template<int n> struct fib { static_assert(n>=0, "Argument must be non-negative"); static const int value = fib<n-2>::value + fib<n-1>::value; };
template<> struct fib<0> { static const int value = 0; };
template<> struct fib<1> { static const int value = 1; };
int main() { cout<<fib<5>::value<<endl; return 0; }
|
观察汇编结果可以发现:在输出fib<5>::value
时汇编直接把5进行赋值,说明编译期就已经计算出结果,而不是运行期进行的计算。
编译期If
编译期计算主要是把计算转化成类型推导。
比如我们可以编译器完成条件语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| template<bool cond, typename Then, typename Else> struct If;
template<typename Then, typename Else> struct If<true, Then, Else> { typedef Then Action; };
template<typename Then, typename Else> struct If<false, Then, Else> { typedef Else Action; };
template<int x, int y> struct _add { static const int result = x + y; };
template<int x, int y> struct _sub { static const int result = x - y; };
template<bool cond, int x, int y> struct Operation { static const int ans = If<cond, _add<x, y>, _sub<x, y>>::Action::result; };
int main() {
int t = Operation<2*2*2==8, 2, 5>::ans; cout<<t<<endl; return 0; }
|
对真假分别进行特化,也是编译期计算的,如果真则执行_add
操作,否则执行_sub
操作。
编译期While
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| template<bool cond, typename Body> struct WhileLoop;
template<typename Body> struct WhileLoop<true, Body> { typedef typename WhileLoop<Body::cond_value, typename Body::next_type>::type type; };
template<typename Body> struct WhileLoop<false, Body> { typedef typename Body::res_type type; };
template<typename Body> struct While { typedef typename WhileLoop<Body::cond_value, Body>::type type; };
template<typename T, T v> struct integral_constant_selfmade { static const T value = v; typedef T value_type; typedef integral_constant_selfmade type; };
template<int result, int n> struct SumLoop { static const bool cond_value = (n != 0); static const int res_value = result; typedef integral_constant_selfmade<int, res_value> res_type; typedef SumLoop<result+n, n-1> next_type; };
template<int n> struct Sum { typedef SumLoop<0, n> type; };
int main() {
cout<<While<Sum<100>::type>::type::value<<endl; return 0; }
|
这是一个1加到100的编译期计算方法,没啥好说的,多看看理解一下。
这部分唯一需要提一下的是一个模板语法,如果你之前模板用得不多的话,还有一个需要了解的细节:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| template<typename Body> struct WhileLoop<true, Body> { typedef typename WhileLoop<Body::cond_value, typename Body::next_type>::type type; };
template<typename Body> struct WhileLoop<false, Body> { typedef typename Body::res_type type; };
template<typename Body> struct While { typedef typename WhileLoop<Body::cond_value, Body>::type type; };
|
上面三句话在typedef后紧接着使用了typename, 因为用::
取一个成员类型、并且 ::
左边有模板参数的话,得额外加上 typename 关键字来标明结果是一个类型。上面循环模板的定义里就出现了多次这样的语法。MSVC 在这方面往往比较宽松,不写typename 也不会报错,但这是不符合 C++ 标准的用法。
编译器类型推导
为了方便在值和类型之间做切换,一些工具类是必不可少的,类似的设计比如 STL中的iterator_traits
可以用来获取迭代器的type。
其实类似的工具类在C++中很多,比如上面While编译期里面我们自己手写了一个integral_constant_selfmade
其实在C++中有这样的东西叫做integral_constant
。
为了方便使用,type_traits.h
中还实现了false_type
和true_type
可以发现他们就是模板integral_constant
的一种实现而已。
他的应用常在函数重载中用到:
| template<typename T> class Container { public: ... Some implementation ... static void destroy(T* ptr) { _destroy(ptr, is_trivially_destructible<T>()); } private: static void _destroy(T* ptr, true_type){} static void _destroy(T* ptr, false_type){} };
|
类似上面,很多容器类里会有一个 destroy
函数,通过指针来析构某个对象。为了确保最大程度的优化,常用的一个技巧就是用 is_trivially_destructible
模板来判断类是否是可平凡析构的(不调用析构函数,不会造成任何资源泄漏问题)。
模板返回的结果还是一个类,要么是 true_type
,要么是false_type
。如果要得到布尔值的话,当然使用 is_trivially_destructible<T>::value
就可以,但此处不需要。我们需要的是,使用 () 调用该类型的构造函数返回true_type/false_type
,让编译器根据数值类型来选择合适的重载,这样的写法会优雅一些。这样,在优化编译的情况下,编译器可以把不需要的析构操作彻底全部删除。
类似的traits类还有:
| is_array is_enum is_function is_pointer is_reference is_const has_virtual_destructor
|