初识C++模板元编程(Template Mega Programming)

前言:毕设时在开源库上做的程序,但是源码看得很晕(当时导师告诉我这是模板元编程,可以不用太在乎),最近自己造轮子时想学习STL的源码,但也是一样的感觉,大致了解他这么做要干什么,但是不知道里面的机制。于是开始学习《C++模板元编程》,看完第二章对一些东西豁然开朗。

PS:该书也有点老了,C++11标准还没出来,主要用的Boost库。

Traits(特征)

说正题,在STL中经常可以见到后缀为traits的名字,比如经常用到的std::string,本质是类模板basic_string的第一个参数charT为char的实例化。相应的,用来表示宽字节的字符串模板类wstring,charT为wchar_t。

template < class charT,
           class traits = char_traits<charT>,    // basic_string::traits_type
           class Alloc = allocator<charT>        // basic_string::allocator_type
           > class basic_string;
typedef basic_string<char, char_traits<char>, allocator<char> >
 string;

根据模板声明,3个模板参数,charT表示存放字符类型(Char Type);Alloc则是allocator<T>的实例化,通常的new是一次性分配完内存再在内存上构造T类型的元素,allocator<T>则分离了这两个过程,可以先分配一段连续的内存空间,相当于内存池,再直接在上面构造T类型元素,不需要使用时销毁元素即可,内存空间并未释放,可以重新在上面构造新的元素。这样就省去了每次都重新分配空间再构造元素的过程。有点类似对C的malloc/free的封装。(没仔细研究,不去祥述)

第2模板参数traits则是char_traits模板参数为charT的实例化

template<class _Elem>
    struct char_traits
        : public _Char_traits<_Elem, long>
    {    // properties of a string or stream unknown element
    };

可以发现char_traits继承于由_Char_traits的实例化的模板类,可以提供扩展。这里没有扩展,基本可以将char_traits等价模板类于_Char_traits<_Elem, long>

template<class _Elem,
    class _Int_type>
    struct _Char_traits
    {    // properties of a string or stream element
    typedef _Elem char_type;
    typedef _Int_type int_type;
    typedef streampos pos_type;
    typedef streamoff off_type;
    typedef _Mbstatet state_type;
    // ...
    }

省略了后面的静态方法,可以看出在此之前有5个typedef。在C++中,类的内部可以用typedef定义类型别名,可以像使用静态成员一样使用它。
也就是说可以用模板类_Char_traits<_Elem, _Int_type>::char_type来得到类型名。结合之前所说,char_traits继承自该模板类,因此可以用char_traits::char_type来得到类型名。可以发现,对模板类进行继承有点像对模板类进行实例化获取类型别名(比如typedef std::map<int, std::string> MapIntStr;),大大减轻了键盘输入的压力。

回过头来,std::basic_string的第2个模板参数traits是由字符类型charT作为模板参数(_Elem)实例化的char_traits

也就是说在std::basic_string中,通过模板参数traits可以直接获得类型。  比如traits::char_type等价于charT。

问题来了:为什么要特意给模板参数再套到一个模板上?

直接借用书上对迭代器traits的例子了,跟char_traits其实是类似的。

当我们需要对迭代器进行交换时(功能是交换迭代器指向的值)

    template <class FwIt1, class FwIt2>
    void iter_swap(FwIt1 i1, FwIt2 i2)
    {
        typename FwIt1::value_type tmp = *i1;
        *i1 = *i2;
        *i2 = tmp;
    }

这个实现有个问题,FwIt必须就像char_traits一样,内部有句typedef把某种类型取个别名叫value_type。如果是自己实现新的迭代器,可以一直按照这个标准在每个类模板里加上对应的typedef。

问题在于,且不谈其他的类模板是否实现了typedef,就指针而言在iter_swap中是不兼容的。因此需要加个像适配器一样的东西,让指针也能接入到iter_swap中。

