跟我学c++高级篇——模板元编程之五模板和元编程

一、模板编程

本文的主要目的不是讲模板编程是什么,怎么进行模板编程,而是分析一下模板编程中的哪些技术和元编程和融合在一起,互相影响。模板编程的经典书籍和资料太多了,最经典就是侯捷的《c++ 模板编程全览》,B站上还有他的视频。不过需要说明的是这个已经有第二版了,但是只有英文版,大家可以看一看。
模板元编程在c++中离不开模板编程,而模板编程编程又可以提供元编程的基础方式,下面会就这些分别进行阐述。

二、全特化和偏特化

1、模板的特化
模板的特化非常容易理解,就是把模板中的模板参数给定具体的类型。看下面的例子:

//模板
template <typename T,typname N>
class Data
{};
//特化
template<>
class Data<int,int>
{};

特化,特,就是特别的,也就是不同于广泛的,泛型中的特例,这就好明白了。

2、全特化

特化分为全特化和偏特化,全特化指的是把模板类型参数全部给定,像上面的例子就是,这里再举一个简单的:

//模板
template <typename T>
class Data
{};
//特化
template<>
class Data<int>
{};

3、偏特化
偏特化,顾名思义,其实就是只对部分模板参数进行了特化,也即指定类型。这里不举比较容易理解的,举几个不常见的,一般指针也被当成偏特化:

//模板
template <typename T>
class Data
{};
//指针偏特化
template <typename T>
class Data<T*>
{};
///
//模板
template <typename T,typname N>
class Data
{};
//相同参数偏特化
template<typename T>
class Data<T,T>
{};
//单独偏特化
template<typename N>
class Data<int,N>
{};

4、变量模板
这个在前面刚刚分析过,和constexpr在一起可以在元编程里起到很重要的作用。

5、SFINAE和Concepts
Concepts不用多说,才讲过,它在元编程中的作用就是控制类型的许可,变相的if else语句或者Assert语句。SFINAE其实和Concepts基本一致,只是在c++20之前的标准中使用,不太好理解。

4和5才刚刚分析过,这里就不再举例了,重复来重复去,大家也爱看,有啥不明白的,可以去前面的相关文章里翻一下。SFINAE会在后面的文章专门进行分析讨论和学习。

三、元编程中的模板

1、使用模板实现类型控制
通过上面的偏特化来实现不同的类型,传入不同的类型,根据是何种偏特化或者特化来处理具体的类型。这个在fasle_type、std::conditional等中都有应用。此种比较简单,看一下STL中的类:

std::conditional<
  (sign > 100), double,
  std::conditional<(sign > 80), float,  std::conditional<(sign > 40), int,   char  >::type> ::type
>::type var;

从某种程序这也可以理解为一种分支选择,看怎么理解吧。包括下面的偏特化,其实也可以做为一种分支选择。

2、实现递归和循环
在元编程中,没有循环的定义,但是可以用递归来实现,一个比较常见的例子就是求加法和:

#include <iostream>

template<int N>
constexpr size_t Sum = N + Sum<N-1>;

template<>
constexpr size_t Sum<1> = 1;

int main()
{
    std::cout << sum<4> << std::endl;
    std::cout << sum<5> << std::endl;
    std::cout << sum<6> << std::endl;
    return 0;
}

这个有点变参模板实现的意思。所以可以想到在c++17的折叠表达式,也可以实现类似的情况,有兴趣可以看看前面的文章。

3、实现分支控制
在前面的代码中经常遇到可以通过偏特化来实现,比如模板类的选择和函数模板的不同的特化情况,也可以用SFINAE来实现或者前面提到的constexpr来实现,第一种和第三种都有分析的,下面举一个SFINAE的例子:

template<typename T>
enable_if_t<is_integral<T>::value, T> Get(T t)
{
    return 1;
}

template<typename T>
enable_if_t<!is_integral<T>::value, T> fun(T t)
{
    return 2;
}

类似的代码还有std::is_same等等都可以实现。

4、Policy控制

这个在前面也刚刚分析过,这里不再举例,如果有什么不明白可以翻一下前面的文档。

5、不同类型模板容器
这个很形象化的例子就是std::tuple,这里面的一些实现方式,可以用运行时来实现,如传统的std::map等,也可以用元编程的方式在编译期实现。std::tuple在更新动作时并不是太友好,需要进行整体的移动,时间和空间上都比较麻烦。所以可以在此基础上实现一些类似的容器。
可以看看Boost库的MPL库,再看看前面提到的变参模板,都可以做为实现这种容器的一种参考和手段。

6、模板可以做为元编程中的输入和输出

//输入
#include <type_traits>
template <template <typename> class T1,typename T2>
struct Input
{
	using type = typename T1<T2>::type;
};
template<template<typename> class T1,typename T2>
using Input_ = typename Input<T1, T2>::type;

//输出
template <bool AddRef> struct Output;

template<>
struct Output<true>
{
	template <typename T>
	using type = std::add_lvalue_reference<T>;
};

输入可以理解成在模板参数中,前面的以模板为模板参数的参数可以使用后面模板参数做为参数,绕口,明白意思就可以了。后面这个就是通过特化实现增加左值引用。
通过上面的分析也可以看出模板可以在元编程中的流程、类型和容器以及数据参数中起到重要作用。

四、编译期的控制

元编程在编译展开,有它的优势,最典型的就是不占用运行期的时间,但这不代表其多完美。
在编译期中展开的元编程也有一个致命问题,因为它是在编译期展开,所以当产生的实例化太多,就会极大的增加编译期的时间,记得当初说某大型软件重新编译一次需要五六个小时,想想都可怕。这种现象也叫做编译期实例爆炸,它要么引起崩溃,要么造成编译时间过长不得不强行中断。
那么编译元编程是怎么运行的呢?
因为c++是一门静态语言,所以其很多的控制都是在编译期进行,这就需要对一些异常和安全进行控制,有初步模板编程的开发人员可能会有这种遭遇,一个很简单的模板错误,可能会引起几十甚至上百行的编译错误,让一些开发者根本无从下手,而且这些错误报得莫名其妙,其中只有一两个错误和这个编写错误有关,但湮灭在了这一大堆的错误中。
另外最常见的一个不同在于,在运行期运行的代码,其实例化是可以运行期进行,这就基本限制很少,但在编译期则会出现一个问题,如果代码中有明确的引入一个实例的情况,编译期就会报错误即初始化失败,这在模板编程中经常遇到。那么这种情况一般是增加一个外覆器来解决,不让模板访问到实例的内容。回避开这个问题。这其实也一种Lazy evaluation的方式。同时它也可以做为分支控制的一个手段,所以在元编程中,编译期处理的方式往往都是互相影响互相利用的。
所以在编译期要使用一些Assert进行静态检测,并且可以在编译期进行类型推导和转换,当然也包括继承。包括前面提到的萃取Traits和Policy以及包括c++在新的标准中提供的一系列的Concepts等等。
以上种种的编译期的问题和技术会在一些具体的文章进行分析,就不会在这里统一说明,太复杂了。

五、总结

还得再次说明,本文只是对元编程中用到的模板技术进行一次梳理,不是去讲模板编程。如果对一些细节(模板的模板参数[双重模板或嵌套模板]、成员模板以及变参模板等等)吃不准,还是要多看相关书籍,最重要的一点就是多实践,代码一出,大家都好说话。模板用的少的,尽量回头多看看多翻翻相关的书籍,把一些常用的用法至少要搞明白,这样才能够在学习元编程时付出的代价最小。用一句驾考的话来说就是“禁止以考代学”。
互相学习,共同进步!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值