模板元编程(编译期计算)初探

模板元编程(编译期计算)初探

编译期求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进行赋值,说明编译期就已经计算出结果,而不是运行期进行的计算。

image-20220719095417548

编译期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
/* IF Then Eles 模板 */
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
/* While 模板 */
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;
};

/* 整数常数模板 */
/* integral_constant 模板同时包含了整数的类型和数值,而通过这个类型的 value 成
员我们又可以重新取回这个数值。有了这个模板的帮忙,我们就可以进行一些更通用的计算 */
template<typename T, T v>
struct integral_constant_selfmade
{
static const T value = v;
typedef T value_type;
typedef integral_constant_selfmade type;
};


/* Body定义 */
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_typetrue_type

image-20220719113735906

可以发现他们就是模板integral_constant的一种实现而已。

他的应用常在函数重载中用到:

1
2
3
4
5
6
7
8
9
10
11
12
13
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类还有:

1
2
3
4
5
6
7
is_array
is_enum
is_function
is_pointer
is_reference
is_const
has_virtual_destructor

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!