C++模板元编程 - 挖新坑的时候探索到了模板元编程的新玩法
C++真是一门自由的语言,虽然糖没有C#那么多,但是你想要怎么写,想要实现什么,想要用某种编程范式或者语言特性,它都会提供。
开大数运算类的新坑的时候(又是坑),无意中需要解决一个需求:大数类需要分别实现接受整数和浮点数的构造函数,构造函数中初始化类内保存数据的容器,所有整数的构造方法相同,所有浮点数的构造方法也相同,现在的目标是,找到一个机制,能尽可能少的实现代码,争取做到所有整数实现一份构造函数,所有浮点数实现一份构造函数。
学过C++的你一定会想到模板,这里有了
template<typename T>
Number(T num)
: _num(ToVectorBool(num))
{}
这里我用了vector<bool>来储存一个大数的二进制位模式,ToVectorBool是将数字转换为vector<bool>
但是现在的模板还没有Concept,如何来限制ToVectorBool接受的模板参数只能是整数和浮点数呢?你可能想到用模板的特例化,各种浮点数的实现成为特例,而剩下的整数使用非特例的实现,然后用static_assert来限定非特例实现中不是整数类型的T。
然而不要忘了,特例化的函数是要每个特例写一份代码的,这样多不优雅啊。同样是利用函数重载决议,我们需要一个筛选器,判断类型参数T是整数还是浮点数还是什么都不是。
这样我们就回到了模板元编程的范围,我们需要写一个MultiState模板结构,它接受一个T,和用来接受这个类型T并返回结果的模板结构,用我在模板元编程代码中的约定,就是接受TStatement和TPreds...,依次判断TStatement是否符合TPreds...里每个谓词,返回判断的结果:在第几个谓词哪里得到了TrueType。
我们还需要typedef出State<N>,正好我以前写过Int<N>模板整数类型,就用它了
本着用户不乱用的原则,MultiState本身不能把参数N暴露给用户,于是套了一层MultiStateImpl,用MultiState去“调用”MultiStateImpl
为了可扩展性,这里使用了变长模板参数包,做了一个template<class T> class... TPreds,这样可以用模板参数包的一些技巧,做出接受任意个谓词的MultiState
template<int N> using State = Int<N>; template<int N, class TStatement, template<class T> class TPred, template<class T> class... TPreds> struct MultiStateImpl { using Result = typename If< typename TPred<TStatement>::Result, State<N>, typename MultiStateImpl< N + 1, TStatement, TPreds... >::Result >::Result; }; template<int N, class TStatement> struct MultiStateImpl { using Result = FalseType; }; template<class TStatement, template<class T> class... TPreds> struct MultiState { using Result = typename MultiStateImpl<0, TStatement, TPreds...>::Result; };
可以看到,代码里首先检查TPred<TStatement>::Result,如果正确,直接返回State<N>,否则,返回下一层MultiState<N+1,TStatement, TPreds...>的结果,如果都不成立,到了MultiState<N, TStatement>的实现,就直接返回FalseType,表示哪个都不满足。
爽啊,用起来就是这样的
template<typename TNumber> Number::Number(TNumber& number) : _num(ToVectorBool(number, MultiState<TNumber, IsInteger, IsFloat>::Result())) { } template<typename TInteger> static std::vector<bool> Number::ToVectorBool(TInteger& intNumber, State<1>) { //Integer 实现略 } template<typename TFloat> static std::vector<bool> Number::ToVectorBool(TFloat& floatNumber, State<2>) { //Float 实现略 } template<typename TOther> static std::vector<bool> Number::ToVectorBool(TOther& other, FalseType) { static_assert(0, "Not a number."); }
还可以改,把State也做成模板参数包,State<1>这样的写法还不优雅
感觉怪怪的,不过好有意思