您的位置:首页 > 编程语言 > C语言/C++

【C/C++】C++模板元编程 入门简介

2013-10-28 21:05 162 查看
原文地址:C++模板元编程 入门简介

最近一直在看STL和Boost,源码里边好多涉及到模板元编程技术,简单了解一下,备忘(Boost Python中的涉及模板元的部分重点关注一下)。

// 主模板
template<int N>
struct Fib
{
enum { Result = Fib<N-1>::Result + Fib<N-2>::Result };
};

// 完全特化版
template <>
struct Fib<1>
{
enum { Result = 1 };
};

// 完全特化版
template <>
struct Fib<0>
{
enum { Result = 0 };
};

int main()
{
int i = Fib<10>::Result;
// std::cout << i << std::endl;
}


主要思想

利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元程序则由编译器在编译期解释执行。

优劣及适用情况

通过将计算从运行期转移至编译期,在结果程序启动之前做尽可能多的工作,最终获得速度更快的程序。也就是说模板元编程的优势在于:

  1.以编译耗时为代价换来卓越的运行期性能(一般用于为性能要求严格的数值计算换取更高的性能)。通常来说,一个有意义的程序的运行次数(或服役时间)总是远远超过编译次数(或编译时间)。

  2.提供编译期类型计算,通常这才是模板元编程大放异彩的地方。

模板元编程技术并非都是优点:

  1.代码可读性差,以类模板的方式描述算法也许有点抽象。

  2.调试困难,元程序执行于编译期,没有用于单步跟踪元程序执行的调试器(用于设置断点、察看数据等)。程序员可做的只能是等待编译过程失败,然后人工破译编译器倾泻到屏幕上的错误信息。

  3.编译时间长,通常带有模板元程序的程序生成的代码尺寸要比普通程序的大,

  4.可移植性较差,对于模板元编程使用的高级模板特性,不同的编译器的支持度不同。

总结:

模板元编程技术不适用普通程序员的日常应用,它常常会做为类库开发的提供技术支持,为常规模板代码的内核的关键算法实现更好的性能或者编译期类型计算。模板元程序几乎总是应该与常规代码结合使用被封装在一个程序库的内部。对于库的用户来说,它应该是透明的。

工程应用实例

1. Blitz++:由于模板元编程最先是因为数值计算而被发现的,因此早期的研究工作主要集中于数值计算方面,Blitz++库利用模板将运行期计算转移至编译期的库,主要提供了对向量、矩阵等进行处理的线性代数计算。

2.Loki:将模板元编程在类型计算方面的威力应用于设计模式领域,利用元编程(以及其他一些重要的设计技术)实现了一些常见的设计模式之泛型版本。Loki库中的Abstract Factory泛型模式即借助于这种机制实现在不损失类型安全性的前提下降低对类型的静态依赖性。

3.Boost:元编程库目前主要包含MPL、Type Traits和Static Assert等库。 Static Assert和Type Traits用作MPL的基础。Boost Type Traits库包含一系列traits类,用于萃取C++类型特征。另外还包含了一些转换traits(例如移除一个类型的const修饰符等)。Boost Static Assert库用于编译期断言,如果评估的表达式编译时计算结果为true,则代码可以通过编译,否则编译报错。

技术细节

模板元编程使用静态C++语言成分,编程风格类似于函数式编程,在模板元编程中,主要操作整型(包括布尔类型、字符类型、整数类型)常量和类型,不可以使用变量、赋值语句和迭代结构等。被操纵的实体也称为元数据(Metadata),所有元数据均可作为模板参数。

由于在模板元编程中不可以使用变量,我们只能使用typedef名字和整型常量。它们分别采用一个类型和整数值进行初始化,之后不能再赋予新的类型或数值。如果需要新的类型或数值,必须引入新的typedef名字或常量。
其它范例

// 仅声明
struct Nil;

// 主模板
template <typename T>
struct IsPointer
{
enum { Result = false };
typedef Nil ValueType;
};

// 局部特化
template <typename T>
struct IsPointer<T*>
{
enum { Result = true };
typedef T ValueType;
};

