现代C++与模板元编程

最近在重温《c++程序设计新思维》这本经典著作,感慨颇多。由于成书较早,书中很多元编程的例子使用c++98实现的。而如今c++20即将带着concept,Ranges等新特性一同到来,不得不说光阴荏苒。在c++11之后,得益于新标准很多元编程的复杂技巧能被简化了,STL也提供了诸如<type_traits>这样的基础设施,c++14更是大幅度扩展了编译期计算的适用面,这些都对元编程产生了不小的影响。今天我将使用书中最简单也就是最基础的元容器TypeList来初步介绍现代c++在元编程领域的魅力。

本文索引

什么是TypeList

TypeList的定义

元函数的实现

Length元函数求list长度

TypeAt获取索引位置上的类型

IndexOf获得指定类型在list中的索引

Append为TypeList添加元素

Erase和EraseAll删除元素

NoDuplicates去除所有重复type

Replace和ReplaceAll

Derived2Front将派生类型移动至list前部

元函数实现总结

示例

自制tuple

简化工厂模式

总结

什么是TypeList

TypeList顾名思义,是一个存储和操作type的list,你没有看错,存储的是type(类型信息)而不是data。

这些被存储的type也被称为元数据,存储的它们的TypeList也被称为元容器。

那么,我们存储了这些元数据有什么用呢?答案是用处很多,比如tuple,工厂模式,这两个后面会举例;还可用来实现CRTP技巧(一种元编程技巧),线性化继承结构等,这些也在原书中有详细的演示。

不过光看我在上面的解释多半是理解不了什么是TypeList以及它有什么用的,不过没关系,元编程本身就是高度抽象的脑力活动,只有多读代码勤思考才能有所收获。下面我就展示如何使用现代c++实现一个TypeList,以及对c++11以前的古典版本做些简单的对比。

TypeList的定义

最初的问题是我们要如何存储类型呢?数据可以存变量,单是type和data的不同的东西,怎么办?

聪明的你可能以及想到了,我们可以让模板参数成为type信息的容器。

但是紧接着第二个问题来了,所谓list它的元素数量是固定的,但是直到c++11以前,模板参数的数量都是固定的,那么怎么办?

其实也很简单,参考普通list的链表实现法,我们也可以用相同的思想去构造一个“异质链表”:

template <typename T, typename U> struct TypeList { typedef T Head; typedef U Tail; };

这就是最简单的定义,其中,T是一个普通的类型,而U则是一个普通类型或TypeList。创建TypeList是这样的:

// 创建unsigned char和signed char的list typedef TypeList<unsigned char, signed char> TypedChars; // 现在我们把char也添加进去 typedef TypeList<char, TypeList<unsigned char, signed char> > Chars; // 创建int,short,long,long long的list typedef TypeList<int, TypeList<short, TypeList<long, long long> > > Ints;

可以看到,通过TypeList环环相扣,我们就能把所有的类型都存储在一个模板类组成的链表里了。但是这种实现的弊端有很多:

首先是定义类型不方便,上面的第三个例子中,仅仅为了4个元素的list我们就要写出大量的嵌套代码,可读性大打折扣;

原书中提到,为了简化定义,Loki库提供了TYPELIST_N这个宏,但是它是硬编码的,而且最大只支持50个元素,硬编码在程序员的世界里始终是丑陋的,更不用说还存在硬编码的数量上限,而且这么做也违反了“永远不要复读你自己”的原则,不过对于c++98来说只能如此;

我们没办法清晰得表示只有一个元素或是没有元素的list,所以我们只能引入一个空类NullType来表示list的某一位上没有数据存在,比如:TypeList<char, NullType>或TypeList<NullType, NullType>,当然,你特化出单参数的TypeList也只是换汤不换药。

无法有效得表示list的结尾,除非像上面一样使用NullType最为终结标志。

好在现代c++有变长模板,上述限制大多都不存在了:

template <typename...> struct TypeList; template <typename Head, typename... Tails> struct TypeList<Head, Tails...> { using head = Head; using tails = TypeList<Tails...>; }; // 针对空list的特化 template <> struct TypeList<> {};

通过变长模板,我们可以轻松定义任意长度的list:

using NumericList = TypeList<short, unsigned short, int, unsigned int, long, unsigned long>;

同时,我们特化出了空的TypeList,现在我们可以用它作为终止标记,而不用引入新的类型。如果你对变长模板不熟悉,可以搜索相关的资料,cnblogs上就有很多优质教程,介绍这个语法特性已经超过了本文的讨论范畴。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/c0c52608c23b37e352041892c54b0e9c.html