现在我们编写一个iterator_traits作为适配器。

    template <class Iter>
    struct iterator_traits
    {
        typedef typename Iter::value_type value_type;
    };

类中只有一行代码进行类型别名定义,这样iterator_traits<Iter>::value_type就等价于Iter::value_type,用类型实例化某个类模板来取代类型自己。

C++模板有个优秀的特性是特例化(specialization),当模板参数有着某种特别类型时,选择实例化“特例类型”而不是通用模板。比如std::vector<bool>和其他一般的std::vector<T>的内部实现是不一样的。

对指针类型进行特例化后

    template <class T>
    struct iterator_traits<T*>
    {
        typedef typename T value_type;
    };

这样假如是用指针类型来实例化iterator_traits,将会选择这个来实例化,而不是前一个通用形式。(选择哪一个模板实例化这里也不详述,《C++ Primer》有相关说明)
原来的iter_swap就变成了

    template <class FwIt1, class FwIt2>
    void iter_swap(FwIt1 i1, FwIt2 i2)
    {
    //    typename FwIt1::value_type tmp = *i1;
        typename
            iterator_traits<FwIt1>::value_type
        tmp = *i1;
        *i1 = *i2;
        *i2 = tmp;
    }

可以看出,在模板参数之上再套个模板是起到适配器的作用,在类模板中需要用更通用的方式取得类型信息,相当于在一个体系内达成统一的标准。

C++标准库已经实现了iterator_traits,只有迭代器才能用作模板参数,因为只有迭代器类实现了那几个特定的typedef。另外,iterator_traits也针对指针类型进行了特例化。假如用容器作为模板参数,会报错(比如没有实现哪个typedef)。

回到最初的问题:什么是模板元编程?使用这种traits技巧跟模板元编程有什么关系?

简要地解释元编程(Meta Programming),元编程类似一个解释器,比如C/C++都是生成了汇编最终生成机器码让计算机运行的。

而模板元编程(TMP)是在C++的体系内,模板实例化后会变成新的代码,但这份新的代码仍然是C++代码,模板的优点就像它的名字一样,套个不同的参数能够刻画出一组组功能相似类型又不同的类或函数。比如std::vector<int>和std::vector<float>就是两种类型,但是却有着通用的方法。

模板的实例化是发生在编译期间的,这是C++ TMP的根本原因,能够在编译期间完成的事情不需要在运行期间做到,虽然编译时间更长,但是会得到运行效率的提升。

直接给出《C++模板元编程》第一章的例子,使用模板元编程来把二进制数转换成十进制数,在编译好时已经计算出了实例化的值。

template <unsigned long N>
struct binary
{
    static unsigned const value
        = binary<N / 10>::value << 1
        | N % 10;
};

// 特化
template <>
struct binary<0>
{
    static unsigned const value = 0;
};

它的思想是,一段短的类模板代码等价于好长好长的一个常量表达式(编译期间就确定),在运行时使用binary<110010>::value相当于直接使用一个常量表达式(用作赋值、实参等等)。而若按照传统的方法写个函数来计算,就是在运行时进行计算。

而类型跟值本质上是一样的,就像我们在类中进行了typedef,在类外部使用别名的方式就跟使用静态成员方式一样。模板元编程不仅仅是做这种数值运算,也可以做类型运算,前面花了较大大篇幅提到traits就是一个典例。

时间: 2024-10-08 20:50:48

初识C++模板元编程(Template Mega Programming)的相关文章

AutoSharedLibrary -- 基于模板元编程技术的跨平台C++动态链接加载库

基于模板元编程技术的跨平台C++动态链接加载库.通过模板技术,使用者仅需通过简单的宏,即可使编译器在编译期自动生成加载动态链接库导出符号的代码,无任何额外的运行时开销. ASL_LIBRARY_BEGIN(TestLib) ASL_SYMBOL(Proc_test1, test1, false) ASL_SYMBOL(Proc_test2, test2, true) ASL_LIBRARY_END() TestLib theLib; try { theLib.Load("./1.so"

读书笔记 effective c++ Item 48 了解模板元编程

1. TMP是什么? 模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行.你可以想一想:一个模板元程序是用C++实现的并且可以在C++编译器内部运行的一个程序,它的输出——从模板中实例化出来的C++源码片段——会像往常一样被编译. 2. 使用TMP的优势 如果这没有冲击到你,是因为你没有足够尽力去想. C++不是为了模板元编程而设计的,但是自从TMP早在1990年被发现之后,它就被证明是非常有用的,为了使TMP的使用更加容易

C++模板元编程 - 2 模仿haskell的列表以及相关操作

这是昨天和今天写的东西,利用C++的可变模板参数包以及包展开,模式匹配的一些东西做的,感觉用typename...比轮子叔那个List<A,List<B, List<C, D>>>的设计要好看不少. List有一个很巧妙的继承,只有那么做才能使用类似于List<>::Rest的写法,直接定义成template<typename T, typename... TArgs>List是不行的. Change这里可以给一个args...换另一个包装,这里

C++拾遗--模板元编程

C++拾遗--模板元编程 前言 模板元是用于递归加速的,把运行期的函数调用变到编译期进行代码展开,类似于内联函数.下面看一个实例:斐波那契数列第n项求解. 模板元编程 #include <iostream> #include <ctime> using namespace std; //递归法 int fib(int n) { if (n < 0) return 0; if (n == 1 || n == 2) return 1; return fib(n - 1) + fi

模板元编程

//模板元把运行时消耗的时间,在编译期间优化 //递归极其消耗时间 1 #include <iostream> 2 3 //模板元把运行时消耗的时间,在编译期间优化 4 //递归极其消耗时间 5 6 template <int N> 7 struct data 8 { 9 enum { res = data<N - 1>::res + data<N - 2>::res }; 10 }; 11 12 template <> 13 struct da

模板元编程加速递归

//模板元编程加速递归 //缺点:模板元在编译时处理,无法调试,并且会加大代码体积 #include<iostream> using namespace std; template <int N> struct data { enum{res = data<N - 1>::res + data<N - 2>::res}; }; template <>//模板具体化 struct data<1> { enum{res = 1}; };

【C/C++学院】0816-引用包装器/仿函数/转义字符 R”()”/using别名/模板元编程 比递归优化/智能指针/多线程/静态断言以及调试技能的要求 assert

引用包装器  std::ref(变量) #include<iostream> template<class T> void com(T arg)//模板函数,引用无效,引用包装器 { std::cout <<"com ="<< &arg << "\n"; arg++; } void main() { int count = 10; int & rcount = count; com(coun

C++模板元编程 - 3 逻辑结构,递归,一点列表的零碎,一点SFINAE

本来想把scanr,foldr什么的都写了的,一想太麻烦了,就算了,模板元编程差不多也该结束了,离开学还有10天,之前几天部门还要纳新什么的,写不了几天代码了,所以赶紧把这个结束掉,明天继续抄轮子叔的Win32库去. 逻辑结构和递归说白了就是做了一个If,一个For_N,If就和Excel里的If一样,For_N是把一个模板结构迭代N遍,为了所谓的方便,把If做成了宏,写起来还挺有意思的 1 template<typename TTest, typename TTrue, typename TF

使用模板元编程操作类型集合(C++11下的TypeList)

Wrote by mutouyun. (http://darkc.at/cxx-type-list/) 群里有个朋友要实现这么一个功能:如何在编译期把一个函数类型的参数减少一个. 简单来说,就是实现下面这个模板: remove_func_par<2, void(int, long, short)>::type; // type = void(int, long) 根据输入的编译期整数,把函数参数表里对应的参数干掉一个. 为了实现这种功能,我们需要操作变参模板的参数包.比如像这样: // mak