// 示例
int main()
{
cout << IsPointer<int*>::Result << endl;
cout << IsPointer<int>::Result << endl;
IsPointer<int*>::ValueType i = 1;
//IsPointer<int>::ValueType j = 1;
// 错误:使用未定义的类型Nil
}


//主模板
template<bool>
struct StaticAssert;

// 完全特化
template<>
struct StaticAssert<true>
{};

// 辅助宏
#define STATIC_ASSERT(exp)\
{ StaticAssert<((exp) != 0)> StaticAssertFailed; }

int main()
{
STATIC_ASSERT(0>1);
}


template <int N>
struct Factorial
{
enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0>
{
enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
int x = Factorial<4>::value; // == 24
int y = Factorial<0>::value; // == 1
}


Template type parameter:

template<typename T>
struct Container {
T t;
};

// pass type "long" as argument.
Container<long> test;
Template integer parameter:

template<unsigned int S>
struct Vector {
unsigned char bytes[S];
};

// pass 3 as argument.
Vector<3> test;
Template pointer parameter (passing a pointer to a function)

template<void (*F)()>
struct FunctionWrapper {
static void call_it() { F(); }
};

// pass address of function do_it as argument.
void do_it() { }
FunctionWrapper<&do_it> test;
Template reference parameter (passing an integer)

template<int &A>
struct SillyExample {
static void do_it() { A = 10; }
};

// pass flag as argument
int flag;
SillyExample<flag> test;
Template template parameter.

template<template<typename T> class AllocatePolicy>
struct Pool {
void allocate(size_t n) {
int *p = AllocatePolicy<int>::allocate(n);
}
};

// pass the template "allocator" as argument.
template<typename T>
struct allocator { static T * allocate(size_t n) { return 0; } };
Pool<allocator> test;
A template without any parameters is not possible. But a template without any explicit argument is possible - it has default arguments:

template<unsigned int SIZE = 3>
struct Vector {
unsigned char buffer[SIZE];
};

Vector<> test;
Syntactically, template<> is reserved to mark an explicit template specialization, instead of a template without parameters:

template<>
struct Vector<3> {
// alternative definition for SIZE == 3
};


template metaprogramming (TMP)(模板元编程)是写 template-based(基于模板)的运行于编译期间的 C++ 程序的过程。考虑一下:一个 template metaprogram(模板元程序)是用 C++ 写的运行于 C++ 编译器中的程序。当一个 TMP 程序运行完成,它的输出——从 templates(模板)实例化出的
C++ 源代码片断——随后被正常编译。 C++模板元编程,是使用template进行编译期运算的一种机制,可以认为是C++的一种编程方式。

C++模板元编程的两个例子

C++模板元编程,是使用template进行编译期运算的一种机制,可以认为是C++的一种编程方式。

第一个例子:计算整数N的阶乘。

//模板的一般形式
template<int N>
class Factorial
{
public:
enum
{
_result = N * Factorial<N-1>::_result
};
};

//用于结束递归规则的特化模板
template<>
class Factorial<0>
{
public:
enum
{
_result = 1
};
};

int main()
{
const int Num = 10;
cout << Num << "! = " << Factorial<Num>::_result << endl;
}

运行结果:10! = 3628800
其中的思考方式,我感觉是数学归纳法的应用。注意模板在其中起的作用,在编译期,编译器使用template生成了class Factorial<0>……class Factorial<10>一共11个class定义,在程序运行时其实计算过程并没有占用CPU时间,只不过这么多class的定义占用了一些内存。

第二个例子:编译期的if语句

这是 Bjarne Stroustrup在《Evolving a language in and for the real world C++ 1991-2006》一文中举的例子。

struct T_Left
{
int value;
};
struct T_Right
{
char value;
};

template<bool b, class X, class Y>
struct if_
{
typedef X type; // use X if b is true
};
template<class X, class Y>
struct if_<false, X, Y>
{
typedef Y type; // use Y if b is false
};

int main()
{
cout << "Type Size = " << sizeof(if_<sizeof(T_Left) < 4, T_Left, T_Right>::type) << endl;
}

其实也是根据编译期能确定的值,来进行编译期的选择。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: