前言
最近偶然看到大佬的一种模板的使用方法,感觉很有意思。简单记录下自己的理解。
模板是c++的一种特性,允许函数或者类(对象)通过泛型(generic types)的形式表现或者运行。
模板可以使得函数或类在对应不同的类型(types)的时候正常工作,而无需为每一种类型分别写一份代码。
熟悉STL标准库的同学应该对模板都不陌生,但是模板太大,这里仅仅是想从一个很小的地方展开——从编译器常量来探讨模板相关知识。
1.模板定义
template<class T>
class ValArr
{
public:
using value_type = T;
constexpr ValArr():m_val(0){}
constexpr ValArr(value_type i):m_val(i){}
constexpr value_type operator()() {return m_val;}
private:
value_type m_val;
};
假设有如上 constexpr anytype ,我们想要对其在编译期进行操作,那么可以运用以下方法:
template <class T,class T2,T2 ...Val>
struct FixData{
constexpr static const size_t N = sizeof...(Val) + 1 ;
constexpr static const T data[N]={Val...,{}};
};
1,这里的N是通过sizeof…推导出N的大小,使data在编译器确定长度。
2.data[N]={val…},这个用法也是模板中经常使用的,同样也会在编译期进行赋值。
经过如上代码我们可以实现如下功能:
constexpr static const FixData<ValArr,char,‘2’,‘3’,‘4’> tmpFixDataArr2;
为了方便大家阅读,下面加上以字符串形式表现的代码:
template <char ...Val>
struct FixData{
constexpr static const size_t N = sizeof...(Val) + 1 ;
constexpr static const char data[N]={Val...,'\0'};
};
constexpr static const FixData<'2','3','4'> tmpFixDataArr2;
如上,tmpFixDataArr2在编译期分配空间时,data已经被赋值为“234”的字符串。我们成功将数据化作了模板元编程模板的输入,可以提高模板代码的可扩展性。
2.实例化
这里引入了一个新问题,通过以上方式传入类型很麻烦,需要将数组内每个值全部列出来,并且不同的数据会产数不同的模板结构(例如FixData<‘1’,‘2’>和FixData<‘2’,‘1’>),并且导致二进制膨胀,我们采用以下方法进行解决:
//anytype:
template<class T,class T2 ,T2 ...Val>
auto MakeFaxValArr(FixData<T,T2,Val...>)->
FixData<T,T2,Val...>;
//char
template<char ...Val>
auto MakeFaxValArr(FixData<Val...>)->
FixData<Val...>;
这段代码仅有声明没有实现,他的作用仅仅是为了使用编译器的自动推导功能推导出类型,这个函数只要写入实参即可,用尾置返回告诉编译器类型,例:
//anytype:
decltype(MakeFaxValArr(FixData<valArr<char>,char,'1','2','3','4'>{})) arr;
//char:
decltype(MakeFaxValArr(FixData('1','2','3'){})) arr2;
3.数据填充
实参数据化为单个数据输入,借助宏来解决:
//anytype
template <size_t M,class T,size_t N>
constexpr auto GetValFromArr( const T(&arr) [N])->T
{
return (arr[ M < N ? M : N - 1 ]);
}
//char
template <size_t M,size_t N>constexpr
char GetValFromArr( const char(&arr) [N])
{
return (arr[ M < N ? M : N - 1 ]);
}
#define ValIndex(n,valArr) GetValFromArr<0x##n##00>(valArr),GetValFromArr<0x##n##01>(valArr),\
GetValFromArr<0x##n##02>(valArr),GetValFromArr<0x##n##03>(valArr),\
GetValFromArr<0x##n##04>(valArr),GetValFromArr<0x##n##05>(valArr),\
GetValFromArr<0x##n##06>(valArr),GetValFromArr<0x##n##07>(valArr),\
GetValFromArr<0x##n##08>(valArr),GetValFromArr<0x##n##09>(valArr),\
GetValFromArr<0x##n##0a>(valArr),GetValFromArr<0x##n##0b>(valArr),\
GetValFromArr<0x##n##0c>(valArr),GetValFromArr<0x##n##0d>(valArr),\
GetValFromArr<0x##n##0e>(valArr),GetValFromArr<0x##n##0f>(valArr)
#define VarIndex_L(valArr) ValIndex(0,valArr),ValIndex(1,valArr),ValIndex(2,valArr),ValIndex(3,valArr)
#define VarIndex_H(valArr) ValIndex(4,valArr),ValIndex(5,valArr),ValIndex(6,valArr),ValIndex(7,valArr)
#define VarIndexSquence(valArr) VarIndex_L(valArr),VarIndex_L(valArr)
1.先关注GetValFromArr这个函数,我们不必将模板所有类型全部填入,仅仅填入第一个M代表从数组中获取数据的序号即可,后续类型让编译器自动推导出来。
2.添加两个自动获取数据的宏,暂时只写到最大长度128位。
经过如上改动,我们可以不用一个个输入数据,依旧附加了char举例:
//anytype:
constexpr static const FixData<ValArr<char>,char,'1','2','3'> tmpFixDataArr;
FixData tmpData(GetValFromArr(valArr));
//char:
FixData tmpData2(GetValFromArr("12345"));
上述两者结合在一起:
//anytype:
decltype(MakeFaxValArr(FixData<VarIndexSquence(valArr)>{})) arr;
//char:
decltype(MakeFaxValArr(FixData<VarIndexSquence("12345")>{})) arr;
这里引入了新的问题,我们的宏定义的大小为128,即上述数据会被识别为{‘1’,‘2’,‘3’,‘4’,‘5’,‘/0’,‘/0’…},违背了我们的初衷,现在问题是如何将数据从’/0’处截断。下面提供一种思路。
4.数据截断
//anytype
template<class T,class T2,T2 ...Val>
auto AutoSplitValArr(FixData<T,T2,Val...>)->
decltype(MakeFaxValArr(FixData<T,T2,Val>{}...));
//char
template<char ...Val>
auto AutoSplitValArr(FixData<Val...>)->
decltype(MakeFaxValArr(FixData<Val>{}...));
注意函数实参和返回值,传参类型是FixData<T,T2,Val…>类型,返回值是MakeFaxValArr(FixData<T,T2,Val>{}…)。
从char来理解,我们传入的模板类型为FixData<‘1’,‘2’,‘3’>,返回类型为decltype(MakeFaxValArr(FixData<‘1’>,FixData<‘2’>,…))。
回到问题,我们成功将数据进行了单个的分离,接下来就是要对’\0’进行单独处理。
这里可以通过重载MakeFaxValArr函数进行数据截断,方法如下:
template <class T,class T2,T2 ...Val1,T2 Val2,T2 ...Val3>
auto MakeFaxValArr(FixData<T,T2,Val1...>,FixData<T,T2,Val2>,FixData<T,T2,Val3>...)->
decltype( MakeFaxValArr(FixData<T,T2,Val1...,Val2>{},FixData<T,T2,Val3>{}...) );
template <class T,class T2,T2 ...Val1,T2 ...Val2>
auto MakeFaxValArr(FixData<T,T2,Val1...>,FixData<T,T2,'\0'>,FixData<T,T2,Val2>...)->
FixData<T,T2,Val1...>;//这里方便理解就用了'/0',实际按照T类型对象的截断数据值
//char:
template <char ...Val1,char Val2,char ...Val3>
auto MakeFaxValArr(FixData<Val1...>,FixData<Val2>,FixData<Val3>...)->
decltype( MakeFaxValArr(FixData<Val1...,Val2>{},FixData<Val3>{}...) );
template <char ...Val1,char ...Val2>
auto MakeFaxValArr(FixData<Val1...>,FixData<'\0'>,FixData<Val2>...)->
FixData<Val1...>;
这里比较巧妙,首先通过第一个重载函数的模板类型推导将数据分为三类,分别为val…的数组,待处理的数据val2,后续数据val3…,返回值将val…和val2进行组合,val3继续进行递归。
当检测到数据为’\0’时,模板会匹配到第二个重载函数,返回类型仅仅为va1…,后续数据被舍弃。
以char类型为例:
decltype(AutoSplitValArr(FixData<VarIndexSquence("12345")>{}))
这时数据被识别为:
“12345”
'/0’已经被我们成功截断了。
5.总结
通过探讨如何一种处理编译期常量的方法,分享了一个模板实例代码并介绍了几种巧妙的模板使用方法。模板具有保证类型安全、无关平台、可移植性和可复用性高的特点。并且因模板的无关数据类型的特点,正确的使用模板可以节省很多重复开发的流程,甚至包括在本篇和大家探讨的编译期字符常量,都可以借用模板来进行优化,但是模板也存在着一些缺点,比如因为模板不支持二进制的实时扩展性,所以其不能像库那样被广泛使用,但是在日常开发中结合STL使用模板可以大大减少开发时间,减少代码量。