模板处理编译期常量

前言
最近偶然看到大佬的一种模板的使用方法,感觉很有意思。简单记录下自己的理解。

模板是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使用模板可以大大减少开发时间,减少代码量